https://juejin.im/entry/5aa69dc851882555602093b2
- 什么是自动拆装包?
http://blog.csdn.net/u012542422/article/details/50721701
Java有8种基本类型,每种基本类型又有对应的包装类型。
在Java中,一切都以对象作为基础,但是基本类型并不是对象,
如果想以对象的方式使用这8中基本类型,可以将它们转换为对应的包装类型
转换工作由编译器来完成
(1)当需要一个对象的时候会自动装箱,比如Integer a = 10;equals(Object o)方法的参数是Object对象,所以需要装箱。
(2)当需要一个基本类型时会自动拆箱,比如int a = new Integer(10);算术运算是在基本类型间进行的,所以当遇到算术运算时会自动拆箱,比如代码中的 c == (a + b);
(3) 包装类型 == 基本类型时,包装类型自动拆箱; -
Java的作用域修饰词有哪些?他们的作用是什么?(不要忘记default,default可以理解成包级别的作用域修饰符)
Android基础知识
Activity的生命周期。启动模式。
https://www.cnblogs.com/lwbqqyumidi/p/3769113.html
https://mp.weixin.qq.com/s?__biz=MzIwMzYwMTk1NA%3D%3D&mid=2247488588&idx=1&sn=3f7c59654835ec8d560610ba97d10fc0
在实际应用场景中,假设A Activity位于栈顶,此时用户操作,从A Activity跳转到B Activity。那么对AB来说,具体会回调哪些生命周期中的方法呢?回调方法的具体回调顺序又是怎么样的呢?
开始时,A被实例化,执行的回调有A:onCreate -> A:onStart -> A:onResume。
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。
至此,Activity栈中只有A。在Android中,有两个按键在影响Activity生命周期这块需要格外区分下,即Back键和Home键。我们先直接看下实验结果:
此时如果按下Back键,系统返回到桌面,并依次执行A:onPause -> A:onStop -> A:onDestroy。
此时如果按下Home键(非长按),系统返回到桌面,并依次执行A:onPause -> A:onStop。由此可见,Back键和Home键主要区别在于是否会执行onDestroy。
横竖屏切换(Nexus x5 API 26 Android 7.0模拟器)
1、不设置Activity的Android:configChanges时,切屏会重新调用各个生命周期,不会调用onConfigurationChanged()
,但是会调用onSaveInstanceState(Bundle outState)
onPause
onSaveInstanceState
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
其中onSaveInstanceState与onCreate与onRestoreInstanceState的Bundle参数为同一对象.重新切换,Bundle对象会重新创建
2、设置Activity的android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法,不会调用onSaveInstanceState
singleTop
栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。应用场景:在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。
singleTask
栈内复用模式, activity只会在任务栈里面存在一个实例。应用场景:大多数App的主页。
singleInstance
单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。
FLAG_ACTIVITY_NEW_TASK
使用一个新的Task来启动一个Activity,但启动的每个Activity都讲在一个新的Task中。该Flag通常使用在从Service中启动Activity的场景,由于Service中并不存在Activity栈,所以使用该Flag来创建一个新的Activity栈,并创建新的Activity实例。
FLAG_ACTIVITY_SINGLE_TOP
使用singletop模式启动一个Activity,与指定android:launchMode=“singleTop”效果相同。
FLAG_ACTIVITY_CLEAR_TOP
使用SingleTask模式来启动一个Activity,与指定android:launchMode=“singleTask”效果相同。
FLAG_ACTIVITY_NO_HISTORY
Activity使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Activity栈中。
Service的生命周期,和两种启动方式。
bindService(intent,ServiceConnection ,BIND_AUTO_CREATE);
onCreate() ——> onBind() ——> Service running ——> onUnbind() ——> onDestroy() ——> Service stop
startService
onCreate()——> onStartCommand()——> Service running ——> onDestroy() ——> Service stop
Fragemnt的生命周期和使用场景。
http://www.jb51.net/article/80040.htm
onAttach->onCreate->onCreateView->onActivityCreate->onStart->onResume->
onPause->onStop->onDestroyView_Fragment->onDestroy_Fragment->onDetach_Fragment
- FragmentPagerAdapter保留所有fragment
- 在destroyItem的时候
FragmentStatePagerAdapter会调用remove
onPause->onStop->onDestroyView->onDestroy->onDetach
恢复onAttach->onCreate->onCreateView
而FragmentPagerAdapter会调用detach
onPause->onStop->onDestroyView
恢复onCreateView
BroadCastReciever的两种注册方法
https://www.jianshu.com/p/ca3d87a4cdf3
静态注册:在AndroidManifest文件中注册BroadcastReciever。不需要应用启动,就可以在广播传来时自动启动应用,接收、处理广播。
动态注册:在项目中使用代码注册BroadcastReciever。需要应用启动,运行注册代码,才可以接收、处理广播;应用退出时,要解除注册,否则会报异常。
对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册
ContentProvider的基本使用方法和作用。ContentValue的使用方法,他和HashMap的区别是什么?
- ContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。
Authority:授权信息,用以区别不同的ContentProvider;
Path:表名,用以区分ContentProvider中不同的数据表;
Id:Id号,用以区别表中的不同数据;
getContentResolver()来获得ContentResolver。
- ContentValues 和HashTable类似都是一种存储的机制 但是两者最大的区别就在于,contenvalues Key只能是String类型,values只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西。ContentValues 常用在数据库中的操作。
- HashMap不是线程安全的,HashTable是线程安全的一个Collection。HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
SharedPreference三种获得方法和区别,commit和apply的区别。
- public SharedPreferences getPreferences (int mode)
通过Activity对象获取,获取的是本Activity私有的Preference,保存在系统中的xml形式的文件的名称为这个Activity的名字,因此一个Activity只能有一个,属于这个Activity。
- public SharedPreferences getSharedPreferences (String name, int mode)
因为Activity继承了ContextWrapper,因此也是通过Activity对象获取,但是属于整个应用程序,可以有多个,以第一参数的name为文件名保存在系统中。
- public static SharedPreferences getDefaultSharedPreferences (Context context)
PreferenceManager的静态函数,保存PreferenceActivity中的设置,属于整个应用程序,但是只有一个,Android会根据包名和PreferenceActivity的布局文件来起一个名字保存。
commit和apply虽然都是原子性操作,但是原子的操作不同,commit是原子提交到数据库,所以从提交数据到存在Disk中都是同步过程,中间不可打断。
而apply方法的原子操作是原子提交的内存中,而非数据库,所以在提交到内存中时不可打断,之后再异步提交数据到数据库中,因此也不会有相应的返回值。
所有commit提交是同步过程,效率会比apply异步提交的速度慢,但是apply没有返回值,永远无法知道存储是否失败。
在不关心提交结果是否成功的情况下,优先考虑apply方法。
Android执行异步有哪些方法?线程间通讯的方式?
http://blog.csdn.net/u011803341/article/details/52774867
thread
AsyncTask(源码研究)
handler
rxjava
View的绘制流程?
measure: 判断是否需要重新计算View的大小,需要的话则计算;
layout: 判断是否需要重新计算View的位置,需要的话则计算;
draw: 判断是否需要重新绘制View,需要的话则重绘制。
http://www.cnblogs.com/jycboy/p/6066654.html
mLayoutInflater.inflate->ViewRootImpl.requestLayout->performTraversals()
private void performTraversals() {
// ... ...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ... ...
performLayout(lp, mWidth, mHeight);
// ... ...
performDraw();
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 调用onMeasure()
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
顶层父View向子View的递归调用view.onMeasure
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
// 调用layout()方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
}
从顶层父View向子View的递归调用view.layout方法的过程
private void performDraw() {
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
// ... ...
mView.draw(canvas);
}
View,SurfaceView,GLSurfaceView有什么区别?
这三个都是画布,展示UI
View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。
SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快。
GLSurfaceView:基于SurfaceView视图再次进行拓展的视图类,专用于3D游戏开发的视图;是SurfaceView的子类,openGL专用。
SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面
那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中 thread处理,一般就需要有一个eventqueue(时间队列)的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。
所以基于以上,根据不同的时间需求,一般分成两类。
1 被动更新画面的。这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate()。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。
一般2D游戏开发使用SurfaceView足够,因为它也是google专们扩展用于2D游戏开发的画布
使用普通的游戏画布(Android中2D专用游戏画布)中进行绘制图片,然后在GLSurfaceView(Android中3D游戏专用画布)中渲染图片的对比中发现GLSurfaceView的效率高于SurfaceView的30倍;GLSurfaceView的效率主要是因为机器硬件的GPU加速,现在flash技术也有了GPU加速技术;
下面总结一下:
一般2D游戏使用SurfaceView足够,所以不要认为什么都要使用GLSurfaceView(openGL),而且 GLSurfaceView的弊端在于适配能力差,因为很多机型中是没有GPU加速的。
WebView的基本使用方法。WebViewClient和WebChromeClient。
http://blog.csdn.net/lanxingfeifei/article/details/52045082
WebViewClient类与WebChromClient两个类在android开发的过程中,主要是在使用WebView这个组件的时候,可能会使用到。那么这两个类到底有什么不同之处呢?
WebViewClient 这个类主要帮助WebView处理各种通知、请求时间的,比如:
onLoadResource
onPageStart
onPageFinish
onReceiveError
onReceivedHttpAuthRequest
WebChromeClient主要辅助WebView处理JavaScript的对话框、网站图片、网站title、加载进度等比如
onCloseWindow(关闭WebView)
onCreateWindow()
onJsAlert(WebView上alert无效,需要定制WebChromeClient处理弹出)
onJsConfirm
onProgressChanged
onReceivedIcon
onReceivedTitle
Android和H5通信。(基本上就是JS和Android原生互调)
- Android调用JS
ws.setJavaScriptEnable(True);
webView.loadUrl(url);
webView.loadUrl("javascript:changeTitle('Android调用js')");
- JS调用Android
ws.setJavaScriptEnabled(true);
//给webView添加JS接口类, 该类封装了原生的操作. 参数2是JS中的实体类名字,需要和js代码中名字保持一致
webView.addJavascriptInterface(new JsInterface(),"js2android");
public class JsInterface{
@JavascriptInterface //这个注解一定要带上
public void selectPic(){
Intent intent = new Intent(WebViewActivity.this,GetPicActivity.class);
startActivityForResult(intent,100);
}
}
function appSelectPic(){
javascript:js2android.selectPic();
}
Android的屏幕适配方法有哪些?
- Linearlayout 权重,Relativelayout
- 设置不同分辨率的dimen,多套资源文件
- AndroidAutoLayout 鸿阳大神的百分比布局
- DataBindPercent 自定义@BindingAdapter,设置各种padding,margin等值
- Rudeness 从系统长度计算的入口,TypedValue里的applyDimension方法下手,简单好用,性能优秀,在activityonCreate时修改DisplayMetrics
//系统最终的长度换算
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
}
XML加载的几种方式,各自的原理。都有什么优缺点?
现在解析XML的主流的方法有DOM、SAX、JDOM和DOM4J
http://blog.csdn.net/bai435963/article/details/51246739
消息队列
- sendMessage() 和 sendMessageDelayed()
-
发送的消息会存储在MessageQueue中(单链表结构)
- Loop消息循环
错误写法
new Thread(){
@Override
public void run() {
Handler handler = new Handler();
}
}.start();
正确写法
new Thread(){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
- 主线程中不报错的原因是
public static void main(String[] args) {
// ... 省略部分代码
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
- Looper.prepareMainLooper() 创建了一个 Looper 对象,而且保证一个线程只有一个 Looper
- Looper.loop() 里面是一个死循环,不断的从 消息队列 MessageQueue 中取消息,然后通过 Handler 执行
Android中动画的分类,各自的优缺点。
有哪些容易造成内存泄漏的原因?
1、单例造成的内存泄漏
2、非静态内部类创建静态实例造成的内存泄漏
**解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。
3、Handler造成的内存泄漏
在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。
解决方法:将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
4、线程造成的内存泄漏
AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
解决方法:将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。
5、资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
6、WebView造成的泄露
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
1)将内部类改为静态内部类
2)静态内部类中使用弱引用来引用外部类的成员变量
- 属性动画 Property Animation :通过动态改变对象的属性达到动画效果,View的属性是真正改变了
- View Animation
- Frame Animation 帧动画:图片切换动画
- Tween Animation 补间动画 :可以对view实现一系列的转换,例如:移动、渐变、伸缩、旋转。只是在视图层实现了动画效果,并没有真正改变View的属性。
HashMap
HashMap是由数组和链表组合构成的数据结构,Java8中链表长度超过8时会把长度超过8的链表转化成红黑树;存取时都会根据键值计算出"类别"(hashCode),再根据"类别"定位到数组中的位置并执行操作。
hashCode是一个对象的标识,Java中对象的hashCode是一个int类型值。通过hashCode来指定数组的索引可以快速定位到要找的对象在数组中的位置,之后再遍历链表找到对应值,理想情况下时间复杂度为O(1),并且不同对象可以拥有相同的hashCode。
HashMap的时间复杂度取决于hash算法,优秀的hash算法可以让时间复杂度趋于常数O(1),糟糕的hash算法可以让时间复杂度趋于O(N)。
在数组大小不变的情况下,存放键值对越多,查找的时间效率会降低,扩容可以解决该问题,而负载因子决定了什么时候扩容,负载因子是已存键值对的数量和总的数组长度的比值。默认情况下负载因子为0.75,我们可在初始化HashMap的时候自己修改。
-
HashMap中是允许key为空的情况。再看下主流程:
HashMap是线程不安全的数据结构,多线程情况下HashMap会引起死循环引用
对比
1.发生hash冲突时,Java7会在链表头部插入,Java8会在链表尾部插入
2.扩容后转移数据,Java7转移前后链表顺序会倒置,Java8还是保持原来的顺序
3.关于性能对比可以参考美团技术博客,引入红黑树的Java8大程度得优化了HashMap的性能
- 通过源码分析,Java7在多线程操作hashmap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系;Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。那是不是意味着Java8就可以把HashMap用在多线程中呢?个人感觉即使不会出现死循环,但是通过源码看到put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,建议使用 ConcurrentHashMap。
LruCache
- LRU 是 Least Recently Used 最近最少使用算法。核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
- 1.(必填)你需要提供一个缓存容量作为构造参数。
- 2.(必填) 覆写 sizeOf 方法 ,自定义设计一条数据放进来的容量计算,如果不覆写就无法预知数据的容量,不能保证缓存容量限定在最大容量以内。
- 3.(选填) 覆写 entryRemoved 方法 ,你可以知道最少使用的缓存被清除时的数据( evicted, key, oldValue, newVaule )。
-
4.(记住)LruCache是线程安全的,在内部的 get、put、remove 包括 trimToSize 都是安全的(因为都上锁了)。
LruCache 就是 利用 LinkedHashMap 的一个特性( accessOrder=true 基于访问顺序 )再加上对 LinkedHashMap 的数据操作上锁实现的缓存策略。
LruCache 的数据缓存是内存中的。
1.首先设置了内部 LinkedHashMap 构造参数 accessOrder=true, 实现了数据排序按照访问顺序。
2.然后在每次 LruCache.get(K key) 方法里都会调用 LinkedHashMap.get(Object key)。
3.如上述设置了 accessOrder=true 后,每次 LinkedHashMap.get(Object key) 都会进行 LinkedHashMap.makeTail(LinkedEntry<K, V> e)。
4.LinkedHashMap 是双向循环链表,然后每次 LruCache.get -> LinkedHashMap.get 的数据就被放到最末尾了。
5.在 put 和 trimToSize 的方法执行下,如果发生数据量移除,会优先移除掉最前面的数据(因为最新访问的数据在尾部)。
LruCache重要的几点:
1.LruCache 是通过 LinkedHashMap 构造方法的第三个参数的 accessOrder=true 实现了 LinkedHashMap 的数据排序基于访问顺序 (最近访问的数据会在链表尾部),在容量溢出的时候,将链表头部的数据移除。从而,实现了 LRU 数据缓存机制。
2.LruCache 在内部的get、put、remove包括 trimToSize 都是安全的(因为都上锁了)。
3.LruCache 自身并没有释放内存,将 LinkedHashMap 的数据移除了,如果数据还在别的地方被引用了,还是有泄漏问题,还需要手动释放内存。
4.覆写 entryRemoved 方法能知道 LruCache 数据移除是是否发生了冲突,也可以去手动释放资源。
5.maxSize 和 sizeOf(K key, V value) 方法的覆写息息相关,必须相同单位。( 比如 maxSize 是7MB,自定义的 sizeOf 计算每个数据大小的时候必须能算出与MB之间有联系的单位 )
CircleImageView
一般实现自定义形状的图形有三种方式:PorterDuffXfermode 、BitmapShader、ClipPath。
- 继承ImageView,重写onDraw
- 通过getDrawable获取图片bitmap
- 创建BitmapShader,将bitmap作为底片,TileMode为 CLAMP :如果渲染器超出原始边界范围,会复制范围内边缘染色。
- 使用Matrix,让图片缩放,平移,保持居中,BitmapShader设置该Matrix
- 新建画笔,设置Shader为该BitmapShader
- canvas使用该画笔画圆
mBitmap = getBitmapFromDrawable(getDrawable());
if (mBitmap != null) {
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//设置缩放比
Matrix mShaderMatrix = new Matrix();
mShaderMatrix.setScale(1, 1);
//平移操作,(dx + 0.5f)的处理,是四舍五入
mShaderMatrix.postTranslate(0, 0);
bitmapShader.setLocalMatrix(mShaderMatrix);
Paint paint = new Paint();
paint.setShader(bitmapShader);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, paint);
}
蓝牙4.0
BLE分为三部分:
Service
Characteristic
Descriptor
这三部分都用UUID作为唯一标识符。UUID为这种格式:0000ffe1-0000-1000-8000-00805f9b34fb。比如有3个Service,那么就有三个不同的UUID与Service对应。这些UUID都写在硬件里,我们通过BLE提供的API可以读取到。
一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。
比如。有个蓝牙ble的血压计。他可能包括多个Servvice,每个Service有包括多个Characteristic
注意:蓝牙ble只能支持Android 4.3以上的系统 SDK>=18
2.以下是开发的步骤:
2.1首先获取BluetoothManager
2.2获取BluetoothAdapter
2.3创建BluetoothAdapter.LeScanCallback
2.4.开始搜索设备。
2.5.BluetoothDevice 描述了一个蓝牙设备 提供了getAddress()设备Mac地址,getName()设备的名称。
2.6开始连接设备
2.7连接到设备之后获取设备的服务(Service)和服务对应的Characteristic。
2.8获取到特征之后,找到服务中可以向下位机写指令的特征,向该特征写入指令。
2.9写入成功之后,开始读取设备返回来的数据。
2.10、断开连接
2.11、数据的转换方法
设备厂家都会给一份设备的通讯协议其中就有 哪一个UUID 代表什么。
private void changeMonitorMod(BluetoothGatt gatt, byte[] buffer) {
if (gatt != null && gatt != null) {
BluetoothGattService writeService = gatt.getService(MYUUID);
if (writeService == null) {
return;
}
}
BluetoothGattCharacteristic writeCharacteristic = writeService.getCharacteristic(MYWRITECHARACTERISTIC);
if (writeCharacteristic == null) {
return;
}
writeCharacteristic.setValue(buffer);
//上面的buffer数组中装的就是指令,多长? 每一位上面的数字代表什么意思在协议中查看!
gatt.writeCharacteristic(writeCharacteristic);//像设备写入指令。
}
65535原因及解决
- 因为在Dalvik指令集里,调用方法的
invoke-kind
指令中,method reference index只给了16bits,最多能调用65535个方法,所以在生成dex文件的过程中,当方法数超过65535就会报错。细看指令集,除了method,field和class的index也是16bits,所以也存在65535的问题。
android {
...
defaultConfig {
...
// 设置支持multidex
multiDexEnabled true
}
...
}
- 重写Application的attachBaseContext,使用MultiDex.install(this);
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
或继承
public class MyApplication extends MultiDexApplication {
// 不需要重写attachBaseContext()
//..........
}
进程保活
黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)
白色保活:启动前台Service,白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,
灰色保活:利用系统的漏洞启动前台Service,它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的
public class GrayService extends Service {
private final static int GRAY_SERVICE_ID = 1001;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT < 18) {
startForeground(GRAY_SERVICE_ID, new Notification());//API < 18 ,此方法能有效隐藏Notification上的图标
} else {
Intent innerIntent = new Intent(this, GrayInnerService.class);
startService(innerIntent);
startForeground(GRAY_SERVICE_ID, new Notification());
}
return super.onStartCommand(intent, flags, startId);
}
...
...
/**
* 给 API >= 18 的平台上用的灰色保活手段
*/
public static class GrayInnerService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(GRAY_SERVICE_ID, new Notification());
stopForeground(true);
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
}
}
dumpsys activity services PackageName
打印出指定包名的所有进程中的Service信息,看下有没有 isForeground=true 的关键信息。如果通知栏没有看到属于app的 Notification 且又看到 isForeground=true 则说明了,此app利用了这种灰色保活的手段。
DiskLruCache
- 通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/<application package>/cache 这个路径。选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。
- 而journal文件是DiskLruCache的一个日志文件,程序对每张图片的操作记录都存放在这个文件中,基本上看到journal这个文件就标志着该程序使用DiskLruCache技术了。
- mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
- 图片放入缓存
new Thread(new Runnable() {
@Override
public void run() {
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
- 读取图片缓存
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
mImage.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
- flush()这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中.比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。
- close()
MVC与MVP
MVC的缺点
在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。
View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)
Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)
Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。
两种模式的主要区别:
(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。
因此我们可以发现MVP的优点如下:
1、模型与视图完全分离,我们可以修改视图而不影响模型;
2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;
3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。
举个简单的例子,UI层通知逻辑层(Presenter)用户点击了一个Button,逻辑层(Presenter)自己决定应该用什么行为进行响应,该找哪个模型(Model)去做这件事,最后逻辑层(Presenter)将完成的结果更新到UI层。
通过对比同一实例的MVC与MVP的代码,可以证实MVP模式的一些优点:
在MVP中,Activity的代码不臃肿;
在MVP中,Model(IUserModel的实现类)的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;
在MVP中,IUserView这个接口可以实现方便地对Presenter的测试;
在MVP中,UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。
心得:先实现,再重构吧。直接考虑代码不臃肿得话,不知道什么时候才能写好了
能用第三方库就用第三方库。别管是否稳定,是否被持续维护,因为,任何第三方库的作者,都能碾压刚入门的菜鸟,你绝对写不出比别人更好的代码了。
最后附上知乎上面点赞次数很高的一段话:
如果“从零开始”,用什么设计架构的问题属于想得太多做得太少的问题。
从零开始意味着一个项目的主要技术难点是基本功能实现。当每一个功能都需要考虑如何做到的时候,我觉得一般人都没办法考虑如何做好。
因为,所有的优化都是站在最上层进行统筹规划。在这之前,你必须对下层的每一个模块都非常熟悉,进而提炼可复用的代码、规划逻辑流程。
所以,如果真的是从零开始,别想太多了
Android手势识别
GestureDetector.OnGestureListener
OnDown(MotionEvent e):用户按下屏幕就会触发;
onShowPress(MotionEvent e):如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行,具体这个瞬间是多久,我也不清楚呃……
onLongPress(MotionEvent e):长按触摸屏,超过一定时长,就会触发这个事件
触发顺序:
onDown->onShowPress->onLongPress
onSingleTapUp(MotionEvent e):从名子也可以看出,一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件
触发顺序:
点击一下非常快的(不滑动)Touchup:
onDown->onSingleTapUp->onSingleTapConfirmed
点击一下稍微慢点的(不滑动)Touchup:
onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) :滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
参数解释:
e1:第1个ACTION_DOWN MotionEvent
e2:最后一个ACTION_MOVE MotionEvent
velocityX:X轴上的移动速度,像素/秒
velocityY:Y轴上的移动速度,像素/秒
onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):在屏幕上拖动事件。无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法 在ACTION_MOVE动作发生时就会触发
滑屏:手指触动屏幕后,稍微滑动后立即松开
onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling
拖动
onDown------》onScroll----》onScroll------》onFiling
可见,无论是滑屏,还是拖动,影响的只是中间OnScroll触发的数量多少而已,最终都会触发onFling事件!
要使用GestureDetector,有三步要走:
1、创建OnGestureListener监听函数:
2、创建GestureDetector实例mGestureDetector:
GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
3、onTouch(View v, MotionEvent event)中拦截:
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
GestureDetector.OnDoubleTapListener
使用GestureDetector::setOnDoubleTapListener();函数设置监听:
onSingleTapConfirmed(MotionEvent e):单击事件。用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。触发顺序是:OnDown->OnsingleTapUp->OnsingleTapConfirmed
关于onSingleTapConfirmed和onSingleTapUp的一点区别: OnGestureListener有这样的一个方法onSingleTapUp,和onSingleTapConfirmed容易混淆。二者的区别是:onSingleTapUp,只要手抬起就会执行,而对于onSingleTapConfirmed来说,如果双击的话,则onSingleTapConfirmed不会执行。
onDoubleTap(MotionEvent e):双击事件
onDoubleTapEvent(MotionEvent e):双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件;
Android图像显示的底层原理
注解
- 4种元注解:
- @Target 定义作用域
- @Retention 运行级别,作用时机,常见有RetentionPolicy.CLASS 编译期 RetentionPolicy.CLASS 运行器,多用反射获取信息
- @Documented 表示将此注解包含在javadoc中
- @Inherited 子类继承父类注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value() ;
}
利用APT实现编译期注解
四个模块,前三个为核心模块:
- proxytool-api:框架api模块,供使用者调用,Android Library类型模块
- proxytool-annotations:自定义注解模块,Java类型模块
- proxytool-compiler:注解处理器模块,用于处理注解并生成文件,Java类型模块
- proxytool-sample:示例Demo模块,Android工程类型模块
其中这四个模块的依赖关系如下:
proxytool-api依赖proxytool-annotations模块。
proxytool-compiler依赖proxytool-annotations模块。
proxytool-sample模块依赖proxytool-api模块。
继承AbstractProcessor,最核心的方法就是process(),在这里你可以扫描和处理注解,并生成java文件,使用了javapoet来帮助我们生成Java源码
OKHttp源码解析
从Android4.4开始HttpURLConnection底层实现采用的是okHttp
进行通信的原理是:
上 面是OKHttp总体设计图,主要是通过Diapatcher不断从RequestQueue中取出请求(Call),根据是否已缓存调用Cache或 Network这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()直接返 回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback) 的方式来获取最后结果。
- 支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求
- 如果SPDY不可用,则通过连接池来减少请求延时
- 无缝的支持GZIP来减少数据流量
- 缓存响应数据来减少重复的网络请求
Retrofit分析 对网络请求客户端的完美封装
-
retrofit的最大特点就是解耦,要解耦就需要大量的设计模式,假如一点设计模式都不懂的人,可能很难看懂retrofit。
- Retrofit通俗的说就是一个HTTP请求的一个框架,通过一个Interface来定义api的实现,通过注释的方式进行一些请求的设置。运用大量的设计模式进行网络请求的解耦.
ANR
1.只有主线程才会产生ANR,主线程就是UI线程;
2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;
3.上述事件响应超时,不同的context规定的上限时间不同
a.主线程对输入事件5秒内没有处理完毕
b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。
那么如何避免ANR的发生呢或者说ANR的解决办法是什么呢?
1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。
2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。
Force close
- 发生未捕获异常时产生
- 如何避免弹出Force Close窗口 ,可以实现Thread.UncaughtExceptionHandler接口的uncaughtException方法
- 想要哪个线程可以处理未捕获异常,Thread.setDefaultUncaughtExceptionHandler( this); 这句代码都要在那个线程中执行一次
OOM
堆内存溢出
Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视 为内存泄漏,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程(如果是system_process等系统进程出问题的话,则会 引起系统重启),这是,我们的应用程序就会崩溃,我们就会看到OOM。
- 一次性要求很大内存
- 内存泄漏
app冷启动的流程如下:
-> Application 构造函数
-> Application.attachBaseContext()
-> Application.onCreate()
-> Activity 构造函数
-> Activity.setTheme()
-> Activity.onCreate()
-> Activity.onStart
-> Activity.onResume
-> Activity.onAttachedToWindow
-> Activity.onWindowFocusChanged