《Android开发艺术探索》笔记一

第一章:Activity生命周期和启动模式
  1. Activity关闭时会调用onPause()onStop(),如果下一个Activity是透明主题,则当前Activity不会调onStop()
  2. 启动Activity的请求会由Instrumentation来处理,然后它通过Binder向AMS发请求,AMS内部维护着一个ActivityStatck并负责Activity的状态同步,AMS通过ApplicationThread(Binder)回调通知ActivityThread再去同步Activity状态从而完成主线程生命周期回调。
  3. 前一个Activity的onPause()先调用后才后启动下一个Activity,因此不要在onPause()里做太耗时的操作;后一个Activity启动后,即onResume()后前一个Activity再继续调onSaveInstanceState()onStop()
  4. onSaveInstanceState()方法只有在系统异常终止时才会被调用,其在onStop()之前,不确定与onPause()的顺序(Api11之前,在onPause之前;在Api11之后调整到了onPause之后onStop之前);onRestoreInstanceState()onStart()之后,方法中可以直接获取到Bundle值不用判空,而onCreate()方法中的Bundle需要做判空。View层次的恢复过程为Activity->Window->DecorView逐步向上委托,再由上到下通知子View来保存元素。
  5. 一般给Manifest清单中的configChanges配置orientation|screenSize可避免Activity重启。
  6. 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。
  7. Flags:FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS标志位可以使被启动的Activity不出现在历史栈列表中,可以用于承接跳转Activity使用。
  8. 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()才有效,否则会相互覆盖。
  9. PackageManager或Intent的resolveActivity()方法和PackageManager的queryIntentActivities()方法可以返回匹配的intent,flag参数传MATCH_DEFAULT_ONLY代表只找Activity,这样可以事先判空避免找不到而崩溃。
  10. 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机制
  1. 线程是CPU调度的最小单元,线程是一种有限的系统资源;进程是一个执行单元,一般在PC或移动设备中指一个程序或应用;一个进程中可以包含多个线程,线程间数据共享,进程间数据独享。
  2. 通过给组件设置android:process=":remote"可开启多进程模式;":"代表在包名后面拼接进程名,也可直接指定进程全名。
  3. 进程名以":"开头的属于当前应用的私有进程,其他组件不能和它跑在一起,不以":"开头的为全局进程,通过shareUID方式可以将不同组件跑在同一进程,但是签名需要一致。
  4. 多进程模式中,不同进程组件会拥有独立的虚拟机、Application以及内存空间,数据不会共享,因此会出现单例失效、同步机制失效、SP读写不安全(多进程文件读写存在并发)、多个Application等情况。
  5. 实现Serializable接口(Java提供)并定义serialVersionUID(用于判断反序列化时对象是否一致用的)可实现序列化,使用简单但开销大,需要大量IO操作,适用于持久化到设备或网络;Android特有实现Parcelable接口也可实现序列化,使用稍麻烦,效率高,适用于内存序列化,传值等;注意静态成员不参数序列化,transient关键字标记的成员变量不参与序列化。
  6. Binder是Android中的一个类,实现了IBinder接口,它是Android中的一种跨进程通信方式,是链接各个Manager和ManagerService的桥梁。
  7. Binder可以理解为一种进程间通信规范,因为实现了IBinder接口,我们平时用的Stub继承自Binder,且实现了业务接口,提供两个接口的相互转换方法asBinder()asInterface(),供客户端使用;创建.aidl文件只是为了方便系统自动生成规范的.java代码而已。
  8. 客户端向远程发起请求后,客户端线程将挂起,直到远程回复,因此对客户端来说远程调用可能是耗时的,要在子线程中发起;对于服务端来说,Binder方法是运行在Binder线程池中的,因此同步调用即可。
  9. Binder提供了linkToDeath()unlinkToDeath()方法来给设置死亡代理,当远程进程异常时IBinder.DeathRecipient类的binderDied()方法会被回调,客户端可以收到回调并重连远程Service;Binder的方法isBinderAlive()也可以判断Binder是否死亡。
  10. 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地址已知,网络通讯也可以。
  11. 一般一个Service对应一个Binder(业务接口),如果业务接口太多,创建太多Service就不合适了,所以一般使用Binder连接池。服务端就一个Service,创建好实现各类业务接口的实例后,统一一个queryBinder接口返回给客户端,客户端自己去取想要的业务接口;客户端想要哪个自己去取(取出来的是IBinder类型,要自己去强转一下再使用);建议Binder连接池全程单例,并加上超时重连机制。
  12. 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的事件体系
  1. 几种常见的位置相关类:
    • 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()完成分段滑动。
  2. View的滑动:
    • Scoller、ValueAnimator和Handler等都是只计算了滑动数值,是计算辅助类,而真正的滑动只能通过View自身的scrollTo()scrollBy()实现。
    scrollTo()是绝对滑动,scrollBy()内部也是调用了scrollTo(),是相对滑动;自定义View时srollTo()之后要postInvalidate()来重绘view,如果是在外部调用的话不用重绘。

注意:scrollTo()scrollBy()只能改变View内容的滑动,并不能改变View本身的位置;滑动数值等于View的边缘-View内容的边缘,因此有正负值。理解记忆:想看更多内容,往下滑内容,为正;想看之前的内容,往上滑内容,为负。

  1. 改变布局参数layoutParams之后可以给View再次设置view.setLayoutParams(params),也可以直接view.requestLayout()(这个params必须是view.getLayoutParams()获取的,修改的是同一个引用)。
  2. ViewHelper.getTranslationX()可以获取view的滑动偏移量来改变view的距离,其实ViewHelper是nineoldandroids里的View兼容库,view本身有许多get和set方法可以直接用。
  3. 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),它就默认消耗事件。
  4. 事件分发流程:
    ①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了。
  5. 处理滑动冲突其实Down事件控制不了,会一直传下去,要处理的就是有子View处理Down,且父View再拦截处理后续的Move和Up事件(一般处理move最多),这时可以根据业务需求来内部拦截或者外部拦截来处理滑动事件。
  6. 一般推荐外部拦截法,简单易用,在ViewGroup中控制Down的时候不拦截,Move的时候选择性拦截,Up的时候重置为不拦截,拦截了以后在自己的onTouchEvent中就可以处理事件了。
第四章:View的工作原理
  1. ViewRoot对应于ViewRootImpl,它是连接WindowManager和DecorView的纽带(Window-WindowManager-ViewRootImpl-View),View的三大流程均是通过ViewRoot来完成的,即测量、布局和绘制;measure()决定了View的测量宽高,layout()决定了View的最终宽高和四个顶点的位置,draw()将View绘制到屏幕上;View的绘制流程是从ViewRoot的performTraversals()方法开始,经过measure、layout和draw三大流程将一个View绘制出来。
  2. performTraversals()方法会依次调用performMeasure、performLayou和performDrow,分别完成View的三大绘制流程;measure()方法中根据自身的params和父View的Spec计算出自己的宽高的Spec,然后回调onMeasure()方法,其中后计算出自己的真实size去setMeasuredDimension()设置自己的宽高,如果是ViewGroup还会去计算子View的Spec传给子View
  3. 三大流程:
    ①measure过程决定了View的宽高,measure以后就能通过getMeasuredWidth()getMeasureHeight()获取View的测量宽高了,基本就定于View的最终宽高。
    ②layout过程决定了View四个顶点的位置坐标,layout以后就能通过getTop()、getLeft()、getRight()和getBottom()获取View的位置了,就能得到getWidth()getHeitht()的值了。
    ③draw过程决定了View最终的显示,只有draw以后View才能呈现在屏幕上(主线程绘制)。
  4. 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去赋值。
  5. 子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。
  6. 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的宽高值。

  1. 自定义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。
  2. 对于View这一块要掌握View的三大流程(measure、layout、draw),一些常见方法回调(构造、onAttach、onVisibilityChanged、onDetach)等,以及嵌套中滑动冲突的处理。
第五章:理解RemoteViews
  1. RemoteViews表示一个View结构,它可以在其他进程中显示,提供了一组基础的操作用于跨进程更新界面,多用于通知栏或桌面小部件,一般在系统的SystemService进程。
  2. new RemoteView(packageName,layoutResId)可以创建remoteView,remoteView.setTextViewText(R.id.tv,"xxx")用于设置View值,再给notification.contentView = remoteView即可;用于桌面小部件时,本质其实就是一个广播(BroadcastReceiver)。
  3. 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相等时的点击反应)。
  4. RemoteViews对象可以序列化,通过Intent进行传输,传输到其他应用后资源文件id最好通过协商好的格式getResources().getIdentifier(id,type,packageName)来获取。
第六章:Android的Drawable
  1. Drawable是一种可以在canvas上绘制的抽象概念,比自定义View成本低,可以做一些特殊背景和效果,作为View的背景时会被拉伸,宽高没有什么实际用处。
  2. 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动画深入分析
  1. Android动画类型6种:帧动画、补间动画、布局动画、属性动画、矢量动画和切换动画。
  2. 每种动画的特点:
    • 帧动画: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()方法。
  3. 属性动画详解:
    • 类似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。
  4. 使用动画注意事项:帧动画注意OOM的情况;属性动画如果是循环的,退出时最好cancel()一下,且注意版本兼容性;补间动画不用时clearAnimation()一下,否则有些view方法会无效果;使用动画的过程中如果开启硬件加速可以提高流畅度。
第八章:理解Window和WindowManager
  1. Window是一个抽象类,它的具体实现是PhoneWindow,它是一个存在但看不见的东西,外界访问Window要通过WindowManager,Window是View的直接管理者,View要通过WindowManager附属到Window上才能实现通信和显示,Window的具体实现在WindowManagerService中,WindowManger与WindowManagerService的交互是IPC过程。
  2. ViewManager是个接口,有addView()、updateView()和removeView()三个方法,WindowManager继承自ViewManager,含很多标志位,WindowManagerImpl才是真正的实现类,内部所有操作委托给WindowManagerGlobal实现。
  3. 有View和LayoutParams,View就能附加到Window上显示出来,flag类型如:FLAG_NOT_FOCUSABLE(不处理焦点完全给下级)、FLAG_NOT_TOUCH_MODAL(处理自己范围内的事件 需要开启)、FLAG_SHOW_WHEN_LOCKED(锁屏也显示)。
  4. Window分为三种:应用Window(对应Activity)、子Window(Dialog/PopupWindow等 必须有父Window)和系统Window(Toast等 需要权限 不需要依附token)。
  5. Window有层级,应用Window范围1-99,子Window范围1000-1999,系统Window范围2000-2999,层级越高显示越在上面。
  6. 每个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的重绘。
  7. Window的内部机制(涉及的类):ViewManager/WindowManager -> WindowMangerImpl -> WindowManagerGlobal -> View和ViewImpl -> WindowSession -> WindowManagerService,WindowManagerGlobal里大多是管理ViewRootImpl的逻辑,添加View到Window中的逻辑是在ViewImpl中执行的,ViewImpl中通过WindowSession与WMS进行通信,最后使用WMS来进行将View添加到Window中,属于远程IPC调用,真正的操作逻辑在WMS里。
  8. Activity中Window的创建过程:IPolicy -> PolicyManager -> Wiondow(PhoneWindow) -> 内部创建DecorView(mContentParent) -> 等到Activity的onResume() -> makeVisible() -> ViewManager的addView()将mDecor添加到Window -> mDecor.setVisibility(),Activity最终显示。
  9. 普通的Dialog必须使用Activity的Context才能显示出来,因为Activity的context才有windowToken;Toast可定时取消,用到了Handler,因此Toast无法在没有Looper的线程中使用。
  10. 有了WindowManager,我们就可以随意创建View和设置LayoutParams来让这个Window显示出来,可以设置级别和flag,用于创建应用悬浮窗等。
第九章:四大组件的工作过程
  1. Android四大组件中除了BroadcastReceiver外,其余三个组件都要在Menifest中注册,BroadcastReceiver可以在Menifest中静态注册也可以在代码中动态注册;Activity、Service和BroadcastReceiver需要借助Intent来启动。
  2. Activity主要作用是展示一个界面并与用户交互,扮演一个前台界面的角色;Service是一种计算型组件用于在后台执行一系列计算任务;BroadcastReceiver是一种消息型组件,用于在不同组件乃至不同应用中传递消息;ContentProvider内部的insert、delete、update和query方法是在Binder线程池中调用的,是异步操作,所以需要处理好线程同步问题。
  3. BroadcastReceiver可以静态注册和动态注册,静态注册指在Menifest中注册,此种形式应用不需启动便可收到广播(5.0以后不可),适用于长期接收事件,动态注册指在代码中注册,一般用的多,因为页面关闭后就取消注册了,广播用于实现低耦合的观察者模式。
  4. Service虽然指后台运行,但是还是在主线程中工作的,不能直接做耗时操作,可在IntentService中可以做耗时操作,且任务完成可以自己stop自己,后台计算不需要交互使用startService开启,需要业务接口交互使用bindService开启。
  5. ActivityManagerService(AMS)继承自ActivityManagerNative,ActivityManagerNative继承自Binder并实现了IActivityManager接口,因此AMS是一个Binder,是IActivityManager的具体实现;ApplicationThread继承自ApplicationThreadNative,ApplicationThreadNative继承自Binder并实现了IApplicationThread的接口,ApplicationThread是一个实现了很多schedule方法的业务接口,作为Binder使用,通过Handler回调到ActivityThread中处理;Instrumentation是一个辅助类,负责分担Activity中通过ApplicationThread与AMS交互的一些业务逻辑。
  6. Activity的启动过程涉及到的类:Activity -> Instrumentation -> ApplicationThread -> AMS -> ActivityStarter -> ActivityStackSupervisor -> ActivityStack -> ActivityStackSupervisor -> ApplicationThread -> Handler -> ActivityThread(H);Service的启动过程涉及到的类:Activity -> ContextImpl -> AMS -> ActiveService(AMS的辅助类) -> ApplicationThread -> Handler -> ActivityThread(H)
  7. 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等完成界面绘制并显示。

  1. 广播分为普通广播、有序广播和粘性广播,从Android5.0(其实3.1就开始了)开始系统为所有广播默认添加了FLAG_EXCLUDE_STOPPED_PACKAGES标志,标记广播是否要对已停止的应用起作用。
  2. ContentProvider是内容共享型组件,通过Binder向其他组件乃至其他应用提供数据;当ContentProvider所在的进程启动时,ContentProvider会被创建并发布到AMS中,ActivityThread中会先加载ContentProvider,因此ContentProvider的onCreate()要先于Application的onCreate()执行;ContentProvider是否是单例由
    android:multiprocess属性值来控制,一般都是单实例存在。
  3. 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。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容