自学插件化有一段时间了,这篇博客算是对前几个月学习的“开花结果”。先给自己鼓个掌!其实这段时间是真累,白天工作,晚上九点下班,到家再学习插件化,早上早起学习插件化,真的是累的不行,但是还是比较充实的。好了,步入正题:基于静态代理的插件化
前言
说到插件化,我是被那个斗地主吸引了,不知道各位有没有玩过这个游戏,在游戏大厅中:
想玩斗地主的话,就点击下载,然后想玩麻将的话,就点击下载麻将,下载加载完毕之后,就可以愉快的玩游戏了。 这里用到的就是插件化原理,在我们需要的时候再下载 插件。这里的游戏大厅就相当于一个:宿主APP;而斗地主+麻将等小游戏就是:插件。 这些插件都是在用户需要用的时候下载加载的。 这样的话 我们的宿主APK包就可以小很多。
在实现插件化的过程中,我们的插件APP是可以单独运行的,也就是说,既可以作为插件,也可以作为APP。看下图:
讲解之前,我们再熟悉一个东西:我们是不是看过皮影戏? 没看过,电视上总看过吧?! 每个皮影人物是不是都有人在背后操作,我们看到的的皮影,而实际上真正的幕后老板是:皮影手!
不熟悉代理模式的请转:Android 插件化开发——基础底层知识(代理模式)
静态代理实现插件化原理
我们知道,实际上我们加载外部的插件APK,启动插件中的Activity,面临的第一个问题就是:AMS检查,要多过AMS的检查,我们可以做到:Hook 底层,但是就算“”躲过“了AMS的检查,onPause, onResume等方法也不会执行,因为这些方法都是跟AMS直接交互的,有AMS控制这些方法的执行时机的,在宿主APP中,都不把它当做Activity看待,所以也就是:躲得了初一躲不了十五。
这样的话 ,我们可以能不能找个真正的Activity代替插件中的“假”Activity呢? 答案是可以的! 我们创建一个StubActivity作为占位Activity,然后通过StubActivity实际控制“假”Activity的生命周期执行。看下图:
在上图中我们可以看出,我们看到是“假”Activity在执行一系列生命周期 ,但是那都是假象,实际上背后是有:StubActivity在操控的。那么问题来了:StubActivity是怎么操控呢?通过什么操控呢?答案是:面向接口编程。
我们可以创建一个IPluginActivity接口,里面包含定义了所有Activity.java的要执行的方法, 而我们的插件中的“假”Activity都要实现这个接口,之后我们在StubActivity通过ClassLoader对象加载这个“假”Activity,也就是IPluginActivity对象 ,通过StubActivity持有的这个IPluginActivity对象操控:“假”Activity。 看下图:
注意:静态代理实现插件化,真实的本质是:StubActivity和StubActivity之间的跳转!
Github地址:
在开始之前,先附上GitHub项目地址:
在开始之前,先附上GitHub项目地址: 传送门
功能点:
- 实现宿主到插件跳转
- 支持同一插件之间跳转
- 支持加载不同的插件
- 支持不同插件之间跳转
- 支持对插件的启动模式、(注意是:插件内启动模式)
接下来会根据这些功能点进行详细的实现讲解
方案实施
在在此奉上 = - =: 传送门
1:先看下目录结构:
app也就是宿主, pluginmodel和pluginmodule2都是插件,而基类BaseLibrary是三个都需要依赖的。
2:重要类讲解:
我们看下比较关键的PluginManager类:
/**
* 加载插件APK
* @param apkPath APK或者jar或者dex的目录
*/
public void loadPluginApk(String apkPath) {
//Dex优化后的缓存目录
File odexFile = context.getDir("odex", Context.MODE_PRIVATE);
//创建DexClassLoader加载器
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, odexFile.getAbsolutePath(), null, context.getClassLoader());
//创建AssetManager,然后创建Resources
Resources resources = null;
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.invoke(assetManager, apkPath);
resources = new Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
//存储下不同插件(插件路径,该插件的ClassLoader,该插件下的资源)
pluginItemHashMap.put(apkPath, new PluginItem(apkPath, dexClassLoader, resources));
}
在占位StubActivity中,我们需要个根据不同的插件,选择不同的ClassLoader以及Resource。
@Override
public ClassLoader getClassLoader() {
if(getIntent() == null) {
return super.getClassLoader();
}
return stubActivityImp.getClassLoader() == null ? super.getClassLoader() : stubActivityImp.getClassLoader();
}
@Override
public Resources getResources() {
if(getIntent() == null) {
return super.getResources();
}
return stubActivityImp.getResources() == null ? super.getResources() : stubActivityImp.getResources();
}
3:实现宿主到插件跳转
传递DEX_PATH:作为StubActivity选择对应ClassLoader的标记
传递REALLY_ACTIVITY_NAME : 是为了让第一步选中的ClassLoader“假”的Activity。
传递LAUNCH_MDOEL: “假”Activity的启动时模式。
接下来从对应的ClassLoader中加载出“真实”的Activity:
可以看出我们得到了IPluginActivity的对象:iPluginActivity,根据这个接口调用“假”Activity对应的相关方法。可以看成是:面向接口编程。
4:支持加载不同的插件
要支持加载不同的插件,原理就是:我们把加载的插件保存起来,然后在StubActivity中具体选择要使用哪一个ClassLoader或者Resources
在Pluginmanager类的loadPluginApk方法中:
然后在StubActivityImpl中具体取出:
这样的话,就完成了加载不同插件的类。
5:LanchModel的支持
回想一下:其实我们在插件之间跳转的时候,本质就是:宿主到StubActivity,或者StubActivity到StubActivity。这样的话,我们就不能再Manifest中具体执行StubActivity的启动模式了。
如果我们想要支持LaunchModel呢? 回想一下:你们面试的时候是不是 被问到或者问过一个问题:“怎样安全退出APP中所有的Activity?”。我相信你们肯定回答过:“在基类的BaseActivity中保存已经启动过的Activity,然后在退出的时候,依次销毁这些Activity。”
对我们这里就是这个思路:我们每次打开一个插件Activity,都会根据要打开的“假”Activity的启动模式,做对应的操作,也就是用集合存起来,然后根据启动模式做具体的操作。也可以说成是:自定义Android的启动模式。
Demo中的ActivityStackManager类就是完成上述功能,更类内部 维护了一个List集合,保存已经打开的插件Activity信息。然后在每次启动新的插件Activity之前,判断LaunchModel:
然后处理StubActivity的返回键:
5:Demo运行步骤
这个的细看啊!!!
- 首先 clone Demo
- 分别打包插件1和插件2
- 将插件1APK和插件2APK放入/sdcard/下
- 运行宿主APP程序到模拟器(手机)
- 最后要打开手机的权限(读写权限!!!)
重要事情说三遍:记得打开权限!!!!
运行结果:
本篇博客的知识点就讲解完了,希望感兴趣的小伙伴,可以clone Demo,然后对照Demo具体看博客。
注:看本篇博客比较吃力的童鞋,建议先看下:
手写Android热修复
Android中的类加载器——ClassLoader
在此感谢:
- 包建强的《Android插件化开发指南》
- 任玉刚的DL框架