Android插件化——VirtualAPK插件框架接入项目

由于国内Android 软件的碎片化比较严重,所以衍生了Android热修复和插件化技术,而且最近这几年这两项技术都非常热门,热修复技术能够及时修复已经线上版本的bug,而插件化技术能够有效解决软件的升级成本、发布新功能和解决方法数超过65536,以及能够解耦模块等问题。

之前我已经介绍过微信的tinker热修复框架并且示范过实际项目中如何接入tinker,如果你不知道如何接入tinker,可以参考我的这篇文章《Android tinker热修复——实战接入项目》,而本文将要介绍的是滴滴的开源的插件化框架——VirtualApk

插件概念介绍

插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。

插件化在Android中的作用在文章开头已经介绍过了,对于我的项目而已,我觉得比较大的优点就是模块解耦,组员可以协同开发,还有就是解决65k方法数的问题。

VirtualApk简介

VirtualAPK是滴滴出行自研的一款优秀的插件化框架,支持四大组件,而且不需要在宿主manifest中预注册,每个组件都有完整的生命周期,该框架具有良好的兼容性和极低的入侵性。

VirtualAPK的开源地址:https://github.com/didi/VirtualAPK/wiki

感兴趣的,可以点开源地址进去start下,在wiki里有更加详细的介绍。

gradle编译环境

如果使用VirtualAPK插件框架,需要用到gradle来编译插件,因此这里需要安装gradle环境,gradle版本需要为2.14.1。

下载地址:http://services.gradle.org/distributions/

1、下载完成之后,解压:

2、添加系统环境变量

path中添加:

以上步骤就可以完成了gradle环境的配置了。

VirtualApk接入

VirtualApk接入分为两部分,一个是宿主工程的接入,另一个是插件接入。

宿主工程接入VirtualApk

1)在项目的build.gradle添加依赖

buildscript {
    repositories {
        jcenter()
    }
    dependencies {

        classpath 'com.android.tools.build:gradle:2.1.3'
        classpath 'com.didi.virtualapk:gradle:0.9.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
复制代码

2)宿主工程下的build.gradle添加VirtualApk依赖

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.didi.virtualapk:core:0.9.0'
}
复制代码

同时添加使用插件

apply plugin: 'com.didi.virtualapk.host'
复制代码

项目的gradle版本需要和gradle版本一直,所以需要更改项目的版本为2.14.1

注意:由于Gradle Version需要和Android Plugin Version对应,比如Android Plugin的版本为2.3的,那么Gradle Version最低需要3.3,而Android Plugin Version 2.3以下的,Gradle Version 可以为2.14.1对应。

3)在proguard-rules.pro文件添加混淆规则

-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
-dontwarn com.didi.virtualapk.**
-dontwarn android.content.pm.**
-keep class android.** { *; }
复制代码

4)应用签名 插件包均是Release包,不支持debug模式的插件包,所以需要签名。

signingConfigs {
        release {
            storeFile file("../keystore/myplugin.jks")
            storePassword "123456"
            keyAlias "myplugin"
            keyPassword "123456"
        }
    }

    packagingOptions {
        exclude 'META-INF/services/org.xmlpull.v1.XmlPullParserFactory'
    }

    buildTypes {
        release {
            minifyEnabled false
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            signingConfig signingConfigs.release
        }
    }
复制代码

5)自定义一个类继承了Application,在attachBaseContext初始化VirtualApk。

public class BaseApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        PluginManager.getInstance(base).init();
    }
}
复制代码
插件接入VirtualApk

1)项目的build.gradle添加依赖

dependencies {
        classpath 'com.android.tools.build:gradle:2.1.3'
        classpath 'com.didi.virtualapk:gradle:0.9.0'
    }
复制代码

2)插件build.gradle的VirtualApk配置

apply plugin: 'com.didi.virtualapk.plugin'

virtualApk {
    packageId = 0x7f             // The package id of Resources.
    targetHost = '../MyPlugin/app' // The path of application module in host project.
    applyHostMapping = true      // [Optional] Default value is true.
}

复制代码

对virtualApk的三个参数解析:

packageId:用于定义每个插件的资源id,多个插件间的资源Id前缀要不同,避免资源合并时产生冲突

targetHost:指明宿主工程的应用模块,插件编译时需要获取宿主的一些信息,比如mapping文件、依赖的SDK版本信息、R资源文件,一定不能填错,否则在编译插件时会提示找不到宿主工程。

applyHostMapping:表示插件是否开启apply mapping功能。当宿主开启混淆时,一般情况下插件就要开启applyHostMapping功能。因为宿主混淆后函数名可能有fun()变为a(),插件使用宿主混淆后的mapping映射来编译插件包,这样插件调用fun()时实际调用的是a(),才能找到正确的函数调用。

经过上面两个步骤,插件就可以使用VirtualApk了,宿舍就可以调用插件apk了。

注意

1)插件、宿主的项目gradle版本,以及编译的gradle版本要一致。

2)插件和宿主使用的VirtualApk版本要一致。

3)各个插件的virtualApk下的packageId属性值要不一致。

4) 报Failed to notify project evaluation listener错误,需要修改Gradle和build tools的版本。

5)插件和宿主的资源和文件命名不要相同。

VirtualApk使用

1)新建一个项目,根据上文的VirtualApk的配置,配置好宿主环境。

2)新建一个module

配置好插件的VirtualApk环境。

3)在适当的时机加载插件的apk。

    private void loadPlugin(Context base) {
        PluginManager pluginManager = PluginManager.getInstance(base);

        File testplugin = new File(Environment.getExternalStorageDirectory(), "testplugin.apk");

        if (testplugin.exists()) {
            try {
                pluginManager.loadPlugin(testplugin);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            Toast.makeText(getApplicationContext(),
                    "SDcard根目录未检测到myapp.apk插件", Toast.LENGTH_SHORT).show();
        }
    }
复制代码

4)调用插件的activity

findViewById(R.id.go).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (PluginManager.getInstance(MainActivity.this).getLoadedPlugin("main.plugin.com.appplugin") == null) {
                    Toast.makeText(getApplicationContext(),
                            "插件未加载,请尝试重启APP", Toast.LENGTH_SHORT).show();
                    return;
                }
                Intent intent = new Intent();
                intent.setClassName("main.plugin.com.appplugin", "main.plugin.com.appplugin.HelloWorldActivity");
                startActivity(intent);
            }
        });
复制代码

5)打包宿主apk

6)生成插件 生成插件有两种方式

a、由于在上文介绍中已经安装了gradle环境,因此可以使用Gradle命令生成插件:

gradle clean assemblePlugin
复制代码

b、使用as的Gradle:

7)打包宿主apk,安装即可使用。

调用插件说明

除了调用在项目内新建的module插件,还可以新建项目编译成的apk,也就是说VirtualApk把一切的apk看成插件加载和调用。

因此可以新建一个项目,配置好VirtualApk环境,需要注意的是:由于新建的项目是当作插件来使用的,所以配置的VirtualApk环境需要配置的是插件的VirtualApk环境。在项目的build.gradle中配置如下:

apply plugin: 'com.didi.virtualapk.plugin'
virtualApk {
    packageId = 0x7f             // The package id of Resources.
    targetHost = '../MyPlugin/app' // The path of application module in host project.
    applyHostMapping = true      // [Optional] Default value is true.
}
复制代码

加载插件:

PluginManager pluginManager = PluginManager.getInstance(base);
        File testplugin = new File(Environment.getExternalStorageDirectory(), "testplugin.apk");

        if (testplugin.exists()) {
            try {
                pluginManager.loadPlugin(testplugin);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            Toast.makeText(getApplicationContext(),
                    "SDcard根目录未检测到myapp.apk插件", Toast.LENGTH_SHORT).show();
        }
复制代码

调用插件的Activity

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {       
            @Override
            public void onClick(View v) {           
                if (PluginManager.getInstance(MainActivity.this).getLoadedPlugin(MY_APP) == null) {
                    Toast.makeText(getApplicationContext(),"插件未加载,请尝试重启APP", Toast.LENGTH_SHORT).show();                    
                    return;
                }
                Intent it = new Intent();
                it.setClassName(MY_APP, "com.main.myapp.PluginMainActivity");
                startActivity(it);
            }
        });
复制代码

打包安装好宿主apk,把插件apk都放在加载的目录下就可以了。

运行截图:

宿主apk:

跳转插件的Activity:

跳转插件的Activity:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值