插件化技术随着360公司2016年DroidPlugin、2017年RePlugin的相继公布和开源,达到了顶峰。随后这几年进入了普及和落地期,到今天已不再新鲜和热门。但对于以插件化框架为基础架构进行业务开发的同学而言,熟悉其原理和具体实现,不仅是工作本身需要,也能增进Android内功的修炼。
相信了解过Replugin的同学都知道,Replugin的最大特点是坑位和唯一Hook点。
那么问题来了,什么是坑位,唯一Hook点又Hook的是什么?为什么凭借着这两招Replugin就能够做到Android原生代码的动态加载?
带着这些疑问,让我们先来梳理下系统原生Avtivity的跳转全流程,以android8.0的代码为例,大体流程如下:
从图中可以看到在应用内ActivityA启动AcitivtyB的话,有如下关键内容:
从这个过程中可以知道:
接下来,我们再看一下APP的启动过程,了解一下应用外Acitivity的跳转全流程:
与应用内跳转不同的是,这里需要先确保目标APP的进程已经被创建,在目标进程被创建之后,则同样会通过目标APP的ActivityThread的handleLaunchActivity方法去创建目标ActivityB的实例。
综上两张图,我们发现了两个非常重要的方法:
handleBindApplication、handleLaunchActivity
接下来看一下各自的实现,首先是handleBindApplication,它负责APP的Application实例的创建。
从图中可以看到该方法先后创建了APP的packageInfo、Instrumentation、ClassLoader、Context、以及最终的Application对象。并且在最后阶段调用了Application对象的attach方法,即attachBaseContext方法,这个方法是一个APP中开发者能够接触到的最早的一个系统回调方法。
再来看一下handleLaunchActivity方法的实现:
这个方法相比之下平淡不少,最主要逻辑就是使用上文中系统为APP创建的ClassLoader去实例化一个目标ActivityB的对象,然后赋值给ActivityThread中的ActivityClientRecord对象。
下面从数据的角度来看一下Activity、ActivityThread、AMS之间的关系:
最深的印象是AMS并没有直接持有目标Activity对象,它是通过一个跨进程的token对象来与APP进行数据对照的。从AMS端来看,它管理的页面对象是ActivityRecord。
讲到这里,真是由衷赞叹Replugin框架的牛逼和精妙。对于这些散点的知识和流程,它是怎么想出来坑位和Hook APP的ClassLoader了呢?有机会一定讨教下心法。
回答一下刚才抛出的问题:
什么是坑位?
坑位就是只存在于Manifest文件中的四大组件的类文件描述符,工程中并没有相应的实体类对应。坑位的存在纯粹就是为了骗过PMS,让PMS能够“查有此人”。
唯一Hook点Hook的是什么?
唯一Hook点Hook的是系统为APP创建的ClassLoader,它是PathClassLoader类型的。因为一个APP,它的所有类都会经由自身的ClassLoader加载,Hook了它就可以接管所有类的创建。
下面这张图给出了Replugin是如何Hook APP的classLoader的:
Replugin在APP的Application的方法attachBaseContext中,通过Java反射的方式,找到packageInfo对象中的classLoader成员变量,然后替换为Replugin自行创建的一个PathClassLoader实例。
为什么替换了这里的成员变量,就可以做到Hook APP整个的ClassLoader了呢?因为APP中其他地方对ClassLoader的引用都是出自此处。
最后,看一下Replugin框架中插件Activity的跳转全流程:
这里要说明一点的是,Replugin为了尽量少地Hook系统API,插件框架是没有对Activity、Context甚至Instrumentation中的startActivity方法进行Hook的,为了能走坑位的逻辑,业务方需要调用框架中Replugin的startActivity方法。
以上就是Replugin中最精华的内容,后续文章中将深入介绍框架内部的具体实现,包括内置插件、插件升级等知识点。敬请期待。