VirtualApk是滴滴开源的一套插件化方案,其支持四大组件,支持插件宿主之间的交互,兼容性强,在滴滴出行APP中有应用。下面是官方文档中与其他主流插件化框架的对比(查看原文):
特性 | DynamicLoadApk | DynamicAPK | Small | DroidPlugin | VirtualAPK |
---|---|---|---|---|---|
支持四大组件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 |
组件无需在宿主manifest中预注册 | √ | × | √ | √ | √ |
插件可以依赖宿主 | √ | √ | √ | × | √ |
支持PendingIntent | × | × | × | √ | √ |
Android特性支持 | 大部分 | 大部分 | 大部分 | 几乎全部 | 几乎全部 |
兼容性适配 | 一般 | 一般 | 中等 | 高 | 高 |
插件构建 | 无 | 部署aapt | Gradle插件 | 无 | Gradle插件 |
buildscript {
dependencies {
...
classpath 'com.didi.virtualapk:gradle:0.9.8.6'
...
}
}
引入插件
在app模块的build.gradle中添加apply plugin: 'com.didi.virtualapk.host'
添加依赖
在app模块的build.gradle中的dependencies
中加入implementation 'com.didi.virtualapk:core:0.9.8'
初始化SDK
选择一个合适的时机初始化SDK,一般是在项目的Application类的attachBaseContext
方法中完成。
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
PluginManager.getInstance(base).init()
}
添加gradle依赖
同上面接入主程序环节第一步配置,如果插件模块和主程序在同一个项目中则可以忽略
引入插件
在插件模块的build.gradle中添加apply plugin: 'com.didi.virtualapk.plugin'
注意的是:插件模块也是一个应用项目而非库项目,即apply plugin: 'com.android.application'
而不是apply plugin: 'com.android.library'
声明插件配置
在插件模块的build.gradle底部声明virtualApk配置
virtualApk {
packageId = 0x6f // 资源前缀.
targetHost = '../app' // 宿主模块的文件路径,生成插件会检查依赖项,分析和排除与宿主APP的共同依赖.
applyHostMapping = true //optional, default value: true.
}
其中packageId
是资源id的前缀,用来区分插件资源,所以插件之间要使用不同的前缀。
这个前缀不一定要0x6f
,正常我们的APP编译出来的R文件一般像下面这种,可以看出前缀是0x7f
,理论上这个packageId
的取值范围应为[0x00,0x7f),然而0x01
、0x02
等等已经被系统应用占用,具体占用多少不得而知,因此尽量选择偏大且足够分配给所有插件使用的数字。
public final class R {
public static final class anim {
public static final int abc_fade_in=0x7f010000;
public static final int abc_fade_out=0x7f010001;
public static final int abc_grow_fade_in_from_bottom=0x7f010002;
}
}
到这里就已经完成了VirtualApk的宿主以及插件模块的配置,非常简单,可以看出对我们现有的工程完全几乎不需要修改,我们依然可以用我们习惯的模块化的开发方式。
截止发稿时的最新版本是0.9.8.6
,建议大家尽量使用最新版本,毕竟安卓的碎片化这么严重,而且hook方案多少会有些不完美的地方,相信滴滴以及gayhub的基友们会在新版本不停的完善它,而且老版本很可能不会维护。
一般从官方GitHub项目的releases可以找到当前最新版本。
public class VirtualAPKHostApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 初始化VirtualAPK
PluginManager.getInstance(base).init();
}
@Override
public void onCreate() {
super.onCreate();
// 加载存储根目录的插件apk,实际项目中按需保存
String pluginPath = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/plugin.apk");
File plugin = new File(pluginPath);
if (plugin.exists()) {
try {
PluginManager.getInstance(this).loadPlugin(plugin);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
不要忘了在清单文件中配置Application:
<application
android:name=".VirtualAPKHostApplication">
</application>
com.yl.plugin是插件工程的包名,com.yl.plugin.PluginActivity是插件工程中的类,插件工程的包名可以和宿主工程相同,但是相同包名下的类名不能相同,资源名称也不能相同。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_start_plugin_activity).setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (PluginManager.getInstance(this).getLoadedPlugin("com.yl.plugin") == null) {
Toast.makeText(this, "Plugin is not loaded!", Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent();
intent.setClassName("com.yl.plugin", "com.yl.plugin.PluginActivity");
startActivity(intent);
}
}
}
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
-dontwarn com.didi.virtualapk.**
-dontwarn android.**
-keep class android.** { *; }
插件
gradlew clean assemblePlugin
因为assemblePlugin依赖于assembleRelease,所以插件包均是Release包,不支持debug模式的插件包。
https://blog.csdn.net/yyh352091626/article/details/74852390
https://www.jianshu.com/p/013510c19391
https://www.cnblogs.com/tgltt/p/9542193.html