Android插件化
减少安装包大小,实现app功能模块化动态扩展
发展历程
需解决三个问题
插件中代码的加载和与主工程的互相调用
插件中资源的加载和与主工程的互相访问
四大组件生命周期的管理
框架发展的三代
第一代
dynamic-load-apk:使用ProxyActivity静态代理技术由ProxyActivity去控制插件中PluginActivity的生命周期
缺点:插件中的activity必须继承PluginActivity,开发时要小心处理context,开发侵入性高
第二代
在manifest中预埋一些组件实现对四大组件的插件化。为了实现插件开发的低侵入性和稳定性,实现原理选择尽量少的hook
第三代
VirtualApp:能够实现app的免安装裕兴
动态加载apk简单案例
简单实现
//创建名为Plugin工程,并创建PluginAUtils.java文件如下,然后编译成Plugin.apk
public class PluginAUtils {
public void showToastInfo(Context context) {
Toast.makeText(context,"This is from pluginA",Toast.LENGTH_SHORT).show();
}}
//创建名为PluginApp工程,在app/src/main/目录下创建asserts文件夹,并放入上述Plugin.apk文件
//创建AssertsDexLoader.java,用于动态加载Plugin工程
private static void installBundleDexs(ClassLoader loader, File dexDir, List<File> files, boolean isHotFix) throws Exception {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT >= 23) {
V23.install(loader, files, dexDir,isHotFix);
} else if (Build.VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir,isHotFix);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(loader, files, dexDir,isHotFix);
} else {
V4.install(loader, files,isHotFix);
}}}
//通过反射,调用Plugin工程中的方法
private void runAssertsDexMethod() throws Exception {
Class<?> clazz = Class.forName("h3c.plugina.PluginAUtils");
//Class<?> clazz = getClassLoader().loadClass("h3c.plugina.PluginAUtils");
Constructor<?> constructor = clazz.getConstructor();
Object bundleUtils = constructor.newInstance();
Method printSumMethod = clazz.getMethod("showToastInfo", Context.class);
printSumMethod.setAccessible(true);
printSumMethod.invoke(bundleUtils, getApplicationContext());
}
ClassLoader
类加载器,负责将class文件中的类加载到内存中
App启动时会创建一个ClassLoader实例,将我们创建的ClassLoader挂载到App的ClassLoader下即可实现类的动态加载
AssertsDexLoader
拷贝asserts中的apk文件到系统的/data/data/appPackageDir/;再把ClassLoader拷贝的目录及文件三个参数传递给installBundleDexs()即可动态加载其方法至内存
小结
App拷贝需要读写权限
如无法找到插件APK中的类或方法,可能是插件不含对应的类或方法,或者没有将其编译到插件APK中
插件APK体积越大,加载时间越长,动态加载可在宿主需要时再加载到内存
反射调用必须按照约定的命名规范,否则会导致累或方法无法找到
Dex的加载严重依赖系统版本,需对不同版本做兼容