这章是重新梳理插件化技术的整体思路
插件的工程化
插件化技术分为宿主App 和 插件Plugin1这两个 apk,有时候还有 MyPluginLibrary ,宿主和插件都要引用它。
加载插件中的类
宿主App 想要使用 Plugin1 中的类,还使用 宿主App 中的 ClassLoder 是不行的,由此缠身很多种解决方案:
- 最直接的就是,在反射插件中的类时,使用 Plugin1.apk 对应的 ClassLoader (参考第6章)
- 无论是宿主还是插件,他们各自的 ClassLoader 都对应一个 dex 数组,把这些插件的dex数组都合并到宿主的 dex 数组中,那么,宿主App就可以加载任何类(参考9.3节)。
- 自定义一个ClassLoader,取代原先宿主的ClassLoader 。同时在自定义的 ClassLoader 中放一个集合,承载所有插件的 ClassLoader 。那么自定义的 ClassLoader 在加载任何一个类的时候,无论是插件还是宿主的类,都会现在宿主中找,如果没有,再遍历 ClassLoader 集合,看哪个 ClassLoader 能加载这个类(参考9.36节)。
哪些地方可以Hook
关于Hook 的技术可以参考第4章,Hook可以分为三类:
- 在 App 中使用的类,可以Hook。Android 系统源码中被标记为hide的方法和类,我们可以通过反射使用它们,但是不能Hook。还有一些类,比如 Instrumentation 和 Callback ,在App中也能用,因此可以Hook替换。
- 实现了接口的类,可以Hook。虽然大部分类和方法都标记为hide,但是只要合格类实现了一个接口,我们就可以借助 Proxy.newProxyInstance 方法去截获它的方法,比较典型的是 IActivityManager 接口,以及实现这个接口的 AMN 。
- 集合。 没办法Hook一个标记为hide 的类,但是如果Android源码中某个类拥有集合变量的时候,我们可以反射构造出一个对象,然后还是通过反射添加到这个集合中。典型的是,穿件一个 LoadedApk ,把它事先放在 mPackages 缓存中,这样就能直接命中缓存(参考9.2节)。
Activity 的插件化解决方案
从大方向来讲,分为动态替换和静态代理两种:
- 动态替换:这是“占位”思想。宿主App中声明一个用于占位的 StubActivity ,启动插件中的ActivityA,但是告诉AMS启动的是 StubActivity ,欺骗成功之后,在即将启动Activity时,再把 StubActivity 换回 ActivityA(参考第9章)。
- 静态代理。这是一种牵线木偶的思想,在宿主App中设计一个 ProxyActivity ,由他来决定启动插件中的哪个 Activity。插件中的Activity都是没有生命的,得在ProxyActivity 生命周期中,调用插件Activity 的生命周期函数(参考9.5节)。
此外,还需要解决 LaunchMode 问题,解决方案参考 9.5 节。
资源的解决方案
资源主要为Activity 服务。主要有两种解决方案:
- 进入Plugin1 ,则加载Plugin1的资源,反射调用 AssetManager 的 addAssetPath 方法,参数是 Plugin1.apk 的路径。每次进入或者离开插件,都要切换资源。这是一件很繁琐的事情(参考第7章)。
- 事先把 宿主App的资源以及所有插件的资源都通过 AssetManager 的 addAssetPath 方法添加到一个全局变量中。这样,在插件 Activity 的基类中,重写 Activity 的 getResource 方法,从这个全局变量中提取资源。
针对方案2,由于资源合并到一起,就可能发生资源id冲突,由此产生多种解决方案:
- 修改aapt,更改id的前缀(参考15.2节)
- 修改resources.arsc,在 aapt 执行完成后,修改生成的文件(参考21章)
- 通过 public.xml 固定 plugin1 中所有的资源。这种方案不现实,针对固定一个资源的id还是很好的解决方案。
Fragment是哪个门派
Fragment 与Activity 的最大区别,就是后者的一举一动需要和 AMS 交互,而Fragment不用。这种方案整个应用只有一个Activity ,Fragment 可以在 宿主App 中或者 插件中,只要使用合适的 ClassLoader 加载插件中的类,使用合适的 AssetManager 加载插件中的资源,就是一个完美的解决方案。可以参考 16 章。
Service、ContentProvider 和 BroadcastReceiver 插件化通用方案
因为这三者的数量并不多,插件化中也不会动态新增一个组件,所以最简单的方案是:在 宿主App 的 AndroidManifest 文件中事先声明这些组件。缺点是不能动态新增一个组件。参考 8.1节。
特定于Service 的插件化解决方案
如果不事先在宿主App中声明插件的Service,那么Service 也有自己的解决方案:
- 动态代理。也是欺上瞒下的思路。Service 不同于 Activity ,一个StubActivity 可以对应多个插件Activity,但是StubService 和插件Service 只能一一对应,所以应该在 宿主App 中声明多个 StubService。参考第10章。
- 静态代理,牵线木偶思想。创建一个ProxyService,由 ProxyService 来启动插件中的Service,缺点是插件中有几个Service,宿主App 中就要有相同数量的 ProxyService。,参见 14.1和 14.2.
- 结合前两种,能否在 宿主App中只声明一个Stubservice。参考 14.4
特定于BroadcastReceiver 的插件化解决方案
它的插件化解决方案是把静态的Receiver 转换为动态的Receiver。
特定于ContentProvider 的插件化解决方案
占位思想。宿主App 中由 StubContentProvider 来欺骗 AMS,而实际执行的是 插件中的 ContentProvider 。