VirtualAPK 详解和使用

9,028 阅读5分钟

1 介绍

Android 插件化技术是比较热门领域,VirtualAPK 是滴滴2017年6月3号开源,框架功能完备,支持 Android 四大组件,良好的兼容性,且入侵性较低,作为加载耦合插件方案是较好选择。兼容市面上几乎所有的 Android 手机,这一点已经在滴滴出行客户端中得到验证; 资源方面适配小米、VivoNubia 等,对未知机型采用自适应适配方案;极少的 Binder Hook,目前仅仅hook了两个 BinderAMSIContentProviderhook过程做了充分的兼容性适配;插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的

2 VirtualAPK和主流开源框架的对比

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

3 原理

3.1 基本原理

  • 合并宿主和插件的ClassLoader
    需要注意的是,插件中的类不可以和宿主重复
  • 合并插件和宿主的资源
    重设插件资源的packageId,将插件资源和宿主资源合并
  • 去除插件包对宿主的引用
    构建时通过Gradle插件去除插件对宿主的代码以及资源的引用

3.2 四大组件的实现原理

  • Activity
    采用宿主manifest中占坑的方式来绕过系统校验,然后再加载真正的activity;
  • Service
    动态代理AMS,拦截service相关的请求,将其中转给Service Runtime去处理,Service Runtime会接管系统的所有操作;
  • Receiver
    将插件中静态注册的receiver重新注册一遍;
  • ContentProvider
    动态代理IContentProvider,拦截provider相关的请求,将其中转给Provider Runtime去处理,Provider Runtime会接管系统的所有操作。
    VirtualAPK的整体架构图,更详细的内容请大家阅读源码。

3.3 VirtualAPK的整体架构图

VirtualAPK

3.4 原理分析源码

鸿洋大神写的源码分析(滴滴官网都推荐看鸿洋大神写的)
滴滴插件化方案 VirtualApk 源码解析
下面这位大神就不知道是谁了(滴滴官网推荐看的)
VirtualAPK 资源加载机制分析

4 使用

4.1 插件和宿主必须compile相同的aar

比如宿主compile了如下aar

compile 'com.didi.virtualapk:core:0.9.0'//滴滴VirtualAPK的依赖
compile 'com.alibaba:fastjson:1.2.39'
compile'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'

插件工程需要访问宿主sdk中的类和资源,那么可以在插件工程中同样compile sdk的aar,如下:

compile 'com.didi.virtualapk:core:0.9.0'//滴滴VirtualAPK的依赖
compile 'com.alibaba:fastjson:1.2.39'
compile'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'

这样一来,插件工程就可以正常地引用sdk了。并且,插件构建的时候会自动将这个aar从apk中剔除

4.2 宿主工程

  • 在工程根目录下build.gradle中添加
dependencies {
 classpath 'com.didi.virtualapk:gradle:0.9.0'
}
  • 在App的build.gradle中顶部添加

 apply plugin: 'com.didi.virtualapk.host'
  • 在App的build.gradlecompile 添加
dependencies {
 compile 'com.didi.virtualapk:core:0.9.0'
}
  • 编写MyApp继承Application重写attachBaseContext方法中初始化插件引擎(别忘了在AndroidManifest.xml配置Application)
  @Override
    protected void attachBaseContext(Context context)  {
        super.attachBaseContext(context);
        PluginManager.getInstance(context).init();
    }
  • 推荐大家在Application启动的时候去加载插件,不然的话,请注意插件的加载时机。 考虑一种情况,如果在一个较晚的时机去加载插件并且去访问插件中的资源,请注意当前的Context。比如在宿主Activity(MainActivity)中去加载插件,接着在MainActivity去访问插件中的资源(比如Fragment),需要做一下显示的hook,否则部分4.x的手机会出现资源找不到的情况。
 PluginManager pluginManager = PluginManager.getInstance(this);
//此处是当查看插件apk是否存在,如果存在就去加载(比如修改线上的bug,把插件apk下载到sdcard的根目录下取名为Demo.apk)
        File apk = new File(Environment.getExternalStorageDirectory(), "Demo.apk");
        if (apk.exists()) {
            try {
                pluginManager.loadPlugin(apk);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
  • AndroidManifest.xml添加读写SD卡的权限,如果插件apk是从网上获得的,还需要添加上网权限(我只是简单的测试,直接把插件apk放到sd卡中)
  <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

4.3 插件工程

  • 在工程根目录下build.gradle中添加
dependencies {
 classpath 'com.didi.virtualapk:gradle:0.9.0'
}
  • 在App的build.gradle中顶部添加依赖以及插件配置信息

 apply plugin: 'com.didi.virtualapk.plugin'//注意这个是plugin结尾,宿主是以host结尾的

// 插件配置信息,放在文件最下面
virtualApk {
  // 插件资源表中的packageId,需要确保不同插件有不同的packageId.
    packageId = 0x6f

    // 宿主工程application模块的路径,插件的构建需要依赖这个路径,我这个宿主工程和插件工程在同一级目录下,所以下面这样写
    targetHost = '../VirtualAPKHostDemo/app' 

    //默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致
    applyHostMapping = true 

4.5 Gradle版本推荐

目前VirtualAPK构建器还在持续完善之中,因此插件构建推荐使用Gradle 2.14.1版本,3.x版本可能有适配问题,我们正在努力去兼容各种Gradle版本。同时,待VirtualAPK构建器完善之后,其代码也将开源,敬请期待。

4.4 构建命令

估计大部分还不知道怎么构建吧, 我是用git构建的
进入F:\ARWorkSpace\VirualAPKPluginDemo,点击右键Git Bash Here
输入

./gradlew clean assemblePlugin(这个本人测试通过)
或者
gradle clean assemblePlugin(这个本人测试没有成功,不知道为什么)

来构建构建完成之后再F:\ARWorkSpace\VirualAPKPluginDemo\app\build\outputs\plugin\release有个apk这个就是插件apk,如下图位置

插件apk位置

assemblePlugin依赖于assembleRelease,这意味着:
- 插件包均是Release包,不支持debug模式的插件包
- 如果存在多个productFlavors,那么将会构建出多个插件包
- 插件包位于build目录下

4.4 运行完之后把apk放到SD卡的根目录下,取名为Demo.apk(与之前面对应)

然后测试即可完成 如下图
这里写图片描述

需要注意点

本人使用时的坑

  • host工程必须添加读写sd卡权限
  • 配置插件配置信息,放在文件最下面也就是配置targetHost时一定要看好host工程和plugin工程的相对位置
  • 使用 Gradle 构建命令(本人使用git构建的)

官网常见问题

源码位置 github