Context继承关系
Activity/Service/Application都是继承自ContextWrapper,而ContextWrapper内部会包含一个base context,由这个base context去实现了绝大多数的方法。凡是跟UI相关的,都应该使用Activity作为Context来处理;其它的一些操作,Service/Activity/Application等示例都可以。
参考文档:Android Context 上下文 你必须知道的一切
ActivityThread工作原理,主线程的消息循环,与SystemServer跨进程交互(AMS)
在Application进程中管理执行主线程,调度和执行Activity、Broadcast,以及和Activity管理请求的其它操作。
system_server进程是由ActivityThread.attach()过程创建Application
普通app进程是由ActivityThread.handleBindApplication()过程创建Application(两次Binder调用)
参考文档:理解Application创建过程,ActivityThread
Application/Activity/Service生命周期,Activity的onNewIntent
Service/IntentService,Service和其它组件间通信,顺带HandlerThread
a.IntentService内部维护一个工作线程来处理耗时操作,每一个耗时操作将会在onHandleIntent回调方法中执行,可以多次启动IntentService,依次串行执行,任务执行完成后Service会自动停止(stopSelf)。
b.HandlerThread是自带Looper的Thread,创建后需start,之后便会调用Looper.prepare()创建该线程的Looper,再调用Looper.loop()开启线程消息循环。外界通过该线程的Looper创建对应的Handler对象,就可以发送对应的消息到该线程进行处理。
Fragment的懒加载实现,参数传递与保存
a.当使用FragmentTransaction来控制Fragment的show和hide时,onHiddenChange()方法会被回调。
b.setUserVisibleHint()在Fragment创建时会被回调(时机可能在onCreateView之前,控件使用需谨慎),且当Fragment可见状态发生变化时也会被回调;当Fragment结合ViewPager使用时,setUserVisibleHint()会被调用。
c.当一个Fragment重新创建的时候,系统会再次调用Fragment中的默认构造函数,推荐使用Fragment.setArguments(Bundle bundle)方式来传递参数。Activity重新创建时,会重新构建它所管理的Fragment,原先Fragment的字段值将会全部丢失,但是通过Fragment.setArguments(Bundle bundle)方式设置的bundle会保留下来,并在重建时恢复,setArguments方法的使用必须要在FragmentTransaction的commit之前使用。
d.设置setRetainInstance(true)后,可以让Fragment在Activity被重建时保持实例不变。
参考文档:为什么要通过Fragment.setArguments(Bundle)传递参数
Fragment使用陷阱
参考文档:
ContentProvider实例详解
ContentProvider(内容提供者)用于提供数据的统一访问格式,封装底层的具体实现。对于数据的使用者来说,无需知晓数据的来源是数据库、文件、或者网络,只需简单地使用ContentProvider提供的数据操作接口,也就是增(insert)、删(delete)、改(update)、查(query)四个过程。
参考文档:ContentProvider 使用详解,理解ContentProvider原理
BroadcastReceiver/LocalBroadcastManager实现原理
广播(Broadcast)机制用于进程/线程间通信,分为静态/动态
--->ApplicationThread.scheduleReceiver
参考文档:Android Broadcast广播机制分析
Android页面恢复,onSaveInstanceState(Activity容易被系统销毁时触发)和onRestoreInstanceState
a.在onSaveInstanceState里面保存数据,而在onRestoreInstanceState恢复数据,这个场景是在进程被系统回收之后,我们在最近使用程序列表里面点击那个应用时,系统会重启进程并且帮我们恢复Activity。前台进程死亡后恢复,恢复的是当前显示的Activity的上一个Activity,记住Activity要想被恢复,必须是经历过onSaveInstanceState的Activity。
b.onSaveInstanceState不一定会触发,onPause和onStop之后的Activity遇到系统内存不足、系统配置变化等会导致Activity销毁并可能触发onSaveInstanceState,不适合保存持久化数据(应在onPause中),适合保存瞬态数据,如UI控件状态、成员变量的值。
Android消息机制Handler/Looper/MessageQueue,主线程Looper.loop()为啥不会死循环
在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监听多个描述符,当某个描述符就绪(读或写就绪),则立即通知响应程序进行 读或写操作,本质同步IO,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
参考文档:Android消息机制
IdleHandler闲时任务处理
每次消费掉一个有效Message,在获取下一个Message时,如果当前时刻没有需要消费的有效(需要立即执行的)Message,那么会执行一次IdleHandler,执行完成之后线程进入休眠状态,直到被唤醒。
参考文档:你知道android的MessageQueue.IdleHandler吗?
Activity/Window/DecorView/TitleView+ContentView
a.Activity像个控制器,不负责视图部分。Window像个承载器,装着内部视图。DecorView是顶层视图,是所有View的最外层布局。ViewRoot像个连接器,负责沟通,通过硬件的感知来通知视图,进行用户之间的交互。
b.当用户点击屏幕产出一个触摸行为,这个触摸行为则是通过底层硬件来传递捕获,然后交给ViewRootImpl,接着将事件传递给DecorView,而DecorView再交给PhoneWindow,PhoneWindow再交给Activity,然后接下来就是我们常见的的View事件分发了。
参考文档:简析Window、Activity、DecorView以及ViewRoot之间的错综关系
ViewRootImpl源码分析事件分发
Android事件分发机制,嵌套滑动实现原理
a.Android事件分发先是传递到ViewGroup,再由ViewGroup传递到View的。
b.在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
c.子View中如果将传递的事件消费掉,ViewGroup中将无法接收到如何事件。
父View收到ACTION_DOWN,如果没有拦截事件,则ACTION_DOWN前驱事件被子视图接收,父视图后续事件会发送到子View。此时如果在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接收后续消息了,同时子视图会收到ACTION_CANCEL事件。
滑动冲突解决:一文解决Android View滑动冲突
参考文档:Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
一文读懂Android View事件分发机制
Android多线程的实现:Thread/ThreadPool/HandlerThread/AsyncTask/IntentService
ThreadPool:别再说你不懂线程池——做个优雅的攻城狮
AsyncTask:你真的了解AsyncTask?
View/SurfaceView/TextureView
参考文档:视频画面帧的展示控件 SurfaceView 及 TextureView 对比
Android UI 显示原理分析小结
一篇文章看明白 Android 图形系统 Surface 与 SurfaceFlinger 之间的关系
LayoutInflater实现原理
1、如果root为null,attachToRoot将失去作用,设置任何值都没有意义,新解析的view会直接作为结果返回。
2、如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root,新解析出来的view会被add到root中去,然后将 root 作为结果返回。
3、如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。
4、在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
参考文档:LayoutInflater原理分析
ViewStub
1、使用 ViewStub 需要在 XML 中设置 android:layout,不是 layout,否则会抛出异常。throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
2、ViewStub 不能作为根布局,它需要放在 ViewGroup 中, 否则会抛出异常。throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
3、一旦调用 setVisibility(View.VISIBLE) 或者 inflate() 方法之后,该 ViewStub 将会从试图中被移除(此时调用 findViewById() 时找不到该 ViewStub 对象)。
4、如果指定了 mInflatedId , 被 inflate 的 layoutView 的 id 就是 mInflatedId。
5、被 inflate 的 layoutView 的 layoutParams 与 ViewStub 的 layoutParams 相同。
参考文档:ViewStub解析
MeasureSpec源码解析
对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。
View的绘制流程,自定义View,自定义ViewGroup,View宽高获取时机
参考文档:一文彻底搞懂Android View的绘制流程
动画(帧动画/补间动画/属性动画)
requestLayout/invalidate/postInvalidate区别与联系
子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的View及其子View都会进行测量、布局、绘制。
当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
参考文档:Android View 深度分析requestLayout、invalidate与postInvalidate
RecyclerView与ListView(缓存原理,区别联系,优缺点)
ViewPager的缓存实现
SharedPreferences
SharedPreferences是Android平台上 轻量级的存储类,用来保存App的各种配置信息,其本质是一个以 键值对(key-value)的方式保存数据的xml文件,其保存在/data/data/shared_prefs目录下。虽然 内存缓存机制 表面上看起来好像是一种 空间换时间 的权衡,实际上规避了短时间内频繁的I/O操作对性能产生的影响,而通过良好的代码规范,也能够避免该机制可能会导致内存占用过高的副作用。
本质上文件的I/O是一个非常重的操作,直接放在主线程中的commit()方法某些场景下会导致ANR(比如数据量过大),因此更合理的方式是应该将其放入子线程执行。但是,更多的问题随之而来,比如子线程更新文件,必然会引发线程安全问题,通过使用了3把锁(mLock/mEditorLock/mWritingToDiskLock),对整个读写操作的线程安全进行了保证。
apply()方法设计的初衷是为了规避主线程的I/O操作导致ANR问题的产生,但并没有完全规避掉这个问题。在apply()方法中,首先会创建一个等待锁,根据源码版本的不同,最终更新文件的任务会交给QueuedWork.singleThreadExecutor()单个线程或者HandlerThread去执行,当文件更新完毕后会释放锁。但当Activity.onStop()以及Service处理onStop等相关方法时,则会执行 QueuedWork.waitToFinish()等待所有的等待锁释放,因此如果SharedPreferences一直没有完成更新任务(比如太频繁无节制的apply()导致任务过多),有可能会导致卡在主线程,最终超时导致ANR。Google 在 Activity 和 Service 调用 onStop 之前阻塞主线程来处理 SP,我们能猜到的唯一原因是尽可能的保证数据的持久化,因为如果在运行过程中产生了 crash,也会导致 SP 未持久化,持久化本身是 IO 操作,也会失败。由于没有使用跨进程的锁,SharedPreferences是进程不安全的,在跨进程频繁读写会有数据丢失的可能。实现思路很多,比如使用文件锁,保证每次只有一个进程在访问这个文件;或者对于Android开发而言,ContentProvider作为官方倡导的跨进程组件,其它进程通过定制的ContentProvider用于访问SharedPreferences,同样可以保证SharedPreferences的进程安全。
参考文档:SharedPreference存在的问题,SP ANR问题,SharedPreferences源码解析
MMKV
DataStore
Android SQLite的入门使用
assets目录与res目录的区别
两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。
res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹。
Android换肤原理
Android两种虚拟机,DVM和ART
参考文档:Dalvik 和 ART 有什么区别?
Android混淆
参考文档:Android混淆(Proguard)详解,点亮技能树 - 从害怕到玩转 Android 代码混淆!
ADB常用命令
APP安装过程
与应用相关的一些目录:
a./system/priv-app:系统应用安装路径,Android 4.4+开始出现,区分系统应用权限,拥有SignatureOrSystem权限,此目录下的sevice具有保活能力;
b./system/app:系统应用安装路径,权限略低于priv-app目录下的应用,放置比如厂商内置应用;
c./data/app:用户应用安装路径,应用安装时将apk复制到此目录下;
d./data/data:用户应用数据存放路径,存在沙箱隔离;
e./data/dalvik-cache:存放应用的dex文件;
f./data/system:存放应用安装相关文件。packages.xml是一个应用的注册表,在解析应用 时创建,有变化时更新,记录系统权限,各应用信息,如name、codePath、flag、version、userid,下次开机时直接读取并添加到内存列表;package.list指定应用的默认存储位置、userid等。
应用安装过程总结:
a.将应用apk拷贝到指定目录下;
b.解压apk,将dex文件拷贝到/data/dalvik-cache目录,创建/data/data数据目录;
c.解析AndroidManifest.xml及其它资源文件,提取应用包信息,注册到packages.xml中;
d.由Launcher进程通过PMS取出所以应用程序,展示在桌面上。
参考文档:Android应用安装过程深度解析
Android应用程序启动流程
Activity/Service/Broadcast启动流程,核心AMS
Android包管理机制,核心PMS
AndroidWindow管理机制,核心WMS
Android多进程
在Android中使用多进程,就是在AndroidManifest中给四大组件指定android:process属性。设置android:process属性时,如果是以:开头的名字,则表示这是一个应用程序的私有进程,否则它是一个全局进程(需符合android包名规范com.example.eg)。私有进程的名称会在冒号前自动加上包名,而全局进程则不会,一般我们都是有私有进程,很少使用全局进程。
1.静态成员和单例模式失效:Android为每一个进程分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致多进程下访问同一个类的对象会产生多份副本。所以在一个进程中修改某个值,只会在当前进程有效,对其他进程不会造成任何影响。解决方法就是使用Intent或者aidl等进程通讯方式传递内容。
2.线程同步失效:因为多进程的内存地址空间不同,锁的不是同一个对象,所以不管是锁对象还是锁全局对象都无法保证线程同步。3.SharedPreference的可靠性下降:因为SharedPrefercences底层通过读写xml实现,并发读写不是安全的操作,甚至会出现数据错乱。解决方法就是多进程的时候不并发访问同一个文件,比如子进程涉及到操作数据库,就可以考虑调用主进程进行数据库的操作。4.Application会多次创建:默认情况下,应用拥有一个主进程。组件被指定新进程之后,系统在启动这个组件的时候,事先会创建这个进程,再创建该组件。打印出进程名称办法:重载Application类的onCreate方法即可。
参考文档:Android 多进程通信
进程间通信IPC方式(socket/pipe/信号signal/消息队列message/共享内存/信号量semaphore)
参考文档:进程间通信IPC (InterProcess Communication),Android 多进程通信
Binder机制
面向对象思想的引入将进程间通信转化为通过某个Binder对象的引用,调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体(本地对象)位于一个进程中,而它的引用(代理对象)却遍布于系统的各个进程之中。
a.对于Server进程来说,Binder指的是Binder本地对象。
b.对于Client进程来说,Binder指的是Binder代理对象,它只是Binder本地对象的远程代理;对这个Binder代理对象的操作,会通过驱动最终转发到Binder本地对象上去完成;对于一个拥有Binder对象的使用者而言,它无需关心这是一个Binder代理对象还是Binder本地对象;对于代理对象的操作和对本地对象的操作对它来说没有区别。
c.对于传输过程而言,Binder是可以进行跨进程传递的对象;Binder驱动会对具有跨进程传递能力的对象做特殊处理:自动完成代理对象和本地对象的转换。
Server进程里面的Binder对象指的是Binder本地对象,Client进程里面的对象指的是Binder代理对象。在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换。因此,Binder驱动必然保存了每一个跨越进程的Binder对象的相关信息。在驱动中,binder_proc是描述进程上下文信息的,每一个用户空间的进程都对应一个binder_proc结构体,Binder本地对象的代表是一个叫做binder_node的数据结构(Server在Binder驱动中的体现),Binder代理对象的代表是一个叫做binder_ref的数据结构(Client在Binder驱动中的体现)。有的地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作Binder引用(句柄),其实指的是Binder对象在驱动里面的表现形式。
“Binder实体”和“Binder引用”可以很好的将Server和Client关联起来:因为Binder实体和Binder引用分别是Server和Client在Binder驱动中的体现。Client获取到Server对象后,“Binder引用所引用的Binder实体(即binder_ref.node)”会指向“Server对应的Binder实体”;同样的,Server被某个Client引用之后,“Server对应的Binder实体的引用列表(即binder_node.refs)”会包含“Client对应的Binder引用”。
IBinder是一个接口,它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。
IBinder负责数据传输,那么client与server端的调用契约(这里不用接口避免混淆)呢?这里的IInterface代表的就是远程server对象具有什么能力,具体来说,就是aidl里面的接口。
Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder,因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。
在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder,说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体IInterface的相关实现需要我们手动完成,这里使用了策略模式。
参考文档:带你简单理解Binder
Binder学习指南
Android Binder机制(一) Binder的设计和框架
Binder异常处理机制
参考文档:Binder异常处理机制
AIDL实现原理
参考文档:源码分析——从AIDL的使用开始理解Binder进程间通信的流程
屏幕适配
FileProvider
参考文档:Android 适配 - FileProvider
权限动态申请
使用兼容库checkSelfPermission、requestPermissions等几个权限相关的方法用v4包里的可以兼容6.0以下版本,否则需要包一层版本判断。
参考文档:Android动态权限申请
MultiDex
参考文档:MultiDex工作原理分析和优化方案
HotPatch热修复
参考文档:Android N混合编译与对热补丁影响解析
Android技术——ASM字节码插桩
Android Transform + ASM 初探
组件化
插件化