Android资源加载流程
Resources持有AssetManager,AssetManager会调用native的AssetManager方法解析资源
通过AssetManager查找资源:编译过的资源通过id访问,未编译的资源通过文件名访问。
Resources和AssetManager中分别包含一个静态变量mSystem
和sSystem
,用于加载/system/framework/framework-res.apk
系统资源
此外还会加载/data/resource-cache/
下的Overlay资源,存储Overlay apk文件路径
AssetManager.Builder.build,会将系统资源和应用资源合并,创建一个新的AssetManager
高版本通过ResourceManager创建Resources
https://blog.51cto.com/shyluo/1229260
插件化加载资源
AssetManager assetManager;
try {
//创建AssetManager
assetManager = AssetManager.class.newInstance();
//反射添加资源路径,加载资源
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.invoke(assetManager, path);
} catch (Exception e) {
throw new SkinException(e);
}
//创建Resource对象
mResources = new Resources(
assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration()
);
插件化加载代码
- 使用DexClassLoader加载dex包
- loadClass加载类
- 反射创建类的实例
- 调用方依赖标准接口,将实例对象强转为标准接口调用
插件化启动没有注册的Activity
原理:
注册StubActivity
使用动态代理创建IActivityManager
代理startActivity方法,修改Intent参数,替换为StubActivity,并保存原来的目标Intent
//Instrumentation.java调用AMS ActivityManagerNative.getDefault().startActivity() //8.0以上替换为getService ActivityManager.getService().startActivity() //10.0以上替换为 ActivityTaskManager.getService().startActivity()
ActivityThread中会创建Activity,并通过Handler创建Activity(可以修改Handler的msg)
反射获取sCurrentActivityThread静态变量,再获取ActivityThread的mH成员变量
自定义Handler.Callback对象替换mH中的mCallback,重写handleMessage,获取msg中的Intent,修改intent为目标Intent
启动AppCompatActivity时PMS.getActivityInfo可能会报错Activity未注册。动态代理PMS的getActivityInfo方法,替换为StubActivity
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
AMS中查找Activity都是通过token,而performLaunchActivity中实例化TargetActivity,已经将token和TargetActivity进行了绑定,从mActivities表中取出ActivityClientRecord调用,因此AMS能够直接操作TargetActivity的生命周期
其他思路
- Fragment:宿主app中有一个Activity作为容器
- Activity代理:宿主app中有一个Activity作为容器,反射创建插件Activity对象,设置到容器中,容器Activity委托调用插件Activity方法
- 修改Element[]数组,替换掉宿主中的Activity
- Hook欺骗AMS
插件化和热修复
插件化和热修复都是通过自定义类加载器实现。
- 热修复是为了修复原有类的缺陷,所以要先加载新的类,替代原有类,类名相同。
- 插件化是为了增加新功能或新资源,所以不需要修改类加载顺序。实现更简单。
另外有的热修复框架还支持通过Diff算法对比新旧版本,生成差分包。
插件化:
1.apk插件化:加载皮肤资源。通过AssetManager
File skinCache = getDownloadedSkinApk(context);
String path = skinCache.exists() ? skinCache.getAbsolutePath() : skinApkPath;
AssetManager assetManager;
try {
assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.invoke(assetManager, path);
} catch (Exception e) {
throw new SkinException(e);
}
mSkinPackage = skinPackage;
mResources = new Resources(
assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration()
);
2.dex插件化(不包含资源):groovy脚本生成dex文件。创建DexClassLoader加载dex文件中的类,传入dex文件路径。
dex如果包含布局,不能用xml,需要用java、kotlin或者anko写布局:
- 启动未注册的Activity。反射hook系统的类避过Activity注册检测。
- 使用Fragment,通过intent的参数反射创建Fragment。
由于Activity或Fragment类在dex文件中,同样需要自定义ClassLoader来加载。插件对外可以提供一个统一的入口类,ClassLoader只需要加载一个类,不需要反射创建其他类