Replugin插件化框架核心原理

插件化技术随着360公司2016年DroidPlugin、2017年RePlugin的相继公布和开源,达到了顶峰。随后这几年进入了普及和落地期,到今天已不再新鲜和热门。但对于以插件化框架为基础架构进行业务开发的同学而言,熟悉其原理和具体实现,不仅是工作本身需要,也能增进Android内功的修炼。

相信了解过Replugin的同学都知道,Replugin的最大特点是坑位和唯一Hook点。

那么问题来了,什么是坑位,唯一Hook点又Hook的是什么?为什么凭借着这两招Replugin就能够做到Android原生代码的动态加载?

带着这些疑问,让我们先来梳理下系统原生Avtivity的跳转全流程,以android8.0的代码为例,大体流程如下:
Replugin插件化框架核心原理_第1张图片
从图中可以看到在应用内ActivityA启动AcitivtyB的话,有如下关键内容:

  1. 不管是Activity内的方法还是Context内的方法,最终都是通过Instrumentation这个APP内全局唯一的类来进行的。
  2. ActivityThread是APP中UI线程的具象,它与AMS相互持有彼此的远端代理类,搭建了基于Binder的双向通信桥梁。
  3. Intent中通过ComponentInfo携带了目标ActivityB的全路径描述符、APP包名等信息,AMS在执行startActivity时首先会征询PMS,利用Intent信息换回一个ActivityInfo;如果AcitivtyB没有在Manifest中注册,那么PMS就会查询失败返回一个空ActivityInfo对象,PMS一看是null则会直接返回错误,于是在Instrumentation的execStartActivity方法中就会抛出Activity未注册的异常。
  4. 如果上述第3步顺利通过,则AMS会通知APP端paused掉当前的ActivityA;AMS在收到APP端发来的ActivityA正常paused的消息后,则会开始准备启动ActivityB。
  5. AMS端会创建一个ActivityRecord实例来记录这个新Activity,ActivityThread会创建一个ActivityClientRecord来与之对应。ActivityB实例的创建最终是通过Instrumentation中的方法完成的。

从这个过程中可以知道:

  1. Activity没有在Manifest中注册的话,是无法被正常启动的;必须要注册。
  2. ActivityB实例的创建是APP端ActivityThread的handleLaunchActivity方法负责的。

接下来,我们再看一下APP的启动过程,了解一下应用外Acitivity的跳转全流程:
Replugin插件化框架核心原理_第2张图片
与应用内跳转不同的是,这里需要先确保目标APP的进程已经被创建,在目标进程被创建之后,则同样会通过目标APP的ActivityThread的handleLaunchActivity方法去创建目标ActivityB的实例。

综上两张图,我们发现了两个非常重要的方法:
handleBindApplication、handleLaunchActivity

接下来看一下各自的实现,首先是handleBindApplication,它负责APP的Application实例的创建。
Replugin插件化框架核心原理_第3张图片
从图中可以看到该方法先后创建了APP的packageInfo、Instrumentation、ClassLoader、Context、以及最终的Application对象。并且在最后阶段调用了Application对象的attach方法,即attachBaseContext方法,这个方法是一个APP中开发者能够接触到的最早的一个系统回调方法。

再来看一下handleLaunchActivity方法的实现:
Replugin插件化框架核心原理_第4张图片
这个方法相比之下平淡不少,最主要逻辑就是使用上文中系统为APP创建的ClassLoader去实例化一个目标ActivityB的对象,然后赋值给ActivityThread中的ActivityClientRecord对象。

下面从数据的角度来看一下Activity、ActivityThread、AMS之间的关系:
Replugin插件化框架核心原理_第5张图片
最深的印象是AMS并没有直接持有目标Activity对象,它是通过一个跨进程的token对象来与APP进行数据对照的。从AMS端来看,它管理的页面对象是ActivityRecord。

讲到这里,真是由衷赞叹Replugin框架的牛逼和精妙。对于这些散点的知识和流程,它是怎么想出来坑位和Hook APP的ClassLoader了呢?有机会一定讨教下心法。

回答一下刚才抛出的问题:

  1. 什么是坑位?
    坑位就是只存在于Manifest文件中的四大组件的类文件描述符,工程中并没有相应的实体类对应。坑位的存在纯粹就是为了骗过PMS,让PMS能够“查有此人”。

  2. 唯一Hook点Hook的是什么?
    唯一Hook点Hook的是系统为APP创建的ClassLoader,它是PathClassLoader类型的。因为一个APP,它的所有类都会经由自身的ClassLoader加载,Hook了它就可以接管所有类的创建。

下面这张图给出了Replugin是如何Hook APP的classLoader的:
Replugin插件化框架核心原理_第6张图片
Replugin在APP的Application的方法attachBaseContext中,通过Java反射的方式,找到packageInfo对象中的classLoader成员变量,然后替换为Replugin自行创建的一个PathClassLoader实例。
为什么替换了这里的成员变量,就可以做到Hook APP整个的ClassLoader了呢?因为APP中其他地方对ClassLoader的引用都是出自此处。

最后,看一下Replugin框架中插件Activity的跳转全流程:
Replugin插件化框架核心原理_第7张图片这里要说明一点的是,Replugin为了尽量少地Hook系统API,插件框架是没有对Activity、Context甚至Instrumentation中的startActivity方法进行Hook的,为了能走坑位的逻辑,业务方需要调用框架中Replugin的startActivity方法。

以上就是Replugin中最精华的内容,后续文章中将深入介绍框架内部的具体实现,包括内置插件、插件升级等知识点。敬请期待。

你可能感兴趣的:(Android)