文章目录
- 一、Java之基本知识
- 二、设计模式相关
- 三、Android环境相关
- 四、Android基础
- 五、Android Framework
- 5.1 Message\Handle 源码解析
- 5.2 异步任务AsyncTask 源码解析
- 5.3 Android控件事件转发流程全解析
- 5.4 深入理解setContentView过程和View绘制过程
- 5.5 自定义VIew
- 5.6 启动流程详解
- 5.7 从Android源码到apk——apk打包过程
- 5.8 关于Android 64K引发的MultiDex你想知道的都在这里:一场由启动黑屏引发的惨案
- 5.8 Android Context详解
- 六、Android Third
- 七、组件化
- 八、插件化
- 九、性能监控
- 十、跨端
一、Java之基本知识
1.1 JVM相关
-
内存区域:
- 【程序计数区,虚拟机栈,本地方法栈】
- 程序计数器:用于指示当前线程所执行的字节码执行到了第几行
- 虚拟机栈:存储的有局部变量表、操作站、动态链接、方法出口等。当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。
- 本地方法栈:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的
- 【堆区】
- 堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存
- 堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域
- 【方法区】:方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。
- 【程序计数区,虚拟机栈,本地方法栈】
-
内存溢出
- 主要是由于代码编写时对某些方法、类应用不合理,或者没有预估到临时对象会占用很大内存量,或者把过多的数据放入JVM缓存,或者性能压力大导致消息堆积而占用内存,以至于在性能测试时,生成庞大数量的临时对象,GC时没有做出有效回收甚至根本就不能回收,造成内存空间不足,内存溢出
-
内存泄漏
- 1、静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。
- 2、内部类和外部类的引用容易出现内存泄露的问题
- 3、监听器的使用,java中往往会使用到监听器,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。
- 4、大量临时变量的使用,没有及时将对象设置为null也可能导致内存的泄露
- 5、数据库的连接没有关闭情况,包括连接池方法连接数据库,如果没有关闭ResultSet等也都可能出现内存泄露的问题。
-
自动GC?:在Java虚拟机中,存在自动内存管理和垃圾清扫机制
- 该机制对 JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证JVM中的内存空间,防止出现内存泄露和溢出问题。
-
Java GC机制主要完成3件事:
- 确定哪些内存需要回收
- 确定什么时候需要执行GC
- 如何执行GC
-
Java内存分配机制
- 对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)
- 年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接 被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉
- 年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再 贴切不过)和两个存活区(Survivor 0 、Survivor 1)。
- 绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;
- 当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);
- 此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0;
- 当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。
- 当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代。
- 年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行Major GC,也叫 Full GC
- 永久代的回收有两种:常量池中的常量,无用的类信息。
- 常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
- 类的所有实例都已经被回收
- 加载类的ClassLoader已经被回收
3.类对象的Class对象没有被引用(即没有通过反射引用该类的地方)
- 常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
-
常见回收算法:
- 【数组法】引用技术算法:每个对象都有一个引用计数器。无法解决相互引用
- 【图法】追踪回收算法:维持对象引用图,根节点遍历,孤点被回收。
- 压缩回收算法:将堆中活动对象移动到堆中的一端。
- 复制回收算法:堆分成两个大小相同的区域,只有一个被使用,满了就停止,并把活动对象复制到另一个区域
- 按代回收算法
-
Java代码相关
- finalize在对象被回收前调用
- system.gc()方法通知垃圾回收期运行
1.2 ClassLoader相关
1.2.1 Java ClassLoader
-
动态加载?
- Java程序在运行时并不一定被完整加载,只有当发现该类还没有加载时,才去本地或远程查找类的.class文件并验证和加载;
- 当程序创建了第一个对类的静态成员的引用(如类的静态变量、静态方法、构造方法——构造方法也是静态的)时,才会加载该类。Java的这个特性叫做:动态加载。
-
一个类的初始化包括3个步骤:
- 加载(Loading),由类加载器执行,查找字节码,并创建一个Class对象(只是创建);
- 链接(Linking),验证字节码,为静态域分配存储空间(只是分配,并不初始化该存储空间),解析该类创建所需要的对其它类的应用;
- 初始化(Initialization),首先执行静态初始化块static{},初始化静态变量,执行静态方法(如构造方法)。
-
根据java虚拟机规范,所有java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化。主动使用有以下6种:
-
- 创建类的实例
-
- 访问某个类或者接口的静态变量,或者对该静态变量赋值(如果访问静态编译时常量(即编译时可以确定值的常量)不会导致类的初始化)
-
- 调用类的静态方法
-
- 反射(Class.forName(xxx.xxx.xxx))
-
- 初始化一个类的子类(相当于对父类的主动使用),不过直接通过子类引用父类元素,不会引起子类的初始化(参见示例6)
-
- Java虚拟机被标明为启动类的类(包含main方法的)
-
-
Bootstrap CLassloder是由C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用
- JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载
- Bootstrap没有父加载器,但是它却可以作为任何一个ClassLoader的父加载器。比如ExtClassLoader
- 这句话的理解,必须结合后文中的 loadClass()过程,也就是在双亲委托模型中向上迭代父加载器查找时,如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader
-
Extention ClassLoader:加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
-
AppClassLoader:加载当前应用的classpath的所有类
-
加载器的实例化顺序 源码解析
- java虚拟机的入口应用,sun.misc.Launcher
-
双亲委托:loadClass的实现
- 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
- 递归,重复第1步的操作。
- 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面。
- Bootstrap ClassLoader首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找
- Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找
- ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找
- 如果没有子类会抛出各种异常
-
为什么要使用这种双亲委托模式呢?
- 【重复】因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 【安全】考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String.class来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String.class已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoade
-
自定义一个ClassLoader,默认加载路径为D:\lib下的jar包和资源
-
NoClassDefFoundError和ClassNotFoundException
- NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
- ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
1.2.2 Android ClassLoader
Android 中的 Dalvik/ART 无法像 JVM 那样 直接 加载 class 文件和 jar 文件中的 class,需要通过 dx 工具来优化转换成 Dalvik byte code 才行,只能通过 dex 或者 包含 dex 的jar、apk 文件来加载(注意 odex 文件后缀可能是 .dex 或 .odex,也属于 dex 文件)
因此 Android 中的 ClassLoader 工作就主要交给了 BaseDexClassLoader 来处理
-
系统内置
ClassLoader
是一个抽象类,其中定义了ClassLoader的主要功能。BootClassLoader
是它的内部类,用于预加载preload()常用类,加载一些系统Framework层级需要的类,我们的Android应用里也需要用到一些系统的类等- 与Java中的BootClassLoader不同,它并不是由C/C++代码实现,而是由Java实现的
SecureClassLoader
类和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。URLClassLoader
类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。在Android中基本无法使用
- BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。
PathClassLoader
加载系统类和应用程序的类,如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件DexClassLoader
可以加载自定义的dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载InMemoryDexClassLoader
是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。
-
用户自定义
- …
-
BaseDexClassLader 寻找 class 的路线就清晰了:
- 当传入一个完整的类名,调用 BaseDexClassLader 的 findClass(String name) 方法
- BaseDexClassLader 的 findClass 方法会交给 DexPathList 的 findClass(String name, List suppressed 方法处理
- 在 DexPathList 方法的内部,会遍历 dexFile ,通过 DexFile 的 dex.loadClassBinaryName(name, definingContext, suppressed) 来完成类的加载
- loadClassBinaryName中调用了Native方法defineClass()加载类
- 标准JVM中,ClassLoader是用defineClass加载类的,而Android中defineClass被弃用了,改用了loadClass方法,而且加载类的过程也挪到了DexFile中,在DexFile中加载类的具体方法也叫defineClass,相信这也是维护代码可读性
-
DexPathList
- BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的数组dexElements。dexElements数组就是odex文件的集合
- odex文件是 dexPath指向的原始dex(.apk,.zip,.jar等)文件在optimizedDirectory文件夹中生成相应的优化后的文件
- 如果不分包一般这个数组只有一个Element元素,也就只有一个DexFile文件
-
defineClass()
- 不同于java,
Android ClassLoader#defineClass(String name)
该方法被废弃使用,改为使用DexPathList#findClass(String name)
- 不同于java,
-
Resource的双亲委托模型
-
实例化顺序
- BootClassLoader的创建:main方法是ZygoteInit入口方法,其中调用了ZygoteInit的preload方法,preload方法中又调用了ZygoteInit的preloadClasses方法
- PathClassLoader的创建:ZygoteInit的startSystemServer()方法
1.3 进程相关
1.4 线程相关
-
新建、就绪、运行和死亡状态
-
不能对已经启动的线程再次调用start()方法,否则会出现java.lang.IllegalThreadStateException异常。
-
如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
-
sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效
-
yield()方法它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态
-
join [调用处的当前线程]加入[调用者线程]后面,等待[调用者线程]终止。
- 线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能
-
线程的使用应该在线程运行完后自动结束,但是可以使用interrupt结束一个线程
- 可以响应阻塞、等待的线程的中断,以实现终结
-
suspend()函数让当前线程进入停滞状态,除非收到resume()信号,否则不唤醒
1.5 并发相关
1.6 clone相关
1.7 注解相关
1.8 异常相关
二、设计模式相关
三、Android环境相关
3.1 Gradle
3.2 Proguard混淆
3.3 CI平台 JenKins
四、Android基础
4.1 Activity 使用详解
4.2 Fragment 使用详解
4.3 Service 使用详解
4.4 android 数据传递详解(Serialization、Parcelable、Parcel、Intent、Bundle)
4.5 自定义View详解
4.6 Animation 使用详解
4.7 Jni使用详解
4.8 HTTP\Socket\SOAP详解
4.9 进程保活
4.10 AOP
4.11 Android动态更换应用Icon之玩转桌面图标
五、Android Framework
5.1 Message\Handle 源码解析
5.2 异步任务AsyncTask 源码解析
- 设置当前AsyncTask的状态为RUNNING,上面的switch也可以看出,每个异步任务在完成前只能执行一次。
- 执行了onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
- 将我们传入的参数赋值给了mWorker.mParams ,mWorker为一个Callable的子类,且在内部的call()方法中,调用了doInBackground(mParams),然后得到的返回值作为postResult的参数进行执行;postResult中通过sHandler发送消息,最终sHandler的handleMessage中完成onPostExecute的调用。
- exec.execute(mFuture),mFuture为真正的执行任务的单元,将mWorker进行封装,然后由sDefaultExecutor交给线程池进行执行。
AsyncTask的缺陷,主要就是内部线程池的调度问题。可以分为两个部分说,在3.0以前,最大支持128个线程的并发,10个任务的等待。在3.0以后,无论有多少任务,都会在其内部单线程执行;
5.3 Android控件事件转发流程全解析
-
点击事件自上而下传递,当点击事件产生后由Activity来处理,传递给PhoneWindows,再传递给DecorView,最后传给定成ViewGroup
-
boolean dispatchTouchEvent(event)实现了整个迭代回调过程,其中调用onInterceptTouchEvent、onTouchEvent和child.dispatchTouchEvent
- Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理
-
ViewGroup#dispatchTouchEvent 实现 整个分发链和消费链的串联过程
- 事件分发链只触及点击位置穿透的控件,由父到子,由上到下. 具体的实现在于 Gropu#dispatchTouchEvent中会倒序遍历 Childrens, 遍历过程中会校验 触摸点位置是否在子View范围内或者子view是否在播放动画
- 消费链中一旦被消费(返回true)就终止整个事件分发流程
- ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),事件优先给 ChildView,会被 ChildView消费掉,ViewGroup 不会响应。因为 ChildView位于消费链的前端
- onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件
-
View#dispatchTouchEvent 处理单击事件(onClick)、长按事件(onLongClick)、触摸事件(onTouch),和View自身 onTouchEvent 方法的调度流程
- 调度顺序应该是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener
- 给 View 注册 OnTouchListener 不会影响 View 的可点击状态。即使给 View 注册 OnTouchListener ,只要不返回 true 就不会消费事件
- 只要View是CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE就会消费该点击事件。无论点击回调和长按回调中如何处理,都会消费点击事件(返回true)
- 点击包括很多种情况:譬如给View注册了 onClickListener、onLongClickListener、OnContextClickListener 其中的任何一个监听器或者设置了 android:clickable=”true”
- 某些 View 默认就是可点击的,例如,Button,CheckBox 等
- 所有事件都应该被同一 View 消费
- 安卓对第一次的事件( ACTION_DOWN )进行了特殊判断,View 只有消费了 ACTION_DOWN 事件,才能接收到后续的事件(可点击控件会默认消费所有事件),并且会将后续所有事件传递过来,不会再传递给其他 View,除非上层 View 进行了拦截
- 调度顺序应该是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener
5.4 深入理解setContentView过程和View绘制过程
5.4.1 Android屏幕层级
- PhoneWindow:窗口的具体实现,譬如Activity,一个Dialog,一个Toast,一个Menu菜单等
- PhoneWindow类是主要功能是:把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。
- DecorView(FrameLayout):一个应用窗口的根容器
- mContentRoot (LinearLayout):是DecorView本身或者是DecorView的一个子元素,在PhoneWindow#generateLayout通过加载预设layout实例,包含两个子元素,一个是TitleView,另一个是ContentView
- TitleView:ActionBar的容器
- ContentView(FrameLayout,contentParent , android.R.id.content):窗口内容的容器, 我们平常用的setContentView就是设置它的子View
5.4.2 setContentView过程
- 当我们自定义Activity继承自android.app.Activity时候,调用的setContentView()方法是Activity类的. 内部调用
getWindow().setContentView(layoutResID);
- getWindow()方法会返回Activity所关联的PhoneWindow,也就是说,实际上调用到了PhoneWindow的setContentView()方法
- 【2.1】mContentParent即为上面提到的ContentView,若为空则调用 ·installDecor()· 生成
- 【2.2】一般情况会来到这里
mLayoutInflater.inflate(layoutResID, mContentParent)
,调用mLayoutInflater.inflate()方法来填充布局,并传入了 实际布局父容器
- 在PhoneWindow的setContentView()方法中传入了ContentView作为LayoutInflater.inflate()的root参数,我们可以看到,通过层层调用,最终调用的是inflate(XmlPullParser, ViewGroup, boolean)方法来填充布局
- 一直读取xml文件,直到遇到开始标记
- 最先遇到的不是开始标记,报错
- **单独处理标签,递归地填充布局 **
- 递归加载根View的所有子View
rInflateChildren(parser, temp, attrs, true);
- 上面的inflate()和rInflate()方法中都调用了rInflateChildren()方法,rInflateChildren()方法实际上调用了rInflate()方法
- 调用rInflate()方法来递归填充布局
到此,整个setContentView基本上已经完成,但是这时,我们的View还是不可见的,因为我们仅仅是加载了布局,并没有对View进行任何的测量、布局、绘制工作。
5.4.2.1 installDecor过程
- 首先,会执行【2.1.1】生成mDecor代码,调用PhoneWindow#generateDecor方法,就是例化了DecorView,是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面
- 之后会执行【2.1.2】传入mDecor,并生成mContentParent
- 【2.1.2.0】根据设置的主题样式来设置DecorView的风格,比如说有没有titlebar之类的
- 【2.1.2.1】 将系统预设布局实例化为mContentRoot ,并加入到mDecor中
- 【2.1.2.2】在mContentRoot中找到ID_ANDROID_CONTENT,并实例为mContentParent
5.4.3 View绘制前过程
ViewRootImpl类: window对象,DecorView对象,以及windowManager对象的绑定过程
- 【步骤1】 在ActivityThread#handleLaunchActivity中启动Activity,在这里面会调用到Activity#onCreate方法,里边会有SetContentView()过程,从而完成上面所述的DecorView创建动作
- 【步骤2】 当onCreate()方法执行完毕,在handleLaunchActivity方法会继续调用到ActivityThread#handleResumeActivity方法
- performResumeActivity触发onResume()方法
- 获得window对象、DecorView对象、windowManager对象并调用
wm.addView(decor, l)
- 【步骤3】 WindowManager是抽象类,它的实现类是WindowManagerImpl,所以后面调用的是WindowManagerImpl#addView方法
- 【步骤4】实际上调用了mGlobal的成员函数,而mGlobal则是WindowManagerGlobal的一个实例,那么我们接着看WindowManagerGlobal#addView方法
- 【4.1】实例化了ViewRootImpl类
- 在【4.2】ViewRootImpl.setView()函数中,ViewRootImpl、DecorView和WMS会彼此关联,最后通过WMS调用ViewRootImpl#requestLayout方法开始View的测量、布局、绘制流程
View绘制的起点ViewRootImpl#requestLayout
- 检查发起布局请求的线程是否为主线程
- scheduleTraversals()
- 该方法会向主线程发送一个“遍历”消息,最终会导致ViewRootImpl#performTraversals()方法被调用。
- ViewRootImpl#performTraversals()
- 主要执行了三个方法,分别是performMeasure、performLayout、performDraw这三个方法,在这三个方法内部又会分别调用measure、layout、draw这三个方法来进行不同的流程,里边会调用我们最常接触的 onMeasure、onLayout、onDraw
5.4.4 测量measure阶段
在该阶段中,需要为整个ViewTree计算出每个控件的实际的大小,以便在layout阶段进行全局的放置 和 draw阶段进行绘制绘制,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际测量宽高都是由父视图和本身视图决定的
- ViewRootImpl#performTraversals中获取当前的窗口的全屏尺寸,作为最顶级的计划宽高规格
ViewRootImpl#performMeasure
通过调用DectorView的measure(计划宽高作为参数)
,开始整个全局ViewTree的测量过程- final measure()函数不会被重写,最终调用的是View#measure(),其中会调用onMeasure(计划宽高),并将计算后的实际测量宽高mMeasuredWidth、mMeasuredHeight读到内存中
- ViewGroup会在OnMeasure()中:
- (1)遍历子控件,考虑padding,child.margin,计划宽高及其已被占用部分作为参数,生成child的计划宽高,(2)调用子控件的measure(),迭代开始步骤3,这也是ViewGroup#measureChildWithMargins的过程
- 获取到子控件测量后的宽高,按照一定的业务需求,累积 子控件要求宽高,同时更新计划宽高被其他子控件占用的数值,以作为下次遍历的输入
- 遍历结束后,按照一定的业务需求,将子控件要求宽高 和 计划宽高 做一定的取舍,并通过调用setMeasuredDimension()设置为自己的宽高
- View会在OnMesure中,通过调用setMeasuredDimension()自己的宽高
- ViewGroup会在OnMeasure()中:
5.4.5 布局layout阶段
在该阶段中,得出每一个view的相对绘制位置矩阵(相对父布局的top,left,bottom,right),最终构成全局相对位置绘制树
onLayoutLayout的目的是为了确定子元素 在父容器中的位置,那么这个步骤理应该由父容器来决定而不是子元素,因此,我们可以猜到View中的onLayout方法应该是一个空实现,但是要注意一点无论View和Viewgroup都应该在layout中考虑自己的padding,当然ViewGroup还要考虑child.margin
- 【步骤1】调用DecorView的layout方法,并传入DecorView的实际测量宽高
- 测量阶段我们就已经知道了 DecorView继承自FrameLayout继承自ViewGroup继承自View,实际上调用的还是【步骤2】View#layout,这是个final方法
- 【步骤2.1】调用了setFrame方法,并把四个位置信息传递进去,这个方法用于确定当前控件的四个顶点的位置,即初始化mLeft,mRight,mTop,mBottom这四个值,当初始化完毕后,当前控件自身位置就会被确定
- 【步骤2.2】会调用onLayout()方法,该方法在View中是一个空实现,在ViewGroup中用于确定子View的位置,即在该方法内部,子View会调用自身的layout方法来进一步完成自身的布局流程。
- 遍历子控件
- 根据子View的layout_gravity属性、子View的测量宽高、父容器的padding值、child.margin,和其他子控件累积占用的位置来确定子View的布局参数
- 调用child.layout方法,把布局流程从父容器传递到子元素,开启迭代过程
- 更新累积的宽高
5.4.6 绘制draw阶段
测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成
- 重写View或ViewGroup的onDraw(): *传入了一个画布,然后当前view在画布上作画
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.GRAY);
}
我们来看下performDraw()
过程:
- 画布生成过程
- 【步骤1】实际调用了ViewRootImpl#draw方法,并传递了fullRedrawNeeded参数,而该参数由mFullRedrawNeeded成员变量获取,它的作用是判断是否需要重新绘制全部视图
- 【步骤2】ViewRootImpl#draw:
- 【步骤2.1】获取mDirty,该值表示需要重绘的区域
- 【步骤2.2】如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制。第一次绘制流程,需要绘制所有视图
- 【步骤2.3】调用了ViewRootImpl#drawSoftware方法,并把相关参数传递进去
- 【步骤2.3.1】实例化Canvas对象,并锁定canvas区域,由dirty区域决定
- 【步骤2.3.2】对canvas进行一系列的属性赋值
- 【步骤2.3.3】调用DecorView.draw,正式开始整个递归绘制过程
- View#draw迭代过程
- 对View的背景进行绘制
- 保存当前的图层信息(可跳过)
- 绘制View的内容,这里调用了View#onDraw方法
- 每个View都需要重载该方法;ViewGroup一般不需要实现该方法,除非有特定效果
- 对View的子View进行绘制(如果有子View),这里调用了dispatchDraw(canvas)实现迭代绘制过程
- dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个 地方“需要重绘”的视图才会调用draw()方法)
- 值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能
- 绘制View的褪色的边缘,类似于阴影效果(可跳过)
- 绘制View的装饰(例如:滚动条)
5.4.7 View的生命周期
-
onFinishInflate 【setContentView阶段完成】当View中所有的子控件均被映射成xml后触发
-
onAttachedToWindow() 【View绘制前阶段完成】当view被附着到一个窗口时触发,只会调用一次,在界面OnResume()之后,PhoneWindow#addView之前,可用来修改窗口尺寸
-
onMeasure(int, int) 确定所有子元素的大小
-
onSizeChanged(int, int, int, int) 当view的大小发生变化时触发,在layout阶段的setFrame时触发,常用来确定确定View大小(记录当前View的宽高)
-
onLayout(boolean, int, int, int, int) 当View分配所有的子元素的大小和位置时触发
-
onDraw(Canvas) view渲染内容的细节
-
onDetachedFromWindow() 当view离开附着的窗口时触发,Android123提示该方法和 onAttachedToWindow() 是相反的。ActivityThread.handleDestroyActivity(),只会调用一次。这时我们就在这个方法做一些收尾工作,如:取消广播注册等等。
-
onKeyDown(int, KeyEvent) 有按键按下后触发
-
onKeyUp(int, KeyEvent) 有按键按下后弹起时触发
-
onTrackballEvent(MotionEvent) 轨迹球事件
-
onTouchEvent(MotionEvent) 触屏事件
-
onFocusChanged(boolean, int, Rect) 当View获取 或失去焦点时触发
-
onWindowFocusChanged(boolean) 当窗口包含的view获取或失去焦点时触发
-
onWindowVisibilityChanged(int) 当窗口中包含的可见的view发生变化时触发
5.4.8 View的显示过程
07-12 13:44:45.413 23734-23734/? D/------﹕ ---onFinanshInflate
07-12 13:44:45.443 23734-23734/? D/------﹕ ---onMeasure
07-12 13:44:45.493 23734-23734/? D/------﹕ ---onSizeChanged
07-12 13:44:45.493 23734-23734/? D/------﹕ ---onLayout
07-12 13:44:45.503 23734-23734/? D/------﹕ ---onMeasure
07-12 13:44:45.503 23734-23734/? D/------﹕ ---onLayout
07-12 13:44:45.503 23734-23734/? D/------﹕ ---onDraw
5.4.9 invalidate(),requsetLaytout()与requestFocus
invalidate(),requsetLaytout()以及requestFocus()最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历,也就是说都会触发View绘制过程
但是他们也有一定的区别:通过一定的变量标示,有一些过程一定会重绘经历,但某些可能不需要
- requestLayout()方法
- 一定会导致调用measure()过程 和 layout()过程 ,但是不见得会draw过程
- 对整个View树重新布局layout过程包括measure()和layout()过程,一般不会调用draw()过程,不会重新绘制任何视图包括该调用者本身
- invalidate()方法
- 请求重绘View树,即draw()过程。
- 假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁请求invalidate()方法,就绘制该视图(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)。
- requestFocus()
- 请求View树的draw()过程,但只绘制“需要重绘”的视图
一般来说,如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局、绘制这三个流程,往往使用requestLayout。
而invalidate则是刷新当前View,使当前View进行重绘,不会进行测量、布局流程,因此如果View只需要重绘而不需要测量,布局的时候,使用invalidate方法往往比requestLayout方法更高效
5.4.9.1 requestLayout()引发过程
子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。
- 一定会导致调用measure()过程 和 layout()过程 ,但是不见得会draw过程
- 对整个View树重新布局layout过程包括measure()和layout()过程,一般不会调用draw()过程,不会重新绘制任何视图包括该调用者本身
源码分析:
- 【步骤1】判断当前View树是否正在布局流程,如果是该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行
- 【步骤2】为当前view设置标记位 PFLAG_FORCE_LAYOUT,该标记位的作用就是标记了当前的View是需要进行重新布局的
- 【步骤3】调用mParent.requestLayout方法,迭代式向父容器请求布局。
- 因为这里是向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法
- 即requestLayout事件层层向上传递,直到DecorView,即根View
- 而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。
- 【步骤4】ViewRootImpl#requestLayout
上边的讲解基本已经可以续上,整个View绘制过程了,在这里,我们补充说明下绘制过程中的一些变量标示
- PFLAG_FORCE_LAYOUT,这个标记位的作用就是在View的measure流程中,如果当前View设置了该标记位,则会进行测绘流程
- PFLAG_LAYOUT_REQUIRED,这个标记位的作用就是在View的layout流程中,如果当前View设置了该标记位,则会进行布局流程
5.4.9.2 invalidate()引发过程
- 请求重绘View树,即draw()过程。
- 假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁请求invalidate()方法,就绘制该视图(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)
当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)
- 【步骤0】生成需要绘制的区域矩阵:(0,0)到(当前控件大小),该值传给父布局使用的
- 【步骤1】根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
- 【步骤2】设置PFLAG_DIRTY标记位
- 【步骤3】 p.invalidateChild(this, damage);把需要重绘的区域传递给父容器
- **【步骤4】ViewGroup#invalidateChild
- 【步骤4】在ViewGroup#invalidateChild方法内部,先设置当前视图的标记位,接着有一个do…while…循环,该循环的作用主要是不断向上回溯父容器,求得父容器和子View需要重绘的区域的并集(dirty)。当父容器不是ViewRootImpl的时候,调用的是ViewGroup的invalidateChildInParent方法,我们来看看这个方法,
- 【步骤4.2.1】ViewGroup#invalidateChildInParent:调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标,接着调用union方法,把子dirty区域与父容器的区域求并集,换句话说,dirty区域变成父容器区域。最后返回当前视图的父容器,以便进行下一次循环
- **【步骤4.2.2】由于不断向上调用父容器的方法,到最后会调用到ViewRootImpl的invalidateChildInParent方法:进行offset和union对坐标的调整,然后把dirty区域的信息保存在mDirty中,最后调用了
一般会引发invalidate()的操作如下:
- 直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
- setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
- setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法, 继而绘制该View。
- setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
5.4.9.3 postInvalidate()引发过程
- 【步骤1】由以上代码可以看出,只有attachInfo不为null的时候才会继续执行,即只有确保视图被添加到窗口的时候才会通知view树重绘,因为这是一个异步方法,如果在视图还未被添加到窗口就通知重绘的话会出现错误,所以这样要做一下判断
- 【步骤2】调用了ViewRootImpl#dispatchInvalidateDelayed方法:用了Handler,发送了一个异步消息到主线程,显然这里发送的是MSG_INVALIDATE,即通知主线程刷新视图
- 【步骤3】具体的实现逻辑我们可以看看该mHandler的实现:
- 参数message传递过来的正是View视图的实例,然后直接调用了invalidate方法,然后继续invalidate流程
5.5 自定义VIew
为了方便理解,我们给自定义View进行以下3中分类:
5.5.1 组合式自定义
- 组合式自定义:通过继承一个已有布局样式,传入自己设定的布局xml,对其中的一些子控件进行合并管理
- 这种方法往往没有绚丽的效果,通过对android现有组件的组合使用,减少代码逻辑和方便调用提高效率,譬如经常使用的表单型控件
5.5.2 拓展式自定义
- 拓展式自定义:通过集成一个现有的控件,对其进行功能拓展
- 譬如我们继承TextView,并实现一些自己需要的特定属性
5.5.3 完全自定义
- 完全自定义:通过完全重新View或者ViewGroup实现自定义
- 技巧性要求更高,但是定制过程更灵活
对于VIew:
- 继承View
- 对Padding处理
- 对wrap_content处理
- 自定义属性
对于ViewGroup:
- 继承ViewGroup
- 对wrap_content处理 onMeasure,内部对margin响应
- 实现onLayout
5.6 启动流程详解
主要涉及进程:
- init进程:linux的根进程,android系统是基于linux系统的,因此可以算作是整个android操作系统的第一个进程
- Zygote(孵化)进程:android系统的根进程,主要作用:可以作用Zygote进程fork出SystemServer进程和各种应用进程;
- Zygote的Java框架层中,会创建一个Server端的Socket,这个Socket用来等待ActivityManagerService来请求Zygote来创建新的应用程序进程的
- Zygote进程通过fock自身创建的应用程序进程,这样应用程序程序进程就会获得Zygote进程在启动时创建的虚拟机实例
- 在应用程序创建过程中除了获取虚拟机实例,还可以获得Binder线程池和消息循环,这样运行在应用进程中应用程序就可以方便的使用Binder进行进程间通信以及消息处理机制了
- SystemServer进程
主要是在这个进程中启动系统的各项服务,比如ActivityManagerService,PackageManagerService,WindowManagerService服务等等; - 各种应用进程
启动自己编写的客户端应用时,一般都是重新启动一个应用进程,有自己的虚拟机与运行环境;
5.6.1 Zygote孵化进程启动流程
Zygote(孵化)进程是
- 所有的android进程的父进程,包括SystemServer和各种应用进程都是通过Zygote进程fork出来的,而Zygote进程则是通过linux系统的init进程启动的。
- init进程在启动Zygote进程时一般都会调用ZygoteInit类的main方法
在C层的处理有以下几个:
- 创建AppRuntime并调用其start方法,启动Zygote进程。
- 创建DVM虚拟机并为DVM注册JNI.
- 通过JNI调用ZygoteInit的main函数进入Zygote的Java框架层。
init进程在启动Zygote进程时最终会调用ZygoteInit类的main方法,因此我们这里看一下该方法的具体实现(基于android23源码)。
Zygote进程main方法主要执行逻辑:
- 由init进程fork出来的,并且执行其main方法启动
- 初始化DDMS;
- 注册Zygote进程的socket通讯;
- 初始化Zygote中的各种类,资源文件,OpenGL,类库,Text资源等等;
- preloadClasses()用于初始化Zygote中需要的class类;内部涉及 BootClassLoader的初始化
- preloadResources()用于初始化系统资源;
- preloadOpenGL()用于初始化OpenGL;
- preloadSharedLibraries()用于初始化系统libraries;
- preloadTextResources()用于初始化文字资源;
- prepareWebViewInZygote()用于初始化webview;
- fork出SystemServer进程,并反射出ZygoteInit实例,并最终执行其SystemServer#main()方法启动
runSelectLoop(abiList)
一个无限的循环,等待AMS的Socket请求来申请创建新的应用进程- fork出的其他进程,关闭socket连接;
5.6.2 SystemServer进程启动流程
SystemServer进程主要的作用是
- 启动各种系统服务,比如ActivityManagerService,PackageManagerService,WindowManagerService等服务,我们平时熟知的各种系统性的服务其实都是在SystemServer进程中启动的
- 当我们的应用需要使用各种系统服务的时候其实也是通过与SystemServer进程通讯获取各种服务对象的句柄的进而执行相应的操作的
主要任务:
- 由Zygote进程fork出来的,并且执行其main方法启动
- 设置系统的语言环境等;
- 设置虚拟机运行内存,加载运行库,设置SystemServer的异步消息
- [3.1]主要是优化设置systemserver的内存环境,进程优先级等
- [3.2]创建main looper thread
Looper.prepareMainLooper();
- [3.3]初始化native service
- 创建上下文Context对象mSystemContext
- 使用上下文对象,创建一个新的SystemServiceManager对象,并保存在LocalServices数据结构中(系统各种service服务的实际管理者)
- 可以理解为一个map对象 <XX.Class, instance>
- SystemServer进程在尝试启动服务之前会首先尝试与Zygote建立socket通讯,只有通讯成功之后才会开始尝试启动服务;
- 使用SystemServiceManager实例注册相关SystemService,并执行其onStart()方法开启(每一个SystemService都有自己的异步消息对象,并运行在单独的线程中)
- startBootstrapService()启动系统Boot级服务过程
- 利用反射,实例、注册并开启[安装服务Installer SystemService]
- 实例注册启动ActivityManagerService服务,并为其设置SysServiceManager和Installer
- 实例注册启动PowerManagerService服务,并与mActivityManagerService关联
- 实例注册启动DisplayManagerService服务,负责管理手机显示方面
- 实例注册启动PackageManagerService,负责管理多apk文件的安装,解析,删除,卸载等等操作
- 实例注册启动UserManagerService,负责创建和删除用户,以及查询用户信息
- 实例注册启动SensorServic,负责各种感应器的状态和数值
- 启动系统进程的Application
- startCoreServices()启动系统核心服务
- 实例注册启动LightsService服务,负责管理手机中关于闪光灯,LED等相关的服务
- 实例注册启动BatteryService,负责电池相关服务
- 实例注册启动UsageStatsManagerInternal,负责收集用户使用每一个APP的频率、使用时常
- 实例注册启动WebViewUpdateService,用于WebView的更新
- startOtherServices() 启动一些非紧要或者是非需要及时启动的服务,并启动Launcher app进程
- 实例注册启动一些非紧要或者是非需要及时启动的服务
- 实例注册启动ContentService,负责数据更新通知的管理者,是数据同步服务的管理中枢
- 实例注册启动ContentService,负责手机震动
- 实例注册启动ContentService,负责数据库等提供解决方法的服务
- 实例注册启动AlarmManagerService,负责定时服务
- …
- …
- 实例注册启动一些非紧要或者是非需要及时启动的服务
- 启动Launcher app进程的各种预备动作
- vibrator.systemReady();
- lockSettings.systemReady();
- wm.systemReady();
- mPowerManagerService.systemReady
- mPackageManagerService.systemReady();
- mDisplayManagerService.systemReady
- mActivityManagerService.systemReady
7.1. startSystemUi(context);
7.2 执行各种SystemService的启动方法,各种SystemService的systemReady方法
- startBootstrapService()启动系统Boot级服务过程
- 开启主线程Looper
Looper.loop();
5.6.3 Launcher启动流程
LauncherActivity中是以ListView来显示我们的应用图标列表的,并且为每个Item保存了应用的包名和启动Activity类名,这样点击某一项应用图标的时候就可以根据应用包名和启动Activity名称启动我们的App了
一般系统的启动页面Activity都会在androidmanifest一般系统的启动页面Activity都会在androidmanifest.xml中配置Intent.CATEGORY_HOME常量
5.6.3.1 Launcher 进程启动过程分析
- Zygote进程
- –> SystemServer进程
- –> startOtherService方法
- SystemServer进程在《startOtherService()启动一些非紧要或者是非需要及时启动的服务,并启动Launcher app进程》的第一阶段【n步启动服务过程】中,会启动LauncherAppService,该服务负责启动Launcher。
- 而后,在《startOtherService()启动一些非紧要或者是非需要及时启动的服务,并启动Launcher app进程》的第二阶段中会调用:mActivityManagerService#systemReady
- –> ActivityManagerService的systemReady方法
- –> startHomeActivityLocked方法
- –> ActivityStackSupervisor的startHomeActivity方法
- –> 执行Activity的启动逻辑,执行scheduleResumeTopActivities()方法
- startActivityLocked启动launcer进程
- scheduleResumeTopActivities启动Activity
5.6.4 应用进程启动流程
- 每一个android应用默认都是在他自己的linux进程中运行。
- android操作系统会在这个android应用中的组件需要被执行的时候启动这个应用进程,并且会在这个应用进程没有任何组件执行或者是系统需要为其他应用申请更多内存的时候杀死这个应用进程。
所以当我们需要启动这个应用的四大组件之一的时候如果这个应用的进程还没有启动,那么就会先启动这个应用程序进程。
5.6.4.1 Binder
!!!实际上,我们获取的Service对象并不是实际的对象类,而是其代理。运用了代理模式:在触发原对象的同时,还会触发Binder驱动的相关操作
** 代理的对象,是客户端持有的远程服务引用, 实际上甚至这个引用也并不是真正的远程Binder对象,下文讲到**
- getSystemService(getApplication().WINDOW_SERVICE);函数内部原理就是向ServiceManager查询标识符为getApplication().WINDOW_SERVICE的远程对象的引用,该对象继承Binder类
- 客户端持有远程服务的引用(并不是实际真实的远程Binder对象,这个引用实质上是WindowManager的某个代理)
- 客户端调用引用的函数,代理把参数打包到Parcel对象中,然后调用transact函数(该函数继承自Binder)
- Binder驱动转发
- 远程进程服务处理
- Binder驱动转发
- 客户端响应
内部核心源码:
//【核心】向ServiceManager拿到一个远程对象的 Binder驱动
IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
//【核心】将驱动封装为代理对象
IAccountManager service = IAccountManager.Stub.asInterface(b);
5.6.4.2 从Activity启动看应用启动
-
Instrumentation负责客户端的处理
- 持有 ActivityManagerNative 是服务端ActivityManagerService的本地应用,内部有代理实现binder
-
ActvityiManagerService负责服务端的处理
- 内部持有ActivityThread向客户端通信
- ActivityStackSupervisor 栈管理员
- ActivityStack栈
应用进程
- 【总入口】Activiyt#startActivity()
- 【内部调用】Activiyt#startActivityForResult()
- ps: 经测试requestCode的值小于0的时候都是不起作用的,所以当我们调用startActivityForResult的时候需要注意这一点
- 【分支判断】判断是否有ParentActivity
- 【分支2:有父】Activity#startActivityFromChild() 方法:有,则调用mParent.startActivityFromChild方法
- 【分支1:无父】Instrumentation#execStartActivity() 方法:没有,则启动Binder通信机制,ActivityManagerNative.getDefault().startActivity,远程调用ActivitManagerService
以下 为 进入服务端进程:
- 【分支1入口】ActivityManagerNative.getDefault().startActivity:Binder通信
- 【步骤1】ActivityManagerService#startActivity():被跨进程调用
- 【步骤1内部】ActvityiManagerService#startActivityAsUser()
- 【步骤1内部】ActivityStackSupervisor#startActivityMayWait()
- 【步骤2】ActivityStackSupervisor#startActivityLocked() 方法:相关初始化后,触发该步骤
- 【步骤3】ActivityStackSupervisor#startActivityUncheckedLocked()方法
- 【步骤4】ActivityStack#startActivityLocked()方法:主要执行初始化了windowManager服务,然后调用resumeTopActivitiesLocked方法
- 【步骤5】ActivityStackSupervisor#resumeTopActivitiesLocked()方法:经过循环逻辑判断之后,最终调用了resumeTopActivityLocked方法
- 【步骤6】ActivityStack#resumeTopActivitiesLocked() 方法
- 【步骤6重载调用】ActivityStack.resumeTopActivitiesLocked()
- 【步骤7 核心】** ActivityStack.resumeTopActivityInnerLocked() **:执行当前栈顶Activity的生命周期 或者 判断需要开启进程等
- 【其他步骤】。。。。。
- 【步骤8】调用了ActivityStackSupervisor#startSpecificActivityLocked()方法
- 【分支判断】判断一下需要启动的Activity所需要的应用进程是否已经启动
- 【分支2:进程已启动】ActivityStackSupervisor#realStartAtivityLocked()
- 【分支1:进程未启动】ActivityManagerService#startProcessLocked():启动应用进程
以下 为 进入应用进程:
- 【步骤1】Process#start():传入启动的进程的名称“android.app.ActivityThread”
- 【步骤2】Process#startViaZygote()
- 【步骤3】 Process#zygoteSendArgsAndGetResult():最终调用了Zygote并通过socket通信的方式让Zygote进程fork除了一个新的进程,并根据我们刚刚传递的”android.app.ActivityThread”字符串,反射出该对象并执行ActivityThread的main方法
- 【步骤4】 ActivityThread#main():开启主线程Looper循环等其他操作
- 【步骤5】 ActivityThread#attach():Binder跨进程通信ActivityManagerNative.getDefault().attachApplication()
以下 为 进入服务端进程:
- 【步骤8】ActivityManagerService#attachApplication():跨进程通信被调用
- 【步骤9】ActivityManagerService#attachApplicationLocked():该方法执行了一系列的初始化操作,这样我们整个应用进程已经启动起来了,以确保Activity可以启动
以下 为 进入应用进程:
- 【A.步骤10】thread.bindApplication:biner机制,跨进程通信
- 【A.步骤11】ApplicationThread#bindApplication:biner机制,跨进程通信被调用
- 【A.步骤12】ActivityThread.sendMessage()
- 【A.步骤13】ActivityThread.handleMessage()
- 【A.步骤14】ActivityThread.handleBindApplication():反射机制创建了Instrumentation对象,并执行了init方法,执行了Insrtumentation对象的初始化;调用了LockedApk.makeApplication方法反射创建了Application对象
- 【A.步骤15】Instrumentation#callApplicationOnCreate
- 【A.步骤16】Application#onCreate
以下 为 服务端进程的继续:
- 【B.步骤10】 ActivityStackSupervisor#attachApplicationLocked()
- 【B.步骤11】 ActivityStackSupervisor#realStartActivityLocked():启动Acitivity
5.6.5 Activity启动流程
在Actvity启动过程中,其实是应用进程与SystemServer进程相互配合启动Activity的过程,涉及到多个进程之间的通讯这里主要是ActivityThread与ActivityManagerService之间的通讯,其中:
- 应用进程主要用于执行具体的Activity的启动过程,回调生命周期方法等操作
ActivityThread响应ActivityManagerService的ActivityStack等的远程调用,并触发Instrumentation 进行Activity生命周期管理
使用ActivityManagerNative向ActivityManagerService申请远端通信,并远程调用ActivityStack、ActivityStackSupervisor进行Activity进出栈等操作 - SystemServer进程则主要是调用其中的各种服务,将Activity保存在栈中,协调各种系统资源等操作
ActivityManagerService响应ActivityManagerNative的远程调用,并使用ActivityStack、ActivityStackSupervisor进行管理
使用ActivityThread.IApplicationThread申请远端通信,并远程调用Instrumentation 进行Activity生命周期管理
5.6.5.1 执行栈顶Activity的onPause方法ActivityStack#startPausingLocked
在《【步骤8】调用了[ActivityStackSupervisor#startSpecificActivityLocked]》前触发
以下为 服务端进程:
- 【第1步】ActivityStack#startPausingLocked():pre.app.thread.schedulePauseActivity方法跨进程调用ActivityThread
以下为 应用进程:
- 【第2步】ActivityThread#schedulePauseActivity():跨进程通信中被调用
- 【第3步】ActivityThread#sendMessage():handler机制触发,发送PAUSE_ACTIVITY_FINISHING消息
- 【第4步】ActivityThread#handleMessage():handler机制响应 响应PAUSE_ACTIVITY_FINISHING
- 【第5步】ActivityThread#handlePauseActivity()
- 【第6步】ActivityThread#performPauseActivity()实现对栈顶Activity的onPause生命周期方法的回调
- 【第6.1步】Instrumentation#callActivityOnPuase()
- 【第6.2步】Activity#performPause() :最终回调到了该方法
- 【第6.3步】Activity#onPause()方法
- 【第7步】ActivityManagerNative.getDefault().activityPaused(token):ActivityThread#handlePauseActivity中会执行,应用进程远程通信告诉服务进程,栈顶Activity已经执行完成onPause方法了
以下为 服务端进程:
- 【第8步】ActivityManagerService#activityPaused()
- 【第9步】ActivityStack#activityPausedLocked()
- 【第10步】ActivityStack#completePauseLocked()
- 【第11步】ActivityStack#resumeTopActivitiesLocked():经过了一系列的逻辑之后,又调用了该方法
- 【第12步】** ActivityStack.resumeTopActivityInnerLocked() **
- 【第13步】ActivityStackSupervisor.startSpecificActivityLocked()
判断一下需要启动的Activity所需要的应用进程是否已经启动- 已启动,realStartAtivityLocked()
- 未启动,ActivityManagerService#startProcessLocked()
Process.start()
Process.startViaZygote()
启动了AcitivtyThread进程并执行了ActivityThread的main方法
5.6.5.2 ActivityStackSupervisor#startSpecificActivityLocked()启动进程和启动Activity
《【B.步骤11】 ActivityStackSupervisor#realStartActivityLocked():启动Acitivity》
以下为 服务端进程:
- 【第1步】ActivityStackSupervisor#realStartActivityLocked() : Binder机制跨进程通信ActivityThread
以下为 应用进程:
- 【第2步】IApplicationThread#scheduleLauncherActivity() :Binder机制跨进程调用响应,内部handler发送信息
- 【第3步】ActivityThread#sendMessage() :内部handler发送信息,H.LAUNCH_ACTIVITY
- 【第4步】ActivityThread#handleMessage()
- 【第5步】ActivityThread#handleLauncherActivity()
- 【第6步】ActivityThread#performLauncherActivity() :执行Activity的启动操作,需要的Activity对象以反射方式启动,并触发Create和Start
- 【第6.1步】Instrumentation#callActivityOnCreate()
- 【第6.1.1步】 Activity#performCreate()
- 【第6.1.2步】 Activity#onCreate() :第二个生命周期方法出来了,onCreate方法
- 【第6.2步】Activity#performStart():内部调用 Instrumentation
- 【第6.2.1步】 Instrumentation#callActivityOnStart()
- 【第6.2.2步】Activity#onStart() :第三个生命周期方法出来了,onStart方法
- 【第6.1步】Instrumentation#callActivityOnCreate()
- 【第7步】ActivityThread#handleResumeActivity() :触发Resume生命周期、界面绘制流程、原栈顶Activity的onStop
- 【第8步】ActivityThread#performResumeActivity()
- 【第8.1步】Activity#performResume()
- 【第8.2步】Instrumentation#callActivityOnResume()
- 【第8.3步】Activity#onResume() :第四个生命周期方法出现了,onResume方法
- 【第9步】《深入理解setContentView过程和View绘制过程》View绘制前过程
- 【第10步】Looper.myQueue().addIdleHandler(new Idler()):Handle机制,触发空闲消息,用于触发原栈顶Activity的stop
- 【第11步】ActivityManagerNative.getDefault().activityResumed(token) :跨进程通信
- 【第12步】ActivityManagerNative.getDefault().finishActivity() :跨进程通信
5.6.5.3 栈顶Activity执行onStop方法
以下为 应用进程:
- 【入口】【第7步】ActivityThread#handleResumeActivity() :触发Resume生命周期、界面绘制流程、原栈顶Activity的onStop
- 【第1步】Looper.myQueue().addIdleHandler(new Idler()) :空闲任务,当Messagequeue执行add方法之后就会回调其queueIdle()方法
- 【第2步】ActivityManagerNative.getDefault()#activityIdle() :binder机制,跨进程通信启动
以下为 服务端进程:
- 【第3步】ActivityManagerService#activityIdle() :binder机制,跨进程通信被调用
- 【第4步】ActivityStackSupervisor#activityIdleInternalLocked()
- 【第5步】ActivityStack#stopActivityLocked()
- 【第6步】IApplicationThread#scheduleStopActivity() :Binder机制,跨进程通信
以下为 应用进程:
- 【第7步】ActivityThread#scheduleStopActivity() :Binder机制,跨进程通信被调用
- 【第8步】ActivityThread#sendMessage() :handle机制发送Message
- 【第9步】ActivityThread#handleMessage() :handle机制,响应Message
- 【第10步】ActivityThread#handleStopActivity()
- 【第11步】ActivityThread#performStopActivityInner()
- 【第12步】ActivityThread#callCallActivityOnSaveInstanceState()
- 【第12.1步】 Instrumentation#callActivityOnSaveInstanceState()
- 【第12.2步】 Activity#performSaveInstanceState()
- 【第12.3步】 Activity#onSaveInstanceState()
- 【第13步】Activity#performStop()
- 【第13.1步】Instrumentation#callActivityOnStop()
- 【第13.2步】Activity#onStop()
5.7 从Android源码到apk——apk打包过程
5.8 关于Android 64K引发的MultiDex你想知道的都在这里:一场由启动黑屏引发的惨案
5.8 Android Context详解
从某种意义上,它就是一个万能接口百宝箱,譬如我们启动Acitivity需要Instruction,启动Service需要IActivityManager,获取pack需要PMS
如果让程序员自己去和这些东西打交道简直太麻烦了,那么能不能我写个百宝箱的接口,只要是常用的操作我就丢里边,然后它的实现类统一去和那些其他乱七八糟的东西打交道呢?
这就是Context
APP Context总数 = Application数(1) + Activity数(Customer) + Service数(Customer);
- ContextImpl和ContextWrapper继承自Context
- ContextImpl 实现了Context类的所有API。
- ContextWrapper内部包含有Context类型的mBase对象,mBase具体指向的是ContextImpl
- ContextImpl提供了很多功能,但是外界需要使用并拓展ContextImpl的功能,因此设计上使用了装饰模式,ContextWrapper是装饰类,它对ContextImpl进行包装,ContextWrapper主要是起了方法传递作用,ContextWrapper中几乎所有的方法实现都是调用ContextImpl的相应方法来实现的
- ContextThemeWrapper、Service和Application都继承自ContextWrapper,这样他们都可以通过mBase来使用Context的方法,同时它们也是装饰类,在ContextWrapper的基础上又添加了不同的功能
- ContextThemeWrapper中包含和主题相关的方法(比如: getTheme方法),因此,需要主题的Activity继承ContextThemeWrapper,而不需要主题的Service则继承ContextWrapper
5.8.1 ContextImpl实例化时间
ContextImpl实例化基本都会遇到的步骤:
- 在ActivityThread线程回调中创建ContextImpl对象,并把自身赋值给内部的mOuterContext对象,产生关联
- 最后经过里面一系列的方法传递,调用相应的attach,将ContextImpl对象传入到ContextWrapper类的mBase变量
Activity中ContextImpl实例化
- 通过startActivity启动一个新的Activity—>回调ActivityThread的handleLaunchActivity()方法—>内部会调用performLaunchActivity()方法
- performLaunchActivity()方法—>调用createBaseContextForActivity(xx,xx)方法
- createBaseContextForActivity(xx,xx )中创建了ContextImpl对象,并且调用了contextImpl的setOuterContext(activity),将当前的Activity对象赋值给了内部成员变量mOuterContext
- 所以到了这一步,ContextImpl类关联了Activity
- 最后通过调用Activity.attach( xx,xx,·····)方法,将createBaseContextForActivity返回的ContextImpl对象传入到ContextWrapper类的mBase变量
- 这样,ContextWrapper类的成员mBase就被实例化l
Service的Context创建过程与Activity的Context创建过程类似,也是在Service的启动过程中被创建
- 通过startService启动一个新的Activity—>回调ActivityThread的handleCreateService()方法创建了ContextImpl对象,并赋值mOuterContext,产生关联。
- 调用Service.attach方法,将ContextImpl对象传入到ContextWrapper类的mBase变量
Application Context的创建过程也是类似的:
- 一个APP以后每次重新启动时都会首先创建Application对象(每个APP都有一个唯一的全局Application对象,与整个APP的生命周期相同)
- 创建Application—>回调ActivityThread的handleBindApplication()方法
- 调用该方法中的LoadedApk类的makeApplication方法创建ContextImpl对象,,并赋值mOuterContext,产生关联。
- 中间还有一系列的attach传递
- 最后调用Application类的attach方法,ContextImpl对象传入到ContextWrapper类的mBase变量
六、Android Third
6.1 RXjava
- 观察者模式
- 代理模式
6.2 Volley
- 有两个任务队列:缓存+网络
- 构建过程:newRequestQueue时
- 构建HurlStack,这里会判断如果手机系统版本号是大于9的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例。
- 又创建了一个Network对象,它是用于根据传入的HttpStack对象来处理网络请求的
- new出一个RequestQueue对象,并调用它的start()方法进行启动,然后将RequestQueue返回
1. 先是根据任务队列创建了一个CacheDispatcher的实例,然后调用了它的start()方法
1. 接着在一个for循环里根据任务队列去创建NetworkDispatcher的实例,并分别调用它们的start()方法。这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的
- 请求过程:RequestQueue的add()方法。 会判断当前的请求是否可以缓存,如果不能缓存则直接将这条请求加入网络请求队列,可以缓存的话则将这条请求加入缓存队列
- 缓存Dispatcher线程处理 run
- 首先在11行可以看到一个while(true)循环,说明缓存线程始终是在运行的
- 接着在第23行会尝试从缓存当中取出响应结果,如何为空的话则把这条请求加入到网络请求队列中,如果不为空的话再判断该缓存是否已过期,如果已经过期了则同样把这条请求加入到网络请求队列中,否则就认为不需要重发网络请求,直接使用缓存中的数据即可。
- 之后会在第39行调用Request的parseNetworkResponse()方法来对数据进行解析,再往后就是将解析出来的数据进行回调了,这部分代码我们先跳过,因为它的逻辑和NetworkDispatcher后半部分的逻辑是基本相同的
- 网络Dispatcher线程处理。 run:
- 第7行我们看到了类似的while(true)循环,说明网络请求线程也是在不断运行的。
- 在第28行的时候会调用Network的performRequest()方法来去发送网络请求,而Network是一个接口,这里具体的实现是BasicNetwork
- 在第14行调用了HttpStack的performRequest()方法,这里的HttpStack就是在一开始调用newRequestQueue()方法是创建的实例,默认情况下如果系统版本号大于9就创建的HurlStack对象,否则创建HttpClientStack对象。前面已经说过,这两个对象的内部实际就是分别使用HttpURLConnection和HttpClient来发送网络请求的
- 回调
- 在NetworkDispatcher中收到了NetworkResponse这个返回值后又会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,以及将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。
- 在解析完了NetworkResponse中的数据之后,又会调用ExecutorDelivery的postResponse()方法来回调解析出的数据
- 第22行调用了Request的deliverResponse()方法,有没有感觉很熟悉?没错,这个就是我们在自定义Request时需要重写的另外一个方法,每一条网络请求的响应都是回调到这个方法中,最后我们再在这个方法中将响应的数据回调到Response.Listener的onResponse()方法中就可以了
6.3 OkHttp
OkHttpClient mOkHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url("https://www.jianshu.com/u/b4e69e85aef6")
.addHeader("user_agent","22222")
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response != null )
Log.i(TAG, "返回服务端数据:"+ String.valueOf(response.body().string()));
}
});
支持的最大并发请求数量64,最多通道5
- mOkHttpClient.newCall首先会new一个Call对象出来,但其实真正new出来的对象是NewCall对象
- 然后会执行NewCall的enqueue方法
- NewCall#enqueue该方法中首先判断请求有没有被执行,如果请求已经执行,那么直接抛出异常,如果请求没有执行,就会执行Dispatcher对象的enqueue方法
- Dispatcher的enqueue方法:
- 如果正在运行的异步请求数量小于最大的并发数,且正在运行的客户端实际数量请求小于规定的每个主机最大请求数量,那么就把该请求放进正在运行的异步请求队列。否则就把该请求放进将要执行的异步请求队列中
- RealCall执行任务
- RealCall通过执行getResponseWithInterceptorChain()返回Response,如果请求被取消则在进行OnFailue回调,如果请求成功则进行onResponse的回调。
- 在配置 OkHttpClient 时设置的 interceptors ()
- 负责失败重试以及重定向的RetryAndFollowUpInterceptor
- 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转为用户友好的响应的 BridgeInterceptor
- 负责读取缓存直接返回、更新缓存的 CacheInterceptor
- 负责和服务器建立连接的 ConnectInterceptor
- 配置 OkHttpClient 时设置的 networkInterceptors
- 负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor
- 在 return chain.proceed(originalRequest),中开启链式调用
- client.dispatcher().finished(this); 执行完成后移除任务,并触发 readyAsyncCalls 的执行
- RealCall通过执行getResponseWithInterceptorChain()返回Response,如果请求被取消则在进行OnFailue回调,如果请求成功则进行onResponse的回调。
6.4 美团页面切分框架Shield源码分析
6.5 Push
6.6 Cydia Substrate的Hook
6.7 butterknife
编译时注解处理,生成一个指定名称的指定接口的实现java类,,,该类的内部的构造函数需要传入目标activity和目标decorview,该类的构造函数中会复制相关bindview注释的变量
调用bind时,当前的activity实例会根据自己的名称找到,对应的上述java类,,,调用起构造函数,并传入自己和自己的decorview
6.8 动态权限
-
Activity基类中,检验主要权限:存储、网络、手机状态
- 启动时在第一个验证界面检测,无权限则调用系统的
ActivityCompat.requestPermissions
- 回调中
- 失败则弹窗
- 成功则执行下一步
- 启动时在第一个验证界面检测,无权限则调用系统的
-
工具类中,调用方法,传入Activity
- Activity中加入一个fragment
- fragment中去调用系统的
ActivityCompat.requestPermissions
- fragment中监听回调
- 成功则移除自己
- 失败则再次弹窗告知未获得权限的影响(其实还是一个透明界面),并移除自己
-
服务中,借助context即可判断是否具备权限,如果不具备:
- 跳往一个透明Activity界面,并传递封装好的PermissionEvent(内部包含权限列表)
- 透明界面onCreate就直接弹出一个 业务对话框: 为了xxx, 请允许获得XX权限。 确定按钮的点击事件为,调用系统的
ActivityCompat.requestPermissions
- 透明界面onRequestPermissionsResult处理回调
- 失败,则再次弹窗告知未获得权限的影响(其实还是一个透明界面)。 确认按钮点击事件为关闭当前界面
- 成功则直接关闭当前界面
后台服务再申请权限时,不做强制校验,这也就意味着,相关异常必须被catche。 后台服务因为无权限可能无法执行相关业务,但是保证不崩溃
6.9 EventBus
两个全局变量:
- 全局的map对象Map<Class<?>, CopyOnWriteArrayList> subscriptionsByEventType其中key是eventType,value为监听者
- 我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的
- Map<Object, List<Class<?>>> typesBySubscriber;其中key是订阅者实例对象,value为该订阅者订阅了的eventType列表
- 根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑
两个核心过程:
-
register:
- 获取订阅者的Class对象
subscriber.getClass()
,由于我们传入的为this,即MainActivity的实例 - 使用反射查找订阅者中的事件处理方法集合
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)
- ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex,ignoreGeneratedIndex 默认就是false,可以通过EventBusBuilder来设置它的值
- 遍历事件处理方法集合,调用subscribe(Object subscriber, SubscriberMethod subscriberMethod)方法
- 在subscribe方法内,将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中
- subscriptionsByEventType
- 将监听者和监听函数封装为 new Subscription(subscriber, subscriberMethod);
- 根据事件类型的Class通过subscriptionsByEventType.get(eventType)获取Subscription集合
- 遍历订阅事件集合,找到比subscriptions中订阅事件小的位置,然后插进去
- typesBySubscriber
- subscriptionsByEventType
- 如果是粘性事件的话,就立马投递、执行
- 获取订阅者的Class对象
-
unregister
- 删除subscriptionsByEventType中与订阅者相关的所有subscription
- 删除typesBySubscriber中与订阅者相关的所有类型
-
postSticky
- 将事件加入到stickyEvents这个Map类型的集合中
- 调用post方法
-
post
- 从PostingThreadState对象中获取事件队列,并将当前事插入到事件队列中
- 将事件加入当前线程的事件队列中
- 将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件
- 事件继承性为false,只发送当前事件类型的事件
- 事件继承性为true,找到当前事件所有的父类型并调用postSingleEventForEventType方法发送事件
- 同步取出该事件对应的Subscription集合并遍历该集合,将事件event和对应Subscription传递给postingState并调用postToSubscription方法对事件进行处理
- 在postToSubscription中分为四种情况
- POSTING,调用invokeSubscriber(subscription, event)处理事件,本质是method.invoke()反射
- MAIN,如果在主线程直接invokeSubscriber处理;反之通过handler切换到主线程调用invokeSubscriber处理事件
- BACKGROUND,如果不在主线程直接invokeSubscriber处理事件;反之开启一条线程,在线程中调用invokeSubscriber处理事件
- ASYNC,开启一条线程,在线程中调用invokeSubscriber处理事件
6.10 Google的Protocol Buffer 序列化
-
序列化的本质:对数据进行编码 + 存储
-
Protocol Buffer的性能好:传输效率快,主要原因 = 序列化速度快 & 序列化后的数据体积小,其原因如下:
- 序列化速度快的原因:
a. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等)
b. 采用 PB 自身的框架代码 和 编译器 共同完成 - 序列化后的数据量体积小(即数据压缩效果好)的原因:
a. 采用了独特的编码方式,如Varint、Zigzag编码方式等等
b. 采用T - L - V (Tag - Length - Value,标识 - 长度 - 字段值)的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑
- 序列化速度快的原因:
-
Varint编码方式介绍
- i. 简介
- 定义:一种变长的编码方式
- 原理:用字节 表示 数字:值越小的数字,使用越少的字节数表示
- 作用:通过减少 表示数字 的字节数 从而进行数据压缩
- 问题:如果采用 Varint编码方式 表示一个负数,那么一定需要 5 个 byte(因为负数的最高位是1,会被当做很大的整数去处理)
- 解决方案: Protocol Buffer 定义了 sint32 / sint64 类型表示负数,通过先采用 Zigzag 编码(将 有符号数 转换成 无符号数),再采用 Varint编码,从而用于减少编码后的字节数
如:
- 对于 int32 类型的数字,一般需要 4个字节 表示;
- 若采用 Varint编码,对于很小的 int32 类型 数字,则可以用 1个字节 来表示
- 虽然大的数字会需要 5 个 字节 来表示,但大多数情况下,消息都不会有很大的数字,所以采用 Varint方法总是可以用更少的字节数来表示数字
- Zigzag编码方式详解
- i. 简介
- 定义:一种变长的编码方式
- 原理:使用 无符号数 来表示 有符号数字;
- 作用:使得绝对值小的数字都可以采用较少 字节 来表示;
七、组件化
7.1 阿里开源路由框架ARouter的源码分析
八、插件化
整个Hook过程简要总结如下:
- 寻找Hook切入点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。
- 静态方法和单例对象都可能涉及全局变量,而全局变量作为独立性存储,才是真正的hook点
- 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。
- 大量使用代理,因为我们是要在保证原逻辑的基础上进行一定程度的修改
- 偷梁换柱——用代理对象替换原始对象,往往使用反射
当然,在实际操作过程中,我们为了 替换目标对象,你会发现 你需要创建并hook 一些中间变量以达成最终目的。
8.1 Binder Hook
系统Service的使用其实就分为两步:
IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象
IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口
- asInterface 提供了Hook点
obj.queryLocalInterface(DESCRIPTOR);
先查看本进程是否存在这个Binder对象,如果有那么直接就是本进程调用了;- 因此我们知道了,我们需要hook
ob对象的queryLocalInterface方法
,而这个obj对象刚好是我们第一步返回ServiceManager.getService("service_name");
的IBinder对象
- 因此我们知道了,我们需要hook
- 如果不存在那么创建一个代理对象,让代理对象委托驱动完成跨进程调用。
最终,我们希望能修改这个getService方法的返回值,让这个方法返回一个我们伪造过的IBinder对象;这样,我们可以在自己伪造的IBinder对象的queryLocalInterface方法作处理,进而使得asInterface方法返回在queryLocalInterface方法里面处理过的值,最终实现hook系统服务的目的
- getService 是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;
- 我们没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量
- ServiceManager为了避免每次都进行跨进程通信,把这些Binder代理对象缓存在一张map里面
- 我们可以替换这个map里面的内容为Hook过的IBinder对象,由于系统在getService的时候每次都会优先查找缓存,因此返回给使用者的都是被我们修改过的对象,从而达到瞒天过海的目的
总结一下,要达到修改系统服务的目的,我们需要如下两步:
- 【目标代理】代理实现伪造一个系统服务对象IXXInterface
- 【静态切入点asInterface】接下来就要想办法让 ** 静态方法asInterface**能够返回我们的这个伪造对象IXXInterface而不是原始的系统服务对象。
- 【切入点延申getService】通过上文分析我们知道,只要让getService返回的IBinder对象,所具有的queryLocalInterface方法直接返回我们伪造过的系统服务对象IXXInterface就能达到目的。
- 所以,我们需要伪造一个IBinder对象,主要是修改它的queryLocalInterface方法,让它返回我们伪造的系统服务对象;
- 【中间代理】 代理实现伪造一个系统服务对象IBinder
- 【最终的反射嵌入】然后把这个伪造对象放置在ServiceManager的缓存map里面即可。(全局单例)
8.2 AMS & PMS & ActivityThread Hook
- AMS
- ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用
- ActivityManagerNative全局,内部的gDefault字段也是全局的,且是一个Singleton对象。
- gDefault内部的mInstance为IActivityManager对象【最终hook对象】
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
// 获取 gDefault 这个字段, 想办法替换它
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// 4.x以上的gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象
Object rawIActivityManager = mInstanceField.get(gDefault);
- PMS
- ActivityThread.sPackageManager 作为ActivityThread中的单例模式存在
- ActivityThread 实际上是主线程,而主线程一个进程只有一个,因此这里是一个良好的Hook点
android.app.ActivityThread
内部的函数currentActivityThread
可以获取当前 ActivityThread对象
- 通过Context类的getPackageManager方法获取到的ApplicationPackageManager对象里面的mPM字段。
- ActivityThread.sPackageManager 作为ActivityThread中的单例模式存在
// 获取全局的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 获取ActivityThread里面原始的 sPackageManager
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);
// 准备好代理对象, 用来替换原始的对象
Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
new Class<?>[] { iPackageManagerInterface },
new HookHandler(sPackageManager));
//获取ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
8.3 Activity
8.3.1 伪注册启动的实现: 基于IActivityManager和Handle.mcallback的实现
-
hook了IActivityManager调包ActivityStackSupervisor的校验注册环节:在ActivityStackSupervisor的校验注册环节,把PlugActivity替换为已注册HasRegActivity,实现绕过
- Activity的启动是要经过AMS进行验证的,要判断其是否在manifest里注册过。所以,我们可以事先在manifest里注册一个备用的HasRegActivity,然后在应用进程传递给AMS信息是把里面的Intent掉包,把启动的插件PlugActivity信息替换为manifest里的HasRegActivity,欺骗AMS
- 为在应用进程本地启动Activity最终是调用ActivityManagerNative里的gDefault里的IActivityManager对象的startActivity方法来把信息交给AMS的
- ActivityManager全局,内部的dDefault字段也是全局的,且是一个Singleton对象。 内部的mInstance为IActivityManager对象【最终hook对象】
- 那我们可以把这个IActivityManager对象给hook掉,替换为我们自己的代理对象,然后修改startActivity方法
-
hook了ActivityThread.handler的mCallBack恢复ActivityThread.handler启动PlugActivity:在收到启动HasRegActivity的信号时,反射替换为启动 PlugActivity,从而具备一样的生命周期
- 启动流程分析,这里会到达ActivityThread里来,然后发送一个异步消息给Handler,我们可以hook掉该Handler,修改它里面启动Activity的方法,把真正要启动的Activity换回去
- 其他生命周期的影响
- AMS和ActivityThread之间的通信采用了token来对Activity进行标识,并且此后的Activity的生命周期处理也是根据token来对Activity进行标识的
- 我们在Activity启动时用插件TargetActivity替换占坑SubActivity,这一过程在performLaunchActivity方法调用之前,因此注释2处的r.token指向的是TargetActivity,在performDestroyActivity的注释1处获取的就是代表TargetActivity的ActivityClientRecord,可见TargetActivity是具有生命周期的
8.3.2 伪注册中:加载插件中的PlugActivity
- 从ActivityThread的handler接收到Launch Message后,获取
r.packageInfo.getClassLoader()
加载class - r.packageInfo是一个LoadedApk类的对象
- r是一个ActivityClientRecord对象
- 它的实例过程中调用getPackageInfo进行 packageInfo的生成
- getPackageInfo方法中,设置了classLoader
- 判断了调用方和或许App信息的一方是不是同一个userId;如果是同一个user,那么可以共享缓存数据(要么缓存的代码数据,要么缓存的资源数据)
- 接下来尝试获取缓存数据
mPackages.get(aInfo.packageName);
; - 如果没有命中缓存数据,才通过LoadedApk的构造函数创建了LoadedApk对象;创建成功之后,如果是同一个uid还放入了缓存
两种方案
- 激将方案:定义实例化一个LoadedApk(持有自定义的ClassLoader),并注入缓存
- mPackages存在于ActivityThread类
- 使用与系统完全相同的方式创建LoadedApk对象,填充这个map
- getPackageInfoNoCheck,我们需要构造两个参数 其一是ApplicationInfo,其二是CompatibilityInfo
- 保守方案:new LoadedApk对象时传入了 当前ApplicationInfo,使用了应用的宿主的ClasLoader,给宿主打补丁
- 给默认PathClassLoader打补丁
- 我们在Context环境中直接getClassLoader()获取到的就是宿主程序唯一的ClassLoader。
- 给默认PathClassLoader打补丁
8.3.3 伪注册启动的实现: 基于Hook Instrumentation的execStartActivity和newAcitivity方案实现
- 首先我们自定义一个Instrumentation,在execStartActivity方法中将启动的TargetActivity替换为SubActivity
- 首先查找要启动的Activity是否已经在AndroidManifest.xml中注册了,如果没有就在注释1处将要启动的Activity(TargetActivity)的ClassName保存起来用于后面还原TargetActivity,接着在注释2处替换要启动的Activity为StubActivity,最后通过反射调用execStartActivity方法,这样就可以用StubActivity通过AMS的验证
- 在InstrumentationProxy 的newActivity方法还原TargetActivity
- newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。
- 编写hookInstrumentation方法,用InstrumentationProxy替换mInstrumentation:
- 在MyApplication的attachBaseContext方法中调用HookHelper的hookInstrumentation方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。
8.4 broadcast
8.4.1 注册过程
BroadcastReceiver的注册也是通过AMS完成的:
- 动态:Context类的registerReceiver的真正实现在ContextImpl里面,而这个方法间接调用了registerReceiverInternal
- 对发送者的身份和权限做出一定的校检
- 把这个BroadcastReceiver以BroadcastFilter的形式存储在AMS的mReceiverResolver变量中,供后续使用。
- ActivityManagerNative.getDefault().registerReceiver
- IIntentReceiver作为服务端调用客户端的手柄传给AMS
- 静态:系统会通过PackageParser解析Apk中的AndroidManifest.xml文件,因此我们有理由认为,系统会在解析AndroidMafest.xml的标签(也即静态注册的广播)的时候保存相应的信息
8.4.2 发送过程context#sendBroadcast
发送广播也是通过AMS进行的:Context中方法的调用都会委托到ContextImpl这个类,我们直接看ContextImpl对这个方法的实现
主要调用了 ActivityManagerNative.getDefault().broadcastIntent
我们转向服务端代码:
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
registeredReceivers = mReceiverResolver.queryIntent(intent,
resolvedType, false, userId);
- 广播的匹配AMS#broadcastIntentLocked
- receivers是对这个广播感兴趣的静态BroadcastReceiver列表;
- mReceiverResolver存储了动态注册的BroadcastReceiver的信息;
现在系统通过PMS拿到了所有符合要求的静态BroadcastReceiver,然后从AMS中获取了符合要求的动态BroadcastReceiver
8.4.3 唤醒过程
唤醒这些广播接受者。简单来说就是回调它们的onReceive方法
AMS跨进程通信
- 首先创建了一个BroadcastRecord代表此次发送的这条广播,然后把它丢进一个队列,最后通过scheduleBroadcastsLocked通知队列对广播进行处理
- 在BroadcastQueue中通过Handle调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法
- 这个IIntentReceiver正是在广播注册过程中由App进程提供给AMS进程的Binder对象,现在AMS通过这个Binder对象进行IPC调用通知广播接受者所在进程完成余下操作
- 在上文我们分析广播的注册过程中提到过,这个IItentReceiver的实现是LoadedApk.ReceiverDispatcher
LoadedApk.ReceiverDispatcher#performReceive方法
中反射实例化BroadcastReceiver并触发回调
8.4.4 hook方式
- 首先调用parsePackage解析到apk对象对应的Package对象Map<ActivityInfo, List<? extends IntentFilter>>
- 读取Package对象里面的receivers字段,注意这是一个 List (没错,底层把当作处理)
- 接下来要做的就是根据这个List 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了)
- 按动态广播方式注册
8.5 Service
过程与Activity基本一样,无非是ActivityThread创建实例时,Service类的创建过程与Activity是略微有点不同的,虽然都是通过ClassLoader通过反射创建,但是Activity却把创建过程委托给了Instrumentation类而Service则是直接进行
8.5.1 启动过程
- 【客户端】Service也是通过AMS进行的:Context中方法的调用都会委托到ContextImpl这个类,我们直接看ContextImpl对这个方法的实现
- 这个方法最终通过ActivityManagerNative实现跨进程通信,借助AMS进而完成Service的绑定过程
- IServiceConnection sd变量 都是ActivityThread给AMS提供的用来与之进行通信的Binder对象;这个接口的实现类为LoadedApk.ServiceDispatcher
- 【服务端】最终调用了ActivityManagerNative的bindService,而这个方法的真正实现在AMS里面
- 首先它通过retrieveServiceLocked方法获取到了intent匹配到的需要bind到的Service组件res;
- 然后把ActivityThread传递过来的IServiceConnection使用ConnectionRecord进行了包装,方便接下来使用;
- 最后如果启动的FLAG为BIND_AUTO_CREATE,那么调用bringUpServiceLocked开始创建Service
- 【服务端】bringUpServiceLocked开始创建Service
- 如果Service所在的进程已经启动,那么直接调用realStartServiceLocked方法来真正启动Service组件
- app.thread#scheduleCreateService本地创建Service(这个Binder的Server端在ActivityThread的ApplicationThread类)
- 进入应用端
- scheduleCreateService发送handle信息触发handleCreateService,反射实例化并调用onCreate方法生命周期
- AMS#requestServiceBindingLocked实现Bind和connect
- app.thread#requestServiceBindingLocked实现Bind和connect
- 进入应用端
- scheduleBindService发送handle信息触发handleBindService,反射实例化并调用onBind方法生命周期
- 【IPC】 ActivityManagerNative.getDefault().publishService
- 进入服务端
- 取出已经被Bind的这个Service对应的IServiceConnection对象,然后调用它的connected方法;
- app.thread#requestServiceBindingLocked实现Bind和connect
- app.thread#scheduleCreateService本地创建Service(这个Binder的Server端在ActivityThread的ApplicationThread类)
- 如果Service所在的进程还没有启动,那么先在AMS中记下这个要启动的Service组件,然后通过startProcessLocked先启动新的进程
- 如果Service所在进程不存在,那么会调用startProcessLocked方法创建一个新的进程,并把需要启动的Service放在一个队列里面;
- 创建进程的过程通过Zygote fork出来,进程创建成功之后会调用ActivityThread的main方法,在这个main方法里面间接调用到了AMS的attachApplication方法,在AMS的attachApplication里面会检查刚刚那个待启动Service队列里面的内容,并执行Service的启动操作;
- 如果Service所在的进程已经启动,那么直接调用realStartServiceLocked方法来真正启动Service组件
8.5.2 hook方式
我们可以注册一个真正的Service组件ProxyService,让这个Service承载一个真正的Service组件所具备的能力(进程优先级等);当启动插件的服务比如PluginService的时候,我们统一启动这个ProxyService,当这个ProxyService运行起来之后,再在它的onStartCommand等方法里面进行分发,执行PluginService的onStartCommond等对应的方法;
我们把这种方案形象地称为「代理分发技术」
- 注册代理Service
- 拦截AMS#startService等调用过程
- 手动控制Service组件的生命周期,需要拦截startService,stopService等调用,并且把启动插件Service全部重定向为启动ProxyService(保留原始插件Service信息);这个拦截过程需要Hook ActvityManagerNative
- 和Activity的方式类似
- 编写并注册代理ProxyService实现Service匹配和创建分发
- 上文中我们把启动插件Service重定向为启动ProxyService,现在ProxyService已经启动,因此必须把控制权交回原始的PluginService
- 要执行特定插件Service的任务,我们必须把这个任务分发到真正要启动的PluginService上去;
- 在一个map中获取实例,并调用对应方法
Service service = mServiceMap.get(serviceInfo.name);
我们还是要按照Service的方式实例化 Service,以保证上下文对象的活力
- 匹配过程
- 预处理:读取插件中的Service组件信息并存储
- private Map<ComponentName, ServiceInfo> mServiceInfoMap = new HashMap<ComponentName, ServiceInfo>();
- 匹配本地的mServiceInfoMap缓存确认目标PluginService
- 预处理:读取插件中的Service组件信息并存储
- 创建以及分发
- private Map<String, Service> mServiceMap = new HashMap<String, Service>();
- 预处理:系统BaseDexClassLoader支持自动加载插件中的Service
- 模仿
ActivityThread类的handleCreateService
- handleCreateService创建出来的Service对象并没有返回, 而是存储在ActivityThread的mServices字段里面, 这里我们手动把它取出来
8.6 ContentProvider
8.6.1 工作原理
- getContentResolver.query
- 去ContextImpl类里面查找的getContentResolver实现,发现这个方法返回的类型是android.app.ContextImpl.ApplicationContentResolver
- resolver.query实际上是调用父类ContentResolver的query实现
- 首先尝试调用抽象方法acquireUnstableProvider拿到一个IContentProvider对象,并尝试调用这个”unstable”对象的query方法
- 万一调用失败(抛出DeadObjectExceptopn,熟悉Binder的应该了解这个异常)说明ContentProvider所在的进程已经死亡,这时候会尝试调用acquireProvider这个抽象方法来获取一个可用的IContentProvide
- 获取ContentProvider并安装
- 【1】首先通过acquireExistingProvider尝试从本进程中获取ContentProvider
- 【2】如果获取不到,那么再请求AMS获取对应ContentProvider;【远程】
- 不论是从哪里获取到的ContentProvider,获取完毕之后会调用installProvider来安装ContentProvider。
ContentProvider是一个数据共享组件,也就是说它不过是一个携带数据的载体而已。为了支持跨进程共享,这个载体是Binder调用,为了共享大量数据,使用了匿名共享内存;
- 【2】如果获取不到,那么再请求AMS获取对应ContentProvider;【远程】
- 使用PackageManagerService的resolveContentProvider根据Uri中提供的auth信息查阅对应的ContentProivoder的信息ProviderInfo。
- 在Android系统启动的时候收集的
- 根据查询到的ContentProvider信息,尝试将这个ContentProvider组件安装到系统上。
- DemoB这个App已经在运行了,那么AMS直接通知DemoB安装ContentProviderAppB(如果B已经安装了那就更好了)
- 其二,DemoB这个app没在运行,那么必须把B进程唤醒,让它干活;
- 启动另外一个进程,当前进程死循环等待;对方进程启动后,响应;
- DemoB进程启动之后会执行ActivityThread类的handleBindApplication方法。 先安装ContentProvider,再调用onCreate
- 使用PackageManagerService的resolveContentProvider根据Uri中提供的auth信息查阅对应的ContentProivoder的信息ProviderInfo。
8.6.2 源码分析
AMS充当一个中间管理员的角色
- 每个进程在启动之后需要把自己应该install的provider, install之后封装PCR进Map,并将PCR的holder告诉AMS,这样后面有其他进程请求这个provider的话,AMS可以告诉你所请求的对端的信息。
- AMS持有Holders, Holder持有Binder
- 客户端请求时,先找自己map中的pcr,有的话则返回PCR里的Binder驱动;没有,找AMS要Holder,并将Holder中的对象封装PCR进Map,返回holder中的驱动
也就是说,客户端总会找自己的map的pcr,但是这个pcr里的驱动可能是来自自己搞出来的,也有可能是AMS给的Holder的
- handleBindApplication
- data.info.makeApplication
- 反射构造Applicaiton
- 调用attach
- installContentProviders(app, data.providers);
- 遍历cpi:ProviderInfos
- installProvider(context, null, cpi, false /noisy/, true /noReleaseNeeded/, true /stable/); 并用返回值填充List<IActivityManager.ContentProviderHolder> results
- // holder为null表示还没有install过 :if (holder == null || holder.provider == null) {
- localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();反射构建实例对象
- provider = localProvider.getIContentProvider();获取Binder驱动
- localProvider.attachInfo(c, info); 上下文和onCreate生命周期
- if (localProvider != null) {
- if (pr != null) { // 不为空代表install过provider = pr.mProvider;
- else ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder);
- ProviderClientRecord pcr = new ProviderClientRecord(auths, provider, localProvider, holder); binder驱动 + 实例对象 封装进入PCR
- PCR对象进入 ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
- localProvider == null
- 还是封装PCR
- 返回 client.holder
- // holder为null表示还没有install过 :if (holder == null || holder.provider == null) {
- ActivityManagerNative.getDefault().publishContentProviders(getApplicationThread(), results); install完成之后,要告诉AMS,传递过去Holders,AMS也有一个MAP
- mInstrumentation.callApplicationOnCreate(app);
- data.info.makeApplication
- ContentResolver.query
- ApplicationContentResolver.query
- 获取驱动:IContentProvider binder = ApplicationContentResolver#acquireUnstableProvider(uri) 或者 acquireProvider(uri)
- 最终:IContentProvider stableProvider = ActivityThread#acquireProvider(Context c, String auth, int userId, boolean stable)
- IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
- ProviderClientRecord pr = mProviderMap.get(key); 从map中获取PCR
- IContentProvider provider = pr.mProvider; 从PCR中获取binderq驱动
- IBinder jBinder = provider.asBinder();
- IActivityManager.ContentProviderHolder holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);
- 如果进程B不存在则先启动进程B并installprovider,告诉AMS之后,由AMS返回给进程A对方的provider信息(此过程中由进程A发起的请求provider的线程会一直等待)
- 如果进程B存在则AMS直接返回给进程A对方的provider信息
- holder = installProvider(c, holder, holder.info,true /noisy/, holder.noReleaseNeeded, stable);相对于上文的过程
- 由于已经有holder对象,不会创建实例localProvider
- localProvider == null 因此仅installProviderAuthoritiesLocked:记录到map中相关binder对象
- 返货Binder驱动 holder.provider
- IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
- 最终:IContentProvider stableProvider = ActivityThread#acquireProvider(Context c, String auth, int userId, boolean stable)
- 驱动操作IContentProvider.query
- 获取驱动:IContentProvider binder = ApplicationContentResolver#acquireUnstableProvider(uri) 或者 acquireProvider(uri)
- ApplicationContentResolver.query
8.6.3 hook方式
- 应用内部
- 让ActivityThread的acquireExistingProvider方法能够返回插件的ContentProvider信息
- App内部自己的ContentProvider信息保存在ActivityThread类的mProviderMap
- 通过反射修改这个成员变量,直接把插件的ContentProvider信息填进去
- 让ActivityThread的acquireExistingProvider方法能够返回插件的ContentProvider信息
- 全局系统:借助代理分发
- 我们在宿主程序里面注册一个货真价实、被系统认可的StubContentProvider组件,把这个组件共享给第三方App;然后通过代理分发技术把第三方App对于插件ContentProvider的请求通过这个StubContentProvider分发给对应的插件
-
- packageParserClass 解析,并反射使用installContentProviders构建
-
- map代理分发
8.7 使用插件中的R资源
8.7.1 具体实现
getResources()获取的就是当前应用的全局Resource对象,然而,插件无论是apk还是so格式的,插件的R.java并没有注册到当前主app的上下文环境
那么我们getResources()所获得全局Resource对象的getXXX(resid)自然就找不到对应的资源路径
- 创建新的Resource对象方式
- 在ResourcesManager的getTopLevelResources方法中创建的
- 获取一个AssetManager实例,使用其“addAssetPath”方法加载APK(里的资源),再使用DisplayMetrics、Configuration、CompatibilityInfo实例一起创建我们想要的Resources实例
- 使用插件的Resources对象,获取资源时,传递的ID必须是离线apk中R文件对应的资源的ID
8.7.1.1 加载离线apk中的layout资源
View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, null);
直接传入当前的context是不行的,因为这是两个不同的上下文对象,当前app的context中是找不到这个插件layout的id的
-
- 创建一个自己的ContextImpl,Override其方法。
-
- 通过反射,直接替换当前context的mResources私有成员变量
8.7.2 Resource分发:处理插件资源与宿主资源的处突
AssetManager的addAssetPath()方法调用native层AssetManager对象的addAssetPath()方法,通过查看c++代码可以知道,该方法可以被调用多次,每次调用都会把对应资源添加起来,而后来添加的在使用资源是会被首先搜索到
- 插件和宿主的id冲突
- 修改aapt,插件和宿主的R资源的生成规则要不同
- 修改aapt,在插件中如果有添加新的资源,则其命名要安装字典排序在原有的资源下递增
- 隔离使用
8.7.3 Resource全局置换:确保插件和宿主使用到的是被修改过的资源
- [方法1]:hook ResourceManager
在Resources中定位到getString(int id)方法,最终寻找资源的调用是有AssetManager来执行的,这个AssetManager是 ResourceManager#getTopLevelResources()创建Resources时来的
需要注意的是:getTopLevelResources()
中具备缓存逻辑
由于ResourceManager是一个单例类,并且持有了当前App的Resource缓存,那么我们直接在App启动时手动替换掉ResourceManager中的Resource缓存,就可以在当前App中添加插件的资源,并且全局有效
- [方法2]:hook 基础组件的生命周期函数
替换掉Activity里Context里的Resources最好要早,基于上面的观察,我们可以在调用Instrumentation的callActivityOnCreate()方法时把Resources替换掉
8.8 热修复
8.8.1 classloader QQ空间
- 将补丁dex加入 ClassLoader的dexfiles的前边
- 如果两个相关联的类在不同的dex中就会报错,但是拆分dex没有报错这是为什么,原来这个校验的前提是:
- 如果引用者(也就是ModuleManager)这个类被打上了CLASS_ISPREVERIFIED标志,那么就会进行dex的校验
- 当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行。
- 虚拟机在启动的时候,会有许多的启动参数,其中一项就是verify选项,当verify选项被打开的时候,上面doVerify变量为true,那么就会执行dvmVerifyClass进行类的校验,如果dvmVerifyClass校验类成功,那么这个类会被打上CLASS_ISPREVERIFIED的标志
- 如果以上方法中引用者直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED
- 防止类被打上CLASS_ISPREVERIFIED标志。最终空间的方案是往所有类的构造函数里面插入了一段代码(可以借助 aop 或者javassist )
- 其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类
- Application作为应用的入口不能插入这段代码
- 因为载入hack.dex的代码是在Application中onCreate中执行的,如果在Application的构造函数里面插入了这段代码,那么就是在hack.dex加载之前就使用该类,该类一次找不到,会被永远的打上找不到的标志
if (ClassVerifier.PREVENT_VERIFY) {
System.out.println(AntilazyLoad.class);
}
存在问题:
- Dalvik 下造成启动耗时长
- Art ,插桩对代码的执行效率并没有什么影响。但是若补丁中的类出现修改类变量或者方法,可能会导致出现内存地址错乱的问题。为了解决这个问题我们需要将修改了变量、方法以及接口的类的父类以及调用这个类的所有类都加入到补丁包中。这可能会带来补丁包大小的急剧增加。
8.8.2 AndFix
-
【生成】
- 有bug的方法在生成的patch的类中的方法都是有注解的
-
java 层的功能就是找到补丁文件,根据补丁中的注解找到将要替换的方法然后交给jni层去处理替换方法的操作
-
在jni中找到要替换方法的Method对象,修改它的一些属性,让它指向新方法的Method对象。
- 我们知道 java 代码里将一个方法声明为 native 方法时,对此函数的调用就会到 native 世界里找
- AndFix原理就是将一个不是native的方法修改成native方法,然后在 native 层进行替换,通过 dvmCallMethod_fnPtr 函数指针来调用 libdvm.so 中的 dvmCallMethod() 来加载替换后的新方法,达到替换方法的目的