第一章:Activity生命周期和启动模式
- Activity关闭时会调用
onPause()
和onStop()
,如果下一个Activity是透明主题,则当前Activity不会调onStop()
。 - 启动Activity的请求会由Instrumentation来处理,然后它通过Binder向AMS发请求,AMS内部维护着一个ActivityStatck并负责Activity的状态同步,AMS通过ApplicationThread(Binder)回调通知ActivityThread再去同步Activity状态从而完成主线程生命周期回调。
- 前一个Activity的
onPause()
先调用后才后启动下一个Activity,因此不要在onPause()
里做太耗时的操作;后一个Activity启动后,即onResume()
后前一个Activity再继续调onSaveInstanceState()
和onStop()
。 -
onSaveInstanceState()
方法只有在系统异常终止时才会被调用,其在onStop()
之前,不确定与onPause()
的顺序(Api11之前,在onPause之前;在Api11之后调整到了onPause之后onStop之前);onRestoreInstanceState()
在onStart()
之后,方法中可以直接获取到Bundle值不用判空,而onCreate()
方法中的Bundle需要做判空。View层次的恢复过程为Activity->Window->DecorView
逐步向上委托,再由上到下通知子View来保存元素。 - 一般给Manifest清单中的configChanges配置
orientation|screenSize
可避免Activity重启。 - Activity启动模式:
• standard:标准模式,始终创建新实例,谁启动Activity,被启动的Activity就和谁在同一个任务栈;非Activity类型的Context不能启动Activity,除非指定标记位为FLAG_ACTIVITY_NEW_TASK
。
• singleTop:栈顶复用模式,Activity如果已在栈顶则不会被新建且会调用onNewIntent()
方法,否则创建新实例与standard效果相同。以上两个模式对于TaskAffinity属性无影响,即使设置了不同的affinity,也不会启动新的任务栈。
• singleTask:栈内复用模式,这个比较特殊,会先寻找是否已有所需任务栈存在,不存在则创建指定任务栈并放入,存在则将栈拉到前台,没有实例则创建Activity,已有则调用onNewIntent()
方法,自带newTask和clearTop标志行为清空顶部其他Activity(这个其实就是对比TaskAffinity是否相同,默认同一个应用下是包名,相同则不启动新的任务栈);TaskAffinity
参数可指定了任务栈名称,默认是包名,新指定不能和包名相同,一般与allowTaskReparenting
属性配对使用,allowTaskReparenting使得在应用A中启动应用B中的某个Activity,这个Activity可以与应用A处于同一任务栈,如果再去启动应用B,则这个Activity又被转移到应用B的Activity栈中。
• singleInstance:单实例模式,Activity独自存在于一个任务栈,后续不会再创建新的(除非这个栈被销毁了),用于多应用共享Activity。 - Flags:
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
标志位可以使被启动的Activity不出现在历史栈列表中,可以用于承接跳转Activity使用。 - IntentFilter匹配规则:
• IntentFilter中的过滤信息有action、category、data等参数。
• 一个Activity中可以有多组intent-filter,一个intent只要匹配其中一组即可。
• action:intent中的action参数能匹配到filter中任何一个即可,区分大小写;且清单中必须要有一个action参数,否则一定失败。
• category:intent中可以没有category参数(系统默认会加上一个default参数),要是有的话必须每个category都能和filter中的某一个匹配才行;清单filter中必须手动加一个DEFAUL
。
• data:由scheme://host:port/path/pathPrefix/pathPattern
组成;URI中scheme和host是必须的,scheme默认为content或file;intent中必须含有data数据且需要和filter中某一条匹配。
• data和type必须一起设置intent.setDataAndType()
才有效,否则会相互覆盖。 - PackageManager或Intent的
resolveActivity()
方法和PackageManager的queryIntentActivities()
方法可以返回匹配的intent,flag参数传MATCH_DEFAULT_ONLY
代表只找Activity,这样可以事先判空避免找不到而崩溃。 - Activity被回收时栈恢复情况:
• 场景:如果是手动强行杀死进程,任何生命周期都不会被回调(考虑下要不要保留状态什么的);如果是Activity栈在后台由于内存不足被回收,则栈顺序会被保存,优先恢复栈顶Activity(其余Activity还没有创建),点击back返回时,其余Activity再创建并显示;如果是前台Activity栈由于崩溃退出,恢复时只恢复到上一个Activity。
• 问题:栈恢复中,只能创建并恢复栈顶Activity,这时前面的Activity还没有创建,所以如果被恢复的栈顶Activity使用了前面Activity的静态引用或者依赖前面Activity的数据则会出现问题。
• 解决:可以在onSaveInstanceState()
和onRestoreInstanceState()
方法中做手脚来恢复数据;如果简单一些的话,可以自己在内存维护一个标志,在BaseActivity中判断,如果标志被置位则认为是进程重启了,这时候重启到LauncherActivity并杀死自己,需要将intent设置intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
。
第二章:IPC机制
- 线程是CPU调度的最小单元,线程是一种有限的系统资源;进程是一个执行单元,一般在PC或移动设备中指一个程序或应用;一个进程中可以包含多个线程,线程间数据共享,进程间数据独享。
- 通过给组件设置
android:process=":remote"
可开启多进程模式;":"代表在包名后面拼接进程名,也可直接指定进程全名。 - 进程名以":"开头的属于当前应用的私有进程,其他组件不能和它跑在一起,不以":"开头的为全局进程,通过shareUID方式可以将不同组件跑在同一进程,但是签名需要一致。
- 多进程模式中,不同进程组件会拥有独立的虚拟机、Application以及内存空间,数据不会共享,因此会出现单例失效、同步机制失效、SP读写不安全(多进程文件读写存在并发)、多个Application等情况。
- 实现Serializable接口(Java提供)并定义serialVersionUID(用于判断反序列化时对象是否一致用的)可实现序列化,使用简单但开销大,需要大量IO操作,适用于持久化到设备或网络;Android特有实现Parcelable接口也可实现序列化,使用稍麻烦,效率高,适用于内存序列化,传值等;注意静态成员不参数序列化,transient关键字标记的成员变量不参与序列化。
- Binder是Android中的一个类,实现了IBinder接口,它是Android中的一种跨进程通信方式,是链接各个Manager和ManagerService的桥梁。
- Binder可以理解为一种进程间通信规范,因为实现了IBinder接口,我们平时用的Stub继承自Binder,且实现了业务接口,提供两个接口的相互转换方法
asBinder()
和asInterface()
,供客户端使用;创建.aidl文件只是为了方便系统自动生成规范的.java代码而已。 - 客户端向远程发起请求后,客户端线程将挂起,直到远程回复,因此对客户端来说远程调用可能是耗时的,要在子线程中发起;对于服务端来说,Binder方法是运行在Binder线程池中的,因此同步调用即可。
- Binder提供了
linkToDeath()
和unlinkToDeath()
方法来给设置死亡代理,当远程进程异常时IBinder.DeathRecipient类的binderDied()
方法会被回调,客户端可以收到回调并重连远程Service;Binder的方法isBinderAlive()
也可以判断Binder是否死亡。 - Android中的几种IPC方式:
①Intent:可以使用Bundle传输数据,它也实现了Parcelable接口,可传输基本数据类型和可序列化后的Object。
②文件共享:Linux没有对文件并发读写做限制,因此要自己控制并发问题;SP文件也一样,系统会在内存缓存一份SP文件,在多进程模式下,也是线程不安全的。
③Messenger:本质是对AIDL的封装,底层也是通过AIDL实现;可通过Handler或IBinder构建Messenger,服务端通过Handler构建,返回msgner.getBinder()
,客户端通过IBinder构建,使用msgner.send(msg)
发数据,可带replyTo
字段,传入自己用Handler构建的Messenger,收服务端的回调;消息顺序执,一次处理一个请求,不用处理并发问题,且只能携带msg支持的参数,局限性还是比较大的。
④AIDL:
• IBinder是统的进程间通信规范,我们定义的.aidl文件实际上就是为了让系统生成.java文件,内含Stub类,实现了IBinder和业务接口,主要用的就是它作为Binder返回给客户端,服务端实现好定义的业务接口并将它在onBind()
方法中返回即可。
• AIDL支持的数据类型有6种:基本数据类型、String和CharSequence、List、Map、Parcelable和AIDL接口本身。自定义对象也要定义.aidl文件声明为parcelable类型,自定义的Parcelable对象就算在同一个包里也要现显式引用import;接口方法中除了基本数据类型,其它类型必须标上in、out或inout方向(服务端给客户端推送,服务端相当于客户端,要用in)。
• AIDL接口中只支持方法,不支持静态常量,这点区别于普通接口。
• 客户端和服务端包含一套相同的.aidl包结构,序列化和反序列化时会用到其中相关的类,一部分逻辑在客户端执行,一部分逻辑在服务端执行。
• Binder中的方法调用都是在Binder线程池中的,是异步的,所以客户端收到服务端回调,如果需要切到主线程的话要使用Handler。
• 对象本身是不能跨进程传输的,涉及序列化与反序列化,因此服务端Binder中传递到客户端的listener会反序列化出新的对象,注册与解注册要使用RemoteCallbackList,内含map通过binder作为key保存每个listener实例,客户端终止后可自动解除注册且内部实现了线程同步;RemoteCallbackList并不是List,遍历它要使用beginBroadcast()
和finishBroadcast()
方法,且必须配对使用。
• Binder意外死亡时需要重新连接服务,一般可以给Binder设置死亡代理DeathRecipient监听binderDied()
回调(线程池中调用的);也可以在onServiceDisconnected()
中重连(UI线程中调用的)。
• AIDL远程调用一般要做身份验证,可以在onBind()
中返回null,或者在onTracsact()
中返回false都能拒绝服务,建议自定义权限和包名双重验证。
⑤ContentProvider:
• 底层也是AIDL实现。六个方法:onCreate、query、update、insert、delete和getType,只有onCreate在主线程其他都在子线程执行;注册Provider一般定义authorities(uri)、permission(权限)、process(可运行在独立进程)。
• 除了query,update、insert和delete都会引起数据源改变,因此数据更新后可以context.getContentResolver().notifyChange(uri,null)
来通知更新,客户端可以注册registerContentObserver()
监听数据更新(如自动读取短信验证码功能)。
• provider的query、update、insert和delete四大方法都是存在多线程并发访问问题的,要自己做好线程同步(Sqlite单连接的话可读不可写,算做了线程同步了)。
⑥Socket:
• 分为流套接字和数据报套接字,对应TCP和UDP协议,TCP是面向连接的(需要三次握手),提供稳定的双向通信,而UDP是无连接的,提供不稳定的单向通信(效率高但不保证数据正确传输)。
• socket不仅可以进程间通信,只要设备之间的IP地址已知,网络通讯也可以。 - 一般一个Service对应一个Binder(业务接口),如果业务接口太多,创建太多Service就不合适了,所以一般使用Binder连接池。服务端就一个Service,创建好实现各类业务接口的实例后,统一一个queryBinder接口返回给客户端,客户端自己去取想要的业务接口;客户端想要哪个自己去取(取出来的是IBinder类型,要自己去强转一下再使用);建议Binder连接池全程单例,并加上超时重连机制。
- AIDL的通信流程:
• 由于.aidl代码包时客户端和服务端有相同的两份,因此有些代码是运行在客户端,有些代码是运行在服务端的,且两边可以相互充当客户端或服务端,可以理解为发出去请求的就是客户端,回应的就是服务端。
• Stub类是AIDL过程的关键,Stub类继承自Binder,实现了IBinder接口和业务接口,符合远程通信的规范,且Stub内部类Proxy在不同进程调用时充当Stub代理。
• Stub类在服务端的onBind()
方法里返回给客户端(已实现了业务接口),在客户端的onServiceConnected()
方法里获取到远程服务端的Stub(可转为业务接口)。
• 使用Stub.AsInterface(binder)
方法时,是调用的客户端的方法,Stub会判断如果是同一个进程则返回本地Stub,否则返回Stub.Proxy代理类,封装远程传过来的Stub(服务端的Stub),拿到统一的业务接口并使用。
• 调用业务接口进行远程方法调用时,方法中会先从本地生成输入输出的pracel对象,然后会调到mRemote.transact()
,系统底层会回调远程Stub里的onTransact()方法,远程业务接口实现类将输入输入date对象写回parcel。
• 因此注意Stub和Proxy的调用过程,transact()
前的都是在客户端执行,onTransact()
后的都是在服务端执行,两边的远程调用方法都是在Binder线程池中的,因此是耗时的,客户端会挂起等待远程回复,不能在客户端直接更新UI线程。
第三章:View的事件体系
- 几种常见的位置相关类:
• View的位置参数:主要由四个顶点top、left、right和bottom决定(相对父容器的位置);3.0以后,增加了x、y、translationX、translationY几个参数,getLeft()
是初始位置(永远不会变),getTranslationX()
是滑动偏移量,getX()
是最终位置,前两个相加(会变);getX()、getY()
是相对父容器的位置(相对位置),getRawX()、getRaxY()
是相对手机屏幕的位置(绝对位置)。
• MotionEvent:封装了一些列事件:ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等。
• TouchSlop:ViewConfiguration.get(context).getScaledTouchSlop()
获取,系统认为的最小滑动距离,是一个常量,与设备有关,一般为8dp,优化滑动敏感度使用。
• VelocityTracker:速度追踪。通过VelocityTracker.obtain()
获取、tacker.addMovement(event)
添加motionEvent、tarcker.computeCurrentVelocity(1000)
设置单位、tarcker.getXVelocity()
获取速度;注意速度值可能为负,因为速度值 = (终点位置 - 起点位置) / 时间段
,也跟设置的单位时间有关,结果就是XX像素/XX毫秒
。
• GestureDetector:手势检测。new GestureDetector(this)
获取、detector.onTouchEvent(event)
接管motionEvent、构造方法里传的是个监听接口,在那里面获取回调,一般只有监听双击等事件才用这个。
• Scoller:计算辅助类。我们只设置滑动距离和时间,它只帮我们封装了计算每一段时间和要滑动的距离scoller.getCurrX()
,具体滑动要自己做,在View的computeScroll()
方法中使用scroller.computeScrollOffset()
判断是否滑动结束,配合scrollTo()
和postInvalidate()
完成分段滑动。 - View的滑动:
• Scoller、ValueAnimator和Handler等都是只计算了滑动数值,是计算辅助类,而真正的滑动只能通过View自身的scrollTo()
或scrollBy()
实现。
•scrollTo()
是绝对滑动,scrollBy()
内部也是调用了scrollTo()
,是相对滑动;自定义View时srollTo()
之后要postInvalidate()
来重绘view,如果是在外部调用的话不用重绘。
注意:
scrollTo()
和scrollBy()
只能改变View内容的滑动,并不能改变View本身的位置;滑动数值等于View的边缘-View内容的边缘
,因此有正负值。理解记忆:想看更多内容,往下滑内容,为正;想看之前的内容,往上滑内容,为负。
- 改变布局参数layoutParams之后可以给View再次设置
view.setLayoutParams(params)
,也可以直接view.requestLayout()
(这个params必须是view.getLayoutParams()
获取的,修改的是同一个引用)。 -
ViewHelper.getTranslationX()
可以获取view的滑动偏移量来改变view的距离,其实ViewHelper是nineoldandroids里的View兼容库,view本身有许多get和set方法可以直接用。 - View事件分发机制:
• 三种事件类型:dispatchTouchEvent()
分发,事件能发给该View一定会调用,内部会调后两个方法、onInterceptTouchEvent()
拦截,拦截一次后不会再次判断了,直接调本层的下一个方法、onTouchEvent()
处理,如果返回fasle,后面的同一个事件序列不会再发给它。
• 调用顺序:先分发、再内部判断拦截、拦截则消费、否则继续向下分发。
• 几种回调优先级顺序:(Acitivity->PhoneWindow->DecorView->ContentView) -> dispatchTouchEvent() > onInterceptTouchEvent() > onTouchListener() > onTouchEvent() > onLongClickListener() > onClickListener()
。
事件分发的一些结论:
①一个事件序列只能由一个View来拦截且消耗,一锤子买卖,因为ViewGroup中会保存mMotionTarget为消耗事件的子View,后面的事件序列直接发给它 。
②一个ViewGroup只要决定拦截,onInterceptTouchEvent()
只会调一次,后面就不再次判断了。
③子View如果不消耗ACTION_DOWN事件,即onTouchEvent()
返回false后,以后的事件序列就不再传给它了,后面依次往上判断调用父View的onTouchEvent()
了,都返回false就给Activity处理了。
④DOWN、MOVE、UP每一个事件都会从上往下传,都可能被拦截,onTouchEvent()
返回true只是改变了传递深度而已,因此除了Down,每一层可以通过requestDisallowInterceptTouchEvent()
来改变父View的拦截限制;如果子View消耗了DOWN却没有消耗后续事件序列,这些事件仍会传过来,最终这些消失的事件会传到Activity那里。
⑤ViewGroup默认不拦截事件,即onInterceptTouchEvent()
返回false,View默认消耗事件,即onTouchEvent()
返回true,不过要看view是否是可点击的(clickable()值),button等默认是true,textview等默认是false。
⑥enable属性不影响onTouchEvent()
的默认返回值,只要view是可点击的(clickable为true),它就默认消耗事件。 - 事件分发流程:
①MotionEvent从Activity发出,如果Window上的所有View都不处理,即getWindow().superDispatchTouchEvent()
返回了false,最后调用Activity自己的onTouchEvent()
。
②ViewGroup的onInterceptTouchEvent()
方法不是每次都调用,只有Down事件或非Down事件且有子View处理事件(就是说子View处理了Down后的Move或Up事件)会调用,也就是即使子View处理了事件,ViewGroup还是会判断拦截Move、Up事件的,这也是我们处理滑动冲突的地方(拦截Move处理),回到上面,如果第一次onInterceptTouchEvent()
返回true的话,后面的Move和Up也就不是Down事件且mMotionTarget为空,则不会向子View分发事件序列了,直接调super.dispatchTouchEvent()
,里面会调ViewGroup自己的onTouchEvent()
。注意Move时子元素也是可以设置disallowInterceptTouchEvent
来禁止父元素就拦截Move和Up事件的。
③子View的onTouchListener会屏蔽掉onTouchEvent,如果返回了true,onTouchEvent()
就不会被调用了,onClick点击事件也就不会被调用了,这样是为了方便外界来处理特殊的触摸事件。
④子元素是否可以消耗onTouchEvent()
决定于它是否可点击,也就是clickable是否为true,与enable无关;默认longclickable都为false,clickable的话Button类的为true,TextView类的为false;但是任何View只要设置了onClickListener()
或onLongClickListener()
就会把它的clickable或longclickable显示置为true,它就可以处理onTouchEvent了。 - 处理滑动冲突其实Down事件控制不了,会一直传下去,要处理的就是有子View处理Down,且父View再拦截处理后续的Move和Up事件(一般处理move最多),这时可以根据业务需求来内部拦截或者外部拦截来处理滑动事件。
- 一般推荐外部拦截法,简单易用,在ViewGroup中控制Down的时候不拦截,Move的时候选择性拦截,Up的时候重置为不拦截,拦截了以后在自己的onTouchEvent中就可以处理事件了。
第四章:View的工作原理
- ViewRoot对应于ViewRootImpl,它是连接WindowManager和DecorView的纽带(Window-WindowManager-ViewRootImpl-View),View的三大流程均是通过ViewRoot来完成的,即测量、布局和绘制;
measure()
决定了View的测量宽高,layout()
决定了View的最终宽高和四个顶点的位置,draw()
将View绘制到屏幕上;View的绘制流程是从ViewRoot的performTraversals()
方法开始,经过measure、layout和draw三大流程将一个View绘制出来。 -
performTraversals()
方法会依次调用performMeasure、performLayou和performDrow,分别完成View的三大绘制流程;measure()
方法中根据自身的params和父View的Spec计算出自己的宽高的Spec,然后回调onMeasure()
方法,其中后计算出自己的真实size去setMeasuredDimension()
设置自己的宽高,如果是ViewGroup还会去计算子View的Spec传给子View - 三大流程:
①measure过程决定了View的宽高,measure以后就能通过getMeasuredWidth()
和getMeasureHeight()
获取View的测量宽高了,基本就定于View的最终宽高。
②layout过程决定了View四个顶点的位置坐标,layout以后就能通过getTop()、getLeft()、getRight()和getBottom()
获取View的位置了,就能得到getWidth()
和getHeitht()
的值了。
③draw过程决定了View最终的显示,只有draw以后View才能呈现在屏幕上(主线程绘制)。 - MeasureSpec:
• 是一个32位的int值,高两位表示SpecMode,低30位表示SpecSize,就是封装了一个测量值的包装类,需要用它来确定View的测量值。
• SpecMode有三种模式:EXACTLY、AT_MOST、UNSPECIFIED,分别对应具体值、wrap_content、第三个不常用,涉及minWidth和background因素。
• 子元素的最终大小由子View的layoutParam和父View的spec共同决定(还与margin和padding有关系),这个过程只是计算spec的基本操作,得到View的spec后回调View的onMeasure()
方法传入spec,让View自己再去计算一下宽高size去赋值。 - 子View的MeasureSpec的创建规则:
• 当子View是固定值的时候,不管父spec是什么,子spec都是EXCATLY,以子size为准。
• 当子View是martch_parent的时候,父spec如果是EXACTLY,子spec就是EXACTLY,父spec如果是AT_MOST,子sepc也是AT_MOST,都以父size为准。
• 当子View是wrap_content的时候,不管父spec是什么(除UNSPECIFIED外),子spec都是AT_MOST,以父size为准。
• 当父spec是UNSPECIFIED的时候,子View除固定值之外也是UNSPECIFIED,且子size为0。 - View的工作流程:
①measure:
•measure()
是final类型的,其中算出spec后会回调onMeasure()
,一般我们在onMeasure()
中获取建议的宽高值,自己再计算并设置setMeasureDimesion()
来设置View的最终的测量值。
• 直接继承自View一般区分USPECIFIED情况,默认size是minSize或backgroundMinSize的值,大多为0;另一种EXACTLY、和AT_MOST,都是使用子spec中的size值(默认是parentSize),因此wrap时要自己算宽高否则与match一样。
• ViewGroup是抽象类,有个measureChildren()
方法,会调用child.measure()
,根据自己的sepc和child的layoutParams来得出子spec,最终参数先去计算子View宽高,然后各个ViewGroup子类定义onMeasure()
,用子View的size总之决定自己的size。
•onMeasure()
可能会被执行多次,只有最后一次基本会与最终宽高值相等,因此一个好的习惯是在onLayout()
中去获取View最终的宽高值。
②layout:
• ViewGroup在layout()
确定位置后,会在onLayout()
中遍历所有子元素并调用其
layout()
方法来布局子元素,里面又会回调子元素的onLayout()
方法;layout()
就是父View来调用确定你自己的位置,然后会回调你的onLayout()
方法告诉你你的位置参数,然后你在调子View的layout()
去设置子View的位置。
③draw:
• draw绘制的过程分四步:绘制背景background.draw(canvas) -> 绘制自己(onDraw()) -> 绘制children(dispatchDraw()) -> 绘制装饰(donDrawScrollBars())
;
View有个默认的willNotDraw标志,ViewGroup默认开启,View默认关闭,为true的话如果不需要绘制任何内容系统会对其进行一些优化,如果明确知道一个ViewGroup必须要通过onDraw来绘制内容时,要显示关闭这个WILL_NOT_DRAW标志。
注意:
• 由于View的measure过程和Activty的生命周期不是同步的,因此在onCreate()、onStart()、onResume()基本均无法获取到View的宽高。
• 四种方式获取View的宽高:1.onWindowFocusChanged()(会多次调用) 2.view.post(runnable)(推荐) 3.ViewTreeObserver.addOnGlobalLayoutListener()(也行,多次调用) 4.view.measure()(基本只有子View具体宽高值才行)。
• getMeasuredWidth()和getWidth()基本是相等的,只是前者在measure后确定(测量值),后者在layout后确定(位置相减),两个顺序不一致,且前者可能会被调用多次,因此,最好使用后者来得到View的宽高值。
- 自定义View:
①分类:继承自View重写onDraw()
、继承自ViewGroup重写onLayout()
、继承自现有View、继承自现有ViewGroup。
②须知:继承自View需要处理wrap_content、需要处理padding、不用在View中使用handler,直接post即可、onDetachedFromWindow()
时记得停止线程或动画,否则可能内存泄露、处理好滑动冲突等。
③流程:
• 自定义View在onMeasure()
中记得getPadding()
处理下padding、根据业务处理下wrap_content的情况;onMeasure()
中父View计算好了一套默认的宽高specMode和specSize,子View根据业务需要重新计算宽高值并setMeasuredDimension()
赋值宽高。
• 自定义ViewGroup时在onMeasure()
中也是父View计算好了一套specMode和specSize,先调用measureChildren()
计算一下子View大小,完了再根据子View的getMeasuredWidth()
算出来最终的总值,再给自己setMeasuredDimension()
赋值宽高。
• 在onLayout()
中根据left、top、right和bottom(父View给自己的定位),以及子View的个数等信息,遍历子View调用childView.layout(l,t,r,b)
来布局子View。 - 对于View这一块要掌握View的三大流程(measure、layout、draw),一些常见方法回调(构造、onAttach、onVisibilityChanged、onDetach)等,以及嵌套中滑动冲突的处理。
第五章:理解RemoteViews
- RemoteViews表示一个View结构,它可以在其他进程中显示,提供了一组基础的操作用于跨进程更新界面,多用于通知栏或桌面小部件,一般在系统的SystemService进程。
-
new RemoteView(packageName,layoutResId)
可以创建remoteView,remoteView.setTextViewText(R.id.tv,"xxx")
用于设置View值,再给notification.contentView = remoteView
即可;用于桌面小部件时,本质其实就是一个广播(BroadcastReceiver)。 - PendingIntent概述:
• 延迟intent,支持三种意图:getActivity()、getService()、getBroadcast()。
• 两个pendingIntent相同的条件是:内部的intent相同且requestCode也相同,intent相同指componentName和intent-filter相同,extras不参与匹配过程。
• requestCode参数基本填0,flag有四种类型:FLAG_ONE_SHOT(少用)、FLAG_NO_CREATE(基本不用)、FLAG_CANCEL_CURRENT(类似第一个 不常用)、FLAG_UPDATE_CURRENT(常用)。FLAG_ONE_SHOT和FLAG_CANCEL_CURRENT都是只能打开一个PI(前者打任一个其余打不开,后者只能打开最新的,前面的被cancel掉),FLAG_NO_CREATE直接返回null,基本不用,FLAG_UPDATE_CURRENT每个都能打开,只是结果都一样。
• notification中id相同的话会相互覆盖前一个,不会出现pendingIntent冲突现象,如果id不同,可以显示多条通知,这时要注意pendingIntent如果相同的话,注意flag的设置问题(id影响显示几条,flag影响PI相等时的点击反应)。 - RemoteViews对象可以序列化,通过Intent进行传输,传输到其他应用后资源文件id最好通过协商好的格式
getResources().getIdentifier(id,type,packageName)
来获取。
第六章:Android的Drawable
- Drawable是一种可以在canvas上绘制的抽象概念,比自定义View成本低,可以做一些特殊背景和效果,作为View的背景时会被拉伸,宽高没有什么实际用处。
- 9中常用Drawable分类:
①单层:
• BitmapDrawable:src、antialias、dither、filter、gravity、mipMap、tileMode等。
• NinePatchDrawable:src、dither等。
• ShapeDrawable:shape、corners、gradient、padding、size、solid、stroke等。有rectangle、oval、line和ring四种类型,画虚线做背景时imageView需要开启软件加速才有效。
②组合:
• LayerDrawable:layer-list。使用item、shape层级显示。
• StateListDrawable:selector。使用item、shape选择显示,默认的状态放到最后,可以匹配任何状态,类似switch语句。
• LevelListDrawable:level-list。使用item、shape多层切换显示,maxLevel取值0~10000。
③不常用:
• TransitionDrawable:transition。过渡效果。
• InsetDrawable:inset。给内置drawable添加内边距,layerdrawable或padding也能实现。
• ScaleDrawable:scale。级别越大,内部drawable越大(正比);比例越大,内部drawable越小(反比)。
• ClipDrawable:clip。级别0~10000,级别越大,裁剪的越大,剩的越少。
第七章:Android动画深入分析
- Android动画类型6种:帧动画、补间动画、布局动画、属性动画、矢量动画和切换动画。
- 每种动画的特点:
• 帧动画:animation-list标签;oneshot属性;item添加动画帧;作为View的背景,获取后转为AnimationDrawable,自己开启和结束;注意不要用太多大图以免OOM。
• 补间动画:平移、缩放、旋转、透明度、set五种类型;interpolator、shareInterpolator、duration、repeatCount、repeatMode、fillAfter等属性,repeatCount默认是0,-1表示无线循环,repeatMode取值restart和reverse;AnimationUtils.loadAnimation()
获取动画,通过view来开启和关闭,注意detachFromWindow是要关闭动画否则可能内存泄露。
• 布局动画:layoutAnimation标签;delay(0~1)、animationOrder(normal/reverse/radom)、animation等属性;可以在布局里给View设置layoutAnimation,也可以在代码里创建LayoutAnimationController来给View设置setLayoutAnimation()
,默认布局中设置layoutAnimationChanged=true
可实现默认动画。
• 属性动画: 含ValueAnimator、ObjectAnimator和AnimatorSet;独立的动画类,自己创建自己开启,可将View设为target(系统封好的或者自己定义的),类似于Scoller的特点;API11之后引入的特性,可以使用nineoldandroid库做版本兼容(3.0以下使用补间动画实现)。
• 矢量动画:SVG动画,可适配不同分辨率,实现完美平滑的过渡效果,5.0以上支持,实现方式比较复杂,建议阅读相关博客。
• 切换动画:overridePendingTransition(enter,exim)
,放在startActivity()
和finish()
之后才能生效,第一个参数是下个Activity进入的动画,第二个参数是本Activity退出的动画;Fragment切换动画用到了FragmentTransaction的setCustomAnimations()
方法。 - 属性动画详解:
• 类似Scroller,其实就是一种计算辅助类,差值器和估值算法,原始动画为ValueAnimator,封装了计算过程,ObjectAnimator多了target属性包装一个View,封装了一些特定属性的get()、set()效果,也可自己实现动画效果。
• 一般在代码中动态创建,xml中创建的话,设置valueType、propertyName等属性,AnimatorInflater.loadAnimator()
获取并设置setTarget()
作用对象。
• 差值器(Interpolator)和估值器(Evaluator)一般配合使用,都是计算辅助接口,一个计算差值比例,一个计算最终的估值结果,可作用于任何地方作为计算器类,可自定义计算变化算法和估值类型和结果值。
• ValueAnimator的addUpdateListener()
可设置更新监听,animator.getAnimatedFraction()
和animator.getAnimatedValue()
可以获取到差值和估值结果,是根据前面自己设置的计算类得到的。
• ObjectAnimator作用时View对应属性要有get()、set()方法,否则会crash,还要定义对应效果,否则没效果,可以自己包装View或者直接用ValueAnimator自己来根据计算值设置View。 - 使用动画注意事项:帧动画注意OOM的情况;属性动画如果是循环的,退出时最好cancel()一下,且注意版本兼容性;补间动画不用时clearAnimation()一下,否则有些view方法会无效果;使用动画的过程中如果开启硬件加速可以提高流畅度。
第八章:理解Window和WindowManager
- Window是一个抽象类,它的具体实现是PhoneWindow,它是一个存在但看不见的东西,外界访问Window要通过WindowManager,Window是View的直接管理者,View要通过WindowManager附属到Window上才能实现通信和显示,Window的具体实现在WindowManagerService中,WindowManger与WindowManagerService的交互是IPC过程。
- ViewManager是个接口,有addView()、updateView()和removeView()三个方法,WindowManager继承自ViewManager,含很多标志位,WindowManagerImpl才是真正的实现类,内部所有操作委托给WindowManagerGlobal实现。
- 有View和LayoutParams,View就能附加到Window上显示出来,flag类型如:FLAG_NOT_FOCUSABLE(不处理焦点完全给下级)、FLAG_NOT_TOUCH_MODAL(处理自己范围内的事件 需要开启)、FLAG_SHOW_WHEN_LOCKED(锁屏也显示)。
- Window分为三种:应用Window(对应Activity)、子Window(Dialog/PopupWindow等 必须有父Window)和系统Window(Toast等 需要权限 不需要依附token)。
- Window有层级,应用Window范围1-99,子Window范围1000-1999,系统Window范围2000-2999,层级越高显示越在上面。
- 每个Window对应着一个View和ViewRootImpl,Window和View通过ViewRootImpl建立联系,实际使用中无法直接访问Window,可通过WindowManger访问;WindowManagerGlobal是真正逻辑实现,内部含mViews、mRoots、mParams和mDyingViews几个列表,存储管理的Views,通过ViewRootImpl更新界面完成Window的添加过程,ViewRootImpl中会
scheduleTraversals()
完成View绘制,并通过WindowManagerGlobal获取WindowSession进行addToDisplay()
,内部是与WindowManagerService进行IPC交互了;removeView也是调到ViewRootImpl中,进行View的dispatchDetachedFromWindow()
以及通过WindowSession来更新IPC等;updateView过程也类似,涉及View的重绘。 - Window的内部机制(涉及的类):
ViewManager/WindowManager -> WindowMangerImpl -> WindowManagerGlobal -> View和ViewImpl -> WindowSession -> WindowManagerService
,WindowManagerGlobal里大多是管理ViewRootImpl的逻辑,添加View到Window中的逻辑是在ViewImpl中执行的,ViewImpl中通过WindowSession与WMS进行通信,最后使用WMS来进行将View添加到Window中,属于远程IPC调用,真正的操作逻辑在WMS里。 - Activity中Window的创建过程:
IPolicy -> PolicyManager -> Wiondow(PhoneWindow) -> 内部创建DecorView(mContentParent) -> 等到Activity的onResume() -> makeVisible() -> ViewManager的addView()将mDecor添加到Window -> mDecor.setVisibility()
,Activity最终显示。 - 普通的Dialog必须使用Activity的Context才能显示出来,因为Activity的context才有windowToken;Toast可定时取消,用到了Handler,因此Toast无法在没有Looper的线程中使用。
- 有了WindowManager,我们就可以随意创建View和设置LayoutParams来让这个Window显示出来,可以设置级别和flag,用于创建应用悬浮窗等。
第九章:四大组件的工作过程
- Android四大组件中除了BroadcastReceiver外,其余三个组件都要在Menifest中注册,BroadcastReceiver可以在Menifest中静态注册也可以在代码中动态注册;Activity、Service和BroadcastReceiver需要借助Intent来启动。
- Activity主要作用是展示一个界面并与用户交互,扮演一个前台界面的角色;Service是一种计算型组件用于在后台执行一系列计算任务;BroadcastReceiver是一种消息型组件,用于在不同组件乃至不同应用中传递消息;ContentProvider内部的insert、delete、update和query方法是在Binder线程池中调用的,是异步操作,所以需要处理好线程同步问题。
- BroadcastReceiver可以静态注册和动态注册,静态注册指在Menifest中注册,此种形式应用不需启动便可收到广播(5.0以后不可),适用于长期接收事件,动态注册指在代码中注册,一般用的多,因为页面关闭后就取消注册了,广播用于实现低耦合的观察者模式。
- Service虽然指后台运行,但是还是在主线程中工作的,不能直接做耗时操作,可在IntentService中可以做耗时操作,且任务完成可以自己stop自己,后台计算不需要交互使用startService开启,需要业务接口交互使用bindService开启。
- ActivityManagerService(AMS)继承自ActivityManagerNative,ActivityManagerNative继承自Binder并实现了IActivityManager接口,因此AMS是一个Binder,是IActivityManager的具体实现;ApplicationThread继承自ApplicationThreadNative,ApplicationThreadNative继承自Binder并实现了IApplicationThread的接口,ApplicationThread是一个实现了很多schedule方法的业务接口,作为Binder使用,通过Handler回调到ActivityThread中处理;Instrumentation是一个辅助类,负责分担Activity中通过ApplicationThread与AMS交互的一些业务逻辑。
- Activity的启动过程涉及到的类:
Activity -> Instrumentation -> ApplicationThread -> AMS -> ActivityStarter -> ActivityStackSupervisor -> ActivityStack -> ActivityStackSupervisor -> ApplicationThread -> Handler -> ActivityThread(H)
;Service的启动过程涉及到的类:Activity -> ContextImpl -> AMS -> ActiveService(AMS的辅助类) -> ApplicationThread -> Handler -> ActivityThread(H)
。 - Activity启动流程:
• Activity内部一些逻辑执行交给Instrumentation,Instrumentation内部通过ApplicationThread这个Binder与AMS进行交互,这个交互是IPC过程,ApplicationThread是ActivityThread的一个内部类,类似Stub类,作为ActivityThread与AMS的桥接Binder对象。
• AMS处理了远程请求的业务之后,进行一些远程同步、控制、存储逻辑后,调用ApplicationThread回来,由于Binder的调用是在线程池中进行(异步),因此在ApplicationThread内通过一个H(handler)来发送消息给ActivityThread处理结果。
• ActivityThread内处理结果时结合contextImpl、mInstrumentation等类进行一些列逻辑处理和生命周期等回调,因此onResume()、onConnection()、onReceive()
等方法都是在主线程执行的。
Activity启动描述:
桌面Launcher也是一个APP,当点击某个应用icon时,Launcher通知AMS启动目标App,AMS记录必要信息后pauseLanucher,当目标应用程序启动时,入口方法为ActivityThread的main()
方法(ActivityThread就是主线程),main()
是一个静态方法,内部会创建ActivityThread实例并创建主线程消息队列、绑定ApplicationThread到AMS、执行主线程循环等,在ActivityThread的attach()
方法中远程调用AMS的attachApplication()
方法将ApplicationThread最为回调Binder绑定到AMS,AMS中处理一些同步工作后,会通过ApplicationThread回调各个shcedule方法到ActivityThread,最后创建Application、Context、初始化View等完成界面绘制并显示。
- 广播分为普通广播、有序广播和粘性广播,从Android5.0(其实3.1就开始了)开始系统为所有广播默认添加了FLAG_EXCLUDE_STOPPED_PACKAGES标志,标记广播是否要对已停止的应用起作用。
- ContentProvider是内容共享型组件,通过Binder向其他组件乃至其他应用提供数据;当ContentProvider所在的进程启动时,ContentProvider会被创建并发布到AMS中,ActivityThread中会先加载ContentProvider,因此ContentProvider的
onCreate()
要先于Application的onCreate()
执行;ContentProvider是否是单例由
android:multiprocess
属性值来控制,一般都是单实例存在。 - Service生命周期:
• startService():onCreate() -> onStartCommand()
• stopService():onDestroy()
• bindService():onCreate() -> onBind()
• unbindService():onUnbind() -> onDestroy()
先startService()
再bindService()
或先bindService()
再startService()
,只关闭其中一步不会onDestroy()
,需要stopService()
和unbindService()
都调用才会onDestory()
,顺序不分先后,但是如果没有bind的话直接调用unbindService()
会抛异常;bindService()
生命周期随调用者,Activity退出后就unbind了,startService()
不随调用了,即使程序双击退出Service仍存在,必须手动stopService()
或去应用管理里关掉Service;如果强杀进程,Service属于意外死亡,默认情况下是粘性Service会自己重新onCreate() -> onStartCommand()
,但是没啥用,进程杀死后内存数据都没了,仅仅是重启了Service;在onStartCommand()
中return标志位"START_NOT_STICKY"杀进程后不会重启Service。