简介
Android 插件化技术是比较热门领域,VirtualAPK 是滴滴2017年6月3号开源,框架功能完备,支持 Android 四大组件,良好的兼容性,且入侵性较低,作为加载耦合插件方案是较好选择。
环境准备
Gradle版本号为2.14.1,可以在gradle/wrapper/gradle-wrapper.properties中更改版本号:
distributionUrl=https://services.gradle.org/distributions/gradle-2.14.1-all.zip
com.android.tools.build的版本号为2.1.3
宿主工程接入
- 在宿主工程根目录的build.gradle添加依赖
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
- 在App的工程模块的build.gradle添加使用gradle插件
apply plugin: 'com.didi.virtualapk.host'
3.添加VirtualAPK SDK compile依赖
dependencies {
compile 'com.didi.virtualapk:core:0.9.0'
}
4.在App的工程模块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.** { *; }
5.HostApplication类是继承了Application,覆写attachBaseContext函数,进行插件SDK初始化工作
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
6.在使用插件之前加载插件,可以根据具体业务场景选择合适时机加载,我是在HostActivity的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();
}
}
}
从SD卡上直接加载Plugin.apk,在AndroidMainifest.xml记得添加下面的权限:
<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" />
具体代码如下:
HostActivity.java
package com.jackie.virtualapkhost;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.didi.virtualapk.PluginManager;
import java.io.File;
import static android.os.Environment.getExternalStorageDirectory;
public class HostActivity extends AppCompatActivity {
private Button mLaunchPluginBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_host);
initView();
initEvent();
}
private void initView() {
mLaunchPluginBtn = (Button) findViewById(R.id.btn_launch_plugin);
// 加载Plugin.apk插件
PluginManager pluginManager = PluginManager.getInstance(this);
// 此处是当查看插件apk是否存在,如果存在就去加载(比如修改线上的bug,把插件apk下载到sdcard的根目录下取名为Plugin.apk)
File file = new File(getExternalStorageDirectory(), "Plugin.apk");
if (file.exists()) {
try {
pluginManager.loadPlugin(file);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void initEvent() {
mLaunchPluginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClassName("com.jackie.virtualapkplugin", "com.jackie.virtualapkplugin.PluginActivity");
startActivity(intent);
}
});
}
}
注意,我这里的取名是HostActivity.java和activity_host.xml,是为了和插件APK区分,使用过程中宿主APK和插件APK的资源名称不能相同。
插件工程接入
1.. 在插件工程根目录的build.gradle添加依赖
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
2.在插件工程模块的build.gradle添加使用gradle插件和插件配置信息,信息需要放在文件最下面。
// 插件配置信息,放在文件最下面
virtualApk {
// 插件资源表中的packageId,需要确保不同插件有不同的packageId.
packageId = 0x6f
// 宿主工程application模块的路径,插件的构建需要依赖这个路径,我这个宿主工程和插件工程在同一级目录下,所以下面这样写
targetHost = 'E:\\Workspace\\VirtualAPKHost\\app'
// 默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致
applyHostMapping = true
}
3.生成插件
gradle clean assemblePlugin
或者
gradlew clean assemblePlugin
编译出来的插件如下:
效果如下:
遇到的问题:
1.Error:A problem occurred configuring project ‘:app_plugin’. > The directory of host application doesn’t exist
解决办法:修改宿主路径,建议targetHost写成绝对路径。
2.Error:A problem occurred configuring project ‘:app_plugin’. > Failed to notify project evaluation listener. > com/android/builder/dependency/ManifestDependency
解决办法:修改项目gradle 版本为2.1.3
3.Error:A problem occurred configuring project ‘:app_plugin’. > Can’t find E:\Workspace\VirtualAPKHost\app\build\VAHost\Host_R.txt, please check up your host application need apply com.didi.virtualapk.host in build.gradle of host application
解决办法:在工程根目录的build.gradle添加使用gradle插件;
同时clean project
4. Can’t find E:\Workspace\VirtualAPKHost\app\build\VAHost\versions.txt
解决办法:在3的基础上 rebuild project
/**
* 结论:
* 1、loadPlugin之后可以像以往一样启动Service,但是必须加一句代码否则会崩溃:it.setPackage(“com.plugin.plugin.plugindemo”);
* 2、实验证明插件工程和宿主工程是在同一个进程中,在插件中用sp保存的数据在宿主工程中可以正常获取,反之也可以
* 3、插件和宿主可以正常发广播和接收广播, 都能正常收到
* 4、生成插件要用gradlew assemblePluginRelease命令,否则Activity打开一片空白
* 5、无论是插件还是宿主activity主题都必须使用这个类型的主题:Theme.AppCompat, 否则不能打包
* 6、插件1和插件2 能像宿主跟插件一样来交互, 如果要加载n个插件就loadPlugin n次
* 7、build.gradle最下面配置packageId = 0x6f,这是packageId,每个插件设置成不一样
* 8、广播和Service、Activity在插件与插件之间交互都一样,没有什么区别
*
* 暂不支持:
* 不支持 Activity 的部分属性,比如 process、configChanges 等;
* 暂不支持 overridePendingTransition(int enterAnim, int exitAnim) 这种形式的转场动画;
* 插件中弹通知,不能使用插件中的资源,比如图片
* 从Android 6.0开始,系统采用了新的权限机制,但是暂时不支持在插件中动态申请权限
*
* 插件管理:
* 一般做法是从服务端下发插件,宿主端管理插件的下载,加载,更新,回滚。宿主端升级,旧版本的插件全部作废重新等待下发。
* 每个插件保存在应用的私有目录下,在私有目录下初始化一个当前最新的版本,并标明当前的版本号, 如果需要就从服务端下载更新的插件,
* 每次升级插件都要作废,是因为每次升级的时候私有目录下已经放了最新的插件。
*/