Android插件化开发 第一篇 [动态加载apk]

引言

随着应用版本的不断迭代,App变得非常庞大而臃肿。新需求接踵而至,旧需求废弃无用而占着空间,工程变得混乱。虽然会采用不同的包名隔离开各个需求模块,但是往往还是觉得如果需求能像插件一样分模块,需要的时候插上去,废弃的时候拔下来就好了。携程开源了其插件化开发框架(携程插件化开发框架),接下来我会学习并从本文开始介绍携程开源的这套插件化开发框架,让更多的人了解安卓的插件化开发。这里感谢携程团队的贡献,文章中如有描述错误或理解错误的地方,也希望大家指点。

这一篇里我们会仿照实现一个简单的插件化开发的例子,这个小例子原理简单的来说就是把一些辅助性的代码(如xxxUtils.java)写在B项目里生成B.apk,把B.apk放到主项目A的assets文件夹中,利用反射的原理让A项目可以调用B.apk中的xxxUtils.java。其实可以更简单的理解为B是一个Android library。这样直接用apk反射调用而不是用library的形式有一个好处就是规避项目编译过程中的dex超过65535方法数Bug,也有可能是为了日后支持例如资源共享,界面跳转之类的,因为传统Android library是不能向宿主获取资源的。另外我们知道反射其实是相对消耗资源的一种调用方式,所以该框架在性能上是否会受到影响我们保留疑问继续学习。

Demo创建

  1. 创建一个工程PluginA,创建一个文件PluginAUtils.java,写上你想要的代码。如:

    public class PluginAUtils {
     public void showToastInfo(Context context) {
         Toast.makeText(context,"This is from pluginA",Toast.LENGTH_SHORT).show();
     }
    }

    编译成功后生成pluginA.apk

  2. 接下来创建工程PluginApp
    在app/src/main/目录下创建asserts文件夹,把刚刚编译好的pluginA.apk放到里面。创建一个AssertsDexLoader.java用来动态加载pluginA工程。
    关键代码如下:

    // 依据不同的系统版本动态加载apk dex文件到ClassLoader中
    private static void installBundleDexs(ClassLoader loader, File dexDir, List<File> files, boolean isHotFix)        throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,InstantiationException,        InvocationTargetException, NoSuchMethodException, IOException {    
     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);
         }
     }
    }

    AssertsDexLoader逻辑为拷贝asserts里的apk文件到系统的/data/data/appPackageDir/里,再把ClassLoader,拷贝的目录及文件三个参数传递给installBundleDexs()即可动态加载其方法至内存。

  3. 在任何需要的地方通过反射调用即可

     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());
     }

    [关键代码可参考这里]
    [Demo可参考这里]

讲解

  • ClassLoader
    ClassLoader顾名思义就是类的加载者,通俗的来说就是把class文件中的类加载到内存里。App启动的时候会创建一个自己的ClassLoader实例,将我们创建的ClassLoader挂载到App的ClassLoader下即可实现类的动态加载。
    [ClassLoader详细介绍]

  • 可能遇到的问题
    App没有添加读写存储权限导致拷贝apk失败
    无法找到插件apk里的类或方法,最大的可能是编译的插件化apk真的不包含该类或方法。推荐反编译插件apk(Android 在线反编译)检查类或方法是否真的被编译进去。(附上Mac的AS编译方法:Build->Build APK 运行结束后在工程文件夹/app/build/outputs/apk/app-debug.apk下找到这个Debug签名的apk)

总结

  • 当插件apk的体积变庞大之后,这种临时加载的过程会变得漫长,不过好在动态加载可以当宿主需要的时候才加载到内存中。
  • 反射调用也必须按照约定的命名规范去调用,否则容易导致类或方法无法找到。
  • 而且Dex的加载与系统版本依赖严重,可能会导致新版SDK不支持等问题。

由此可见此方法适合轻量级的插件化开发。



文/H3c(简书作者)
原文链接:http://www.jianshu.com/p/01a91a9b7a19
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值