Small是一个轻量化的插件化框架,Small的使用介绍可以查看官网文档)了解,Small项目接入总结可以查看博客的另一篇《Small框架实践总结》。这篇文章主要从以下几个角度来看Small框架的实现原理。
- Small如何实现插件代码打包和资源打包
- Small加载插件代码和资源的原理
- Small代理插件activity生命周期的原理
关于Small插件化的其他文档:
《Android插件化之Small框架实践总结》
《Android插件化之资源加载机制》
《Android插件化之从入门到放弃》
0x01 Small如何打包module代码?
small插件化中的三种组件角色,分别是app.*
,lib.*
,宿主
。small在打包过程中会根据不同module的类型针对不同组件使用不同编译插件做处理,分别是:
AppPlugin
–>app.*
LibraryPlugin
–>lib.*
HostPlugin
–>app
AssetPlugin
–>其他
在处理模块依赖上,首先区分gradle的两种依赖方式:
- Compile: compile是对所有的build type以及flavors都会参与编译并且打包到最终的apk文件中。
- Provided: Provided是对所有的build type以及flavors只在编译时使用,只参与编译,不打包到最终apk。
在打包app.*
插件时,将app.*
对其他module的依赖转换成provided
依赖。当执行插件打包时可以看做是插件模块执行assembleRelease
。
在打包lib.*
插件时,LibraryPlugin
会修改插件模块的build.gradle
文件,apply plugin:
从'com.android.library'
修改成'com.android.application'
,将对lib的方式转换成app的打包,再执行assembleRelease
任务。
0x02 Small如何解决资源id冲突?
Android App资源id的格式是0xPPTTNNNN,其中:
- PP 资源的package id
- TT 资源类型的id,类型是attr、layout、string等等
- NNNN 资源的entry id
Android App资源的默认packageId是0x7f,当同时加载多个插件apk时。必然会有插件间资源id冲突的情况。业界解决资源id冲突主要通过package id的分段,实现方式一般有两种方式,第一种方式是修改aapt工具源码,让aapt针对插件打包时每个插件的packageId都不相同,重新编译出aapt,而达到解决资源id冲突的问题。small框架使用的是另外一种方式,那就是读取并重写resources.arsc文件。大致的处理流程如下(出自small原作者在github issues的回答):
- 编译完整的资源包,利用symbol/R.txt搜集完整包entries的资源信息
- 搜集当前插件包res目录的entry信息
- 根据(2),通过重新排序分配各个entry的资源id,并结合(1),形成旧id到新id的映射
- 解析(1)生成的resources.arsc文件,利用(3)进行过滤输出
0x03 Small如何加载插件代码?
Small的加载插件代码的方式基于android dex分包方案的,简单的说small通过将多个dex文件塞入到app的classloader中,达到加载插件代码的目的。Small并不会更换系统的classloader,所以这样在面对多种不同机型时可能就不会遇到的兼容性问题。
怎样把多个dex文件塞入系统classloader中,可以看下QQ空间的热修复方案
0x04 Small如何加载插件资源?
Android资源是由 AssetManager 加载的。应用启动时系统创建一个AssetManager实例,并通过addAssetPath
方法添加资源路径,默认添加:
- “/framework/base.apk” - Android base resources (base)
- “/data/app/*.apk” - The launching apk resources (host)
那么如何让插件的资源能够被系统加载?Small的方式是自己创建的AssetManager,调用AssetManger的addAssetPath
方法添加插件资源路径,再将系统创建的AssetManager替换掉。
Hook AssetManager路径如下:
1 | ActivityThread |
0x05 Small如何代理插件Activity的生命周期?
先看Andriod中Activity是如何启动的。
1 | MyActivity.startActivity() |
通过 Activity 的启动流程可以发现,Activity是由 ActivityThread 和 Instrumentation 启动的,Instrumentation有关Activity启动相关的方法大概有:execStartActivity、newActivity等等。Small是这样操作的:
1 | MyActivity.startActivity() |
Small首先在宿主manifest中注册一个命名特殊的占坑activity来欺骗系统获取生命周期,在封装一个Instrumentation替换掉宿主的,系统启动的是A这个activity,但classloader实际加载的代码却是 MainActivity.class 这个类。
1 | <!-- Stub Activities --> |
伪代码如下:
1 | ActivityThread thread = currentActivityThread(); |