插件化是什么?
对大型APP,需要动态更新模块功能。我们把宿主apk拆分成多个子apk,下发给宿主app来动态加载,这个过程叫做插件化。
我们可以通过ClassLoader类加载机制加载插件。 但是在Android系统,对Activity、Service、广播、contentProvider这些组件是由系统服务管理的。也就是说,数据(DEX)准备好之后,还需要系统服务去加载这个数据(DEX文件)。
我们要用到Hook技术(反射、动态代理、静态代理)和Classloader机制,而且要熟悉AMS、PMS和四大组件交互过程。
加载插件的几种方法?
我们以加载插件中一个activity为例,来分析插件化实现过程。
加载插件中的字节码
合并插件到宿主的PathClassLoader中
1. 使用自定义的DexClassLoader我们就能加载插件dex中任意.class类,用反射技术就能操作得到的插件Class了。
2. 反射BaseDexClassLoader,修改其中“dexElements", 利用插件dex,反射实例化相应到element,把它插入到dexElements数组中,至此以后可以默认使用宿主的classloader加载所有的插件。有个缺点是类库可能会冲突。
ClassLoader加载插件中的Activity
上面通过自定义ClassLoader加载一个class文件到JVM中。我们还需要用Hook技术修改AMS的客户端,达到欺上瞒下的目的。
在启动插件activity启动过程中有两个主体
一个是App进程,一个是AMS。 AMS系统服务,四大组件的大领导,所以你要启动一个activity,你要向领导提前打报告。
上图是启动插件acitivty流程,
1. hook的第一个点:修改Request信息。ActivityManagerProxy,把startActivity(Intent)信息替换成占位activity(StubActivity), StubActivity是宿主AndroidManifest文件中注册的,开机启动时,PMS安装宿主apk的时候,PMS会解析apk信息,并把信息共享给AMS,所以AMS是认识它的。我们把ActivityManagerProxy动态代理成MyActivityManagerProxy,把RealIntent替换stubIntent,欺骗AMS。
2. hook的第二个点,修改响应信息(handler中)。等AMS校验Intent信息结束后,通知宿主启动的时候,我们可以hookActivityThread中的handler的callback接口,把原来的realIntent还原出来。同时要注意在这里我们动态加载插件的activity字节码了,这里两种方案加载插件,第一种就是上文提到的也可以一次性把所有插件dex合并到宿主的baseDexclassloader中,第二种是我们可以修改Loadedapk中classloader,为了隔离,我们采用第二种办法修改loadedAPK类,插件都使用自己的classloader。
加载插件的资源文件,如何解决资源冲突。
加载资源,我们通过getResource().getDrawable(resid),得到资源,其实底层最终都是调用getAssetManager,根据资源id,去resource.arce这个map里面加载映射好的资源文件。
我们可以hook AssetManager的addAssetPath方法,添加插件的路径,那么assetManager会返回插件的资源。
两种加载插件资源的方案
第一种方案是,一次性把插件的资源一次性通过hook assetManager.addAssetPath(pluginPath)合并到宿主的AssetManager里。
第二方案是,每次进入插件的Activity,才去替换AssetManager的assetPath为插件的路径,退出插件的界面时,再把AssetManager的assetPath还原成宿主的assetPath。
对于第一种方案要解决资源id冲突,常见的办法是编译插件的时候,修改aapt工具,把插件资源的packageID重命名,这样就不会与宿主产生冲突。