写在前面
目的
Android插件化有非常多的实现方案,本文中列举的,也并不是最优解或普遍方案,旨在是在对四大组件的一些探索中,总结出在插件化开发过程中,有哪些需要注意的地方,需要解决的问题。
计划
此篇是介绍插件化的一些基础,预计是写完三篇
- 插件化入门(1) -基础
- 插件化入门(2) -欺上瞒下的方案 链接
- 插件化入门(3) -代理式插件化 todo
什么是插件化?
仅安装宿主APK,无需单独安装插件apk,即可运行,无需升级宿主应用
-
减少app的更新频率
-
降低模块耦合
-
按需加载,节省流量
如何加载没有安装的插件apk
一个APK有哪些东西是需要加载并运行的
-
AndroidManifest.xml android描述文件
-
classes.dex Android平台上的可执行文件,由java字节码编译而来
-
META-INF 签名文件夹
-
res 资源文件夹
-
resources.arsc 资源目录信息
-
lib so库
实际需要运行的为dex,即java代码,以及资源文件res,以及so库
加载代码dex(java代码)
要加载dex,需要用到classloader Android有两个类加载器DexClassLoader和PathClassLoader
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
//android8.0前
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
//android8.0之后
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
实际上在Android8.0后使用DexClassLoader和PathClassLoader是没有区别的 而在8.0 之前的区别是ptmizedDirectory 会影响dex优化后.odex的存放目录 关于PathClassLoader和DexClassLoader的区别,可以参考这篇文章的分析
所以加载外部dex代码可以直接如下
PathClassLoader pathClassLoader = new PathClassLoader(apkFile.getAbsolutePath(), null, context.getClassLoader().getParent());
Class<? clz = pathClassLoader.loadClass("com.dennisce.testplugin.PluginActivity");
加载资源res
实际管理资源的东西
在开发过程中,使用资源文件,主要使用R来访问资源文件,如下
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getResources().getColor(R.color.colorAccent);
getResources().getDrawable(R.drawable.ic_launcher_background);
}
先看下这些方法最后都做了什么?
- setContentView
根据调用可以看到,最后是调用到了Resource这个类 - getResources.getxxxx() 也是使用Resource类获取资源
Resource.getColor()的调用逻辑
结论:资源是由Resources及AssetManager来管理的
怎么加载外部的资源
在AssetManager中可以找到以下方法
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
可以添加一组资源文件到AssetManager,APK本身就是一个zip文件,所以可以用这个方法 注意点:
- 此方法被标记hide,需要反射调用
- 在Android9.0此方法已被废弃,替代方法为setApkAssets
- 在Android master代码中,此方法已标记UnsupportedAppUsage,但setApkAssets方法仍可使用 这里不考虑兼容性,以addAssetPath 为例
AssetManager assetManager = null;
try {
assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = context.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
通过以上代码即可获得可以访问插件资源的AssetManager及Resources
加载 so
使用so文件的方法
| 方法名 | 解释 |
|---|---|
| System.load(filename) | filename为so文件的绝对路径,如 /data/user/0/com.dennisce.pluginstudydemo/cache/plugin/libnative-lib.so |
| System.loadLibrary(libname) | libname为jniLibs文件夹下的lib名,如native-lib |
加载插件so的方式
- System.loadLibrary 之前加载dex的时候,提到PathClassLoader的构造方法
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath,null, librarySearchPath, parent);
}
librarySearchPath参数直接传的是null,其实这里的librarySearchPath即是so的搜索路径 所以只需要做以下几个步骤就可以了
- 解压插件APK中的so文件
- 将so文件以逗号隔开生成String
- 在创建PathClassLoader的时候,将该String作为librarySearchPath传入
- System.load
- 插件代码应有个入口调用System.load(filename)
- 解压插件APK so文件到插件System.load(filename) 的filename路径
解压so需要注意的问题
生成插件APK的时候,so的目录会根据gpu的不同来生成,结果如下
代码
写在最后
由于本人水平有限,如有错误,欢迎交流指出