良心博客滴滴开源框架VirtualAPK插件化介绍加教程加DEMO加投入项目

公司要实现一个这样的效果,类似于qq游戏大厅,我们可以下载斗地主,可以下载保皇,下载完成直接就可以玩,不需要安装,也就是说我们的这一款软件里面可以装载多款软件.


公司项目是一套系统管理软件,它里面包括了五款软件,用户可以选择付费选择购买这其中的任一款软件,需求是说为了用户体验好,不让用户购买一款软件就要在桌面上安装一个软件(一共五款,想想用户界面会不会特别乱).


想了想总结一下需要解决以下几个问题:

1.应用占用内存大小,不能让应用占手机内存特别大(五款系统,资源文件不能都放在一个应用里面,那样下载大小太大了~)

2.手机桌面看不到下载的五款应用图标,就像QQ游戏一样,只能看到一个主应用的图标

3.应用之间数据互通(五个应用之间不用使用一个软件就去重新登录,重新获取数据,一方面浪费流量,一方面用户体验也不好)


于是乎开始花了一上午的时间在网上查了一下,得到了三种解决办法 分别如下:

1.插件化,把一款应用分成多个插件, 就是说做一个宿主的主应用,然后里面的五款软件分别拆分成五个插件,当用户购买应用下载时候,其实是把插件下载下来了,直接调用进入就行,

2.设置AndroidManifest.xml里面的主activity 这一行里面的LAUNCHER设置成DEFAULT,也有说直接删除的,就可以让他在桌面上不存在图标了,等等关于他的一系列说法,本人亲测一切都没有用,对,,,没有用  随着安卓版本的更新,这种方法早已经被淘汰了

3.热更新热修复,各大互联网公司基本都有自己的技术,可以很快集成到自己的项目,但是呢~~必须把自己的代码挂到人家那上面 发布版本也是要把代码往人家上面放(..想想...自己代码都给人家了..自己还玩个P啊对吧...)


所以只剩下一个解决办法了  就是使用插件化 恩...下面开始介绍本人插件化的坎坷之路

首先我选用的是滴滴开源的VirtualAPK框架,下面是关于他跟其他插件化框架的不同之处


 
  
特性 DynamicLoadApk DynamicAPK Small DroidPlugin VirtualAPK
支持四大组件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持
组件无需在宿主manifest中预注册 ×
插件可以依赖宿主 ×
支持PendingIntent × × ×
Android特性支持 大部分 大部分 大部分 几乎全部 几乎全部
兼容性适配 一般 一般 中等
插件构建 部署aapt Gradle插件 Gradle插件

可以看到真的很牛逼的样子 

接下来是,集成到项目,开始写代码

一定注意文中所提到的版本 以及gradle版本号  严格规定,只能用这个版本!

 
  

环境准备

  • Gradle版本需要为2.14.1,可以使用gradle -v查看环境中配置的Gradle版本号。也可以使用工程中gradlew来编译,可以在gradle/wrapper/gradle-wrapper.properties中更改版本号
    distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
  • com.android.tools.build的版本号为2.1.3

宿主工程接入

Host宿主工程接入需要以下6步

  1. 在宿主工程根目录的build.gradle添加依赖
    dependencies {
     classpath 'com.didi.virtualapk:gradle:0.9.0'
    }
  2. 在App的工程模块的build.gradle添加使用gradle插件
    apply plugin: 'com.didi.virtualapk.host'
  3. 添加VirtualAPK SDK compile依赖
    dependencies {
     compile 'com.didi.virtualapk:core:0.9.0'
    }
  4. MyApplication类是继承了Application,覆写attachBaseContext函数,进行插件SDK初始化工作
    @Override
    protected void attachBaseContext(Context base)  {
       super.attachBaseContext(base);
       PluginManager.getInstance(base).init();
    }
  5. 在使用插件之前加载插件,可以根据具体业务场景选择合适时机加载,我是在MainActivity的onCreate时机加载
    protected void onCreate(Bundle savedInstanceState) {
           // 加载plugin.apk插件包
         PluginManager pluginManager = PluginManager.getInstance(this);
         File apk = new File(getExternalStorageDirectory(), "plugin.apk");
         if (apk.exists()) {
             try {
                 pluginManager.loadPlugin(apk);
             } catch (Exception e) {
                 e.printStackTrace();
             }
         } 
     }

经过上述6步后,VirtualAPK插件功能就集成到宿主中了,宿主打包和运行方式没有任何改变。接下来看下插件工程如何集成和构建的。

插件工程接入

ImageBrowser插件工程接入分为3步:

  1. ImageBrowser工程根目录的build.gradle添加依赖
    dependencies {
     classpath 'com.didi.virtualapk:gradle:0.9.0'
    }
  2. 在App的工程模块的build.gradle添加使用gradle插件和插件配置信息,信息需要放在文件最下面

    apply plugin: 'com.didi.virtualapk.plugin'
    ...
    ...
    // 插件配置信息,放在文件最下面
    virtualApk {
     packageId = 0x6f             // 插件资源id,避免资源id冲突
     targetHost='../host/app'      // 宿主工程的路径
     applyHostMapping = true      // 插件编译时是否启用应用宿主的apply mapping
    }

    解释一下上面3个参数的作用

    • packageId用于定义每个插件的资源id,多个插件间的资源Id前缀要不同,避免资源合并时产生冲突
    • targetHost指明宿主工程的应用模块,插件编译时需要获取宿主的一些信息,比如mapping文件、依赖的SDK版本信息、R资源文件,一定不能填错,否则在编译插件时会提示找不到宿主工程。
    • applyHostMapping表示插件是否开启apply mapping功能。当宿主开启混淆时,一般情况下插件就要开启applyHostMapping功能。因为宿主混淆后函数名可能有fun()变为a(),插件使用宿主混淆后的mapping映射来编译插件包,这样插件调用fun()时实际调用的是a(),才能找到正确的函数调用。
  3. 最后一步生成插件,需要使用Gradle命令

    gradle clean assemblePlugin
    或者
    ./gradlew clean assemblePlugin

强调一下如果构建时确保Gradle版本需要为2.14.1,否则构建可能发生错误。构建成功后在build/outputs/apk 或者plugin目录中查看插件,plugin目录和apk目录中插件的区别在于plugin将插件以packageName_timestamp格式重命名,DEMO中的插件构建成功后才3KB。


插件包位置.png

官方WIKI中还说明了:

  • 插件包均是Release包,不支持debug模式的插件包
  • 如果存在多个productFlavors,那么将会构建出多个插件包

前面说到VirtualAPK是对耦合型业务有很好的支持,对于我们的DEMO来说,宿主和插件都用到了Picasso库,我们反编译插件包后看一下里面包含的内容如下图所示。


插件包内容.png

可以看到,插件包中没有Picasso库的相关源码,构建时VirtualAPK已经帮我们移除了。需要注意,宿主和插件包中依赖的SDK版本需要完全一致时才会被移除。

运行插件

因为宿主中代码写的是从SD卡根目录加载plugin.apk插件,所以我们需要将生成的插件重命名后放到指定位置。

adb push 插件路径 /sdcard/plugin.apk

然后启动宿主程序后点击"查看更多美图",此时加载的Activity来自于插件中,启动代码如下所示

@Override
public void onClick(View v) {
    if (PluginManager.getInstance(this).getLoadedPlugin(PLUGIN_PKG_NAME) == null) {
        Toast.makeText(getApplicationContext(),
                    "插件未加载,请尝试重启APP", Toast.LENGTH_SHORT).show();
        return;
    }
    Intent intent = new Intent();
    intent.setClassName("com.virtualapk.imageplugin", "com.virtualapk.imageplugin.ImageBrowserActivity");
    startActivity(intent);
}

OK,插件就么运行起来了,可以看到在开发宿主和插件时,几乎没有侵入,在生成插件时需要一些命令操作,可以通过脚本实现编译插件+Push到SD卡中,提高开发效率。

DEMO比较简单只包含了Activity,后面会扩展到其他组件的使用,大家可以仔细看下官方WIKI-插件开发指南,里面介绍了插件中四大组件的使用、so库的加载、已知的约束和插件加载时机的选取问题。

其他注意点

  1. 要想清楚宿主和插件的业务边界很重要,才能找到插件的入口点。Demo中是以ImageBrowserActivity为边界,从这个Activity之后的功能来来自于插件中。其实我们可以把插件看成一个类提供者,可以使用Class.forName()这种方式使用插件中的类,所以不是只能从Activity/Fragment作为入口点。
  2. 编译宿主工程时会生成一些信息(在build/VAHost文件夹下),插件构建时会读取这些信息,所以要确保运行的宿主和插件基于相同信息构建的,宿主变化时请重新构建插件。

资源互相调用也是可以的只要做一套公共的model,然后他们都公用同一个model里面的资源文件,资源ID冲突的问题暂时还没发现,他的生成规则是从头开始按照顺序来的,DEMO比较简单只包含了Activity,但是已经可以满足使用,大家可以仔细看下官方WIKI-插件开发指南


按照上面说的就可以完成,下面附上DEMO

>点我下载DEMO<




你可能感兴趣的:(android)