VirtualAPK是滴滴出行自研的一款优秀的插件化框架,主要有如下几个特性。
· 支持几乎所有的Android特性; · 四大组件方面
1.Activity:支持显示和隐式调用,支持Activity的theme和LaunchMode,支持透明主题; 2.Service:支持显示和隐式调用,支持Service的start、stop、bind和unbind,并支持跨进程bind插件中的Service; 3.Receiver:支持静态注册和动态注册的Receiver; 4.ContentProvider:支持provider的所有操作,包括CRUD和call方法等,支持跨进程访问插件中的Provider。 5.自定义View:支持自定义View,支持自定义属性和style,支持动画; 6.PendingIntent:支持PendingIntent以及和其相关的Alarm、Notification和AppWidget; 7.支持插件Application以及插件manifest中的meta-data; 8.支持插件中的so。
1.兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证; 2.资源方面适配小米、Vivo、Nubia等,对未知机型采用自适应适配方案; 3.极少的Binder Hook,目前仅仅hook了两个Binder:AMS和IContentProvider,hook过程做了充分的兼容性适配; 4.插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。
1.插件开发等同于原生开发,四大组件无需继承特定的基类; 2.精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖; 3.插件的构建过程简单,通过Gradle插件来完成插件的构建,整个过程对开发者透明。
以上内容均引用(https://github.com/didi/VirtualAPK/wiki)
1.项目级的build.gradle文件中
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.didi.virtualapk:gradle:0.9.8.6'
这是第一个坑,VirtualAPK目前只能在gradle3.0一下的版本使用
2.宿主app的build.gradle
apply plugin: 'com.didi.virtualapk.host'
implementation 'com.didi.virtualapk:core:0.9.8'
3.插件plugin_one和plugin_two的build.gradle中
apply plugin: 'com.didi.virtualapk.plugin'
virtualApk {
// 插件资源表中的packageId,需要确保不同插件有不同的packageId这个值的范围在系统和宿主的之间即大于0x02,小于0x7f
packageId = 0x5f
// 宿主App模块的路径,可以填写绝对路径
targetHost = '../PluginDemo/app'
//默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致
applyHostMapping = true
4.在自定义的Application中
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
5.在需要加载插件的Activity中
//加载插件需要文件读写权限,这里我图方便没有写,但是是必须要的
//lugin_one.apk 和 plugin_two.apk 是打好插件后手动push到sd卡根目录的,实际开发中,应该根据实际情况作处理
private fun loadPlugin(){
if (Environment.MEDIA_MOUNTED != Environment.getExternalStorageState()) {
Toast.makeText(this, "sdcard was NOT MOUNTED!", Toast.LENGTH_SHORT).show()
}
val pluginManager = PluginManager.getInstance(this)
val pluginOne = File(Environment.getExternalStorageDirectory(), "plugin_one.apk")
val pluginTwo = File(Environment.getExternalStorageDirectory(), "plugin_two.apk")
try {
if (pluginOne.exists()){
pluginManager.loadPlugin(pluginOne)
Log.e("MainActivity--->","load succss $pluginOne")
}
if (pluginTwo.exists()){
pluginManager.loadPlugin(pluginTwo)
Log.e("MainActivity--->","load succss $pluginTwo")
}
}catch (e:Exception){
Log.e("MainActivity--->",e.toString())
}
}
//跳转插件Activity(BundleUrl位于common模块,用于管理所有插件Activity的地址,
//插件和宿主都依赖该模块,我的理解是用该模块作一下常规通信)
findViewById<Button>(R.id.go_plugin_one)?.setOnClickListener {
val pkg="com.jason.plugin.one"
if (PluginManager.getInstance(this).getLoadedPlugin(pkg) == null) {
Toast.makeText(this, "plugin $pkg not loaded", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val intent = Intent()
intent.setClassName(this, BundleUrl.PLUGIN_ONE_MAIN_URL)
startActivity(intent)
}
6.插件生成
在项目根目录下执行
./gradlew assemblePlugin
如果顺利的话,插件apk会在插件项目的 build/outputs/plugin/release 文件夹下
~~~当然大部分情况下基本都是失败,所以接下来列一下所有踩过的坑
1.在执行./gradlew assemblePlugin前,先执行一下Make Project 确保宿主app/build/VAHost文件夹下有versions.txt文件,否则:
* What went wrong:
A problem occurred configuring project ':plugin_one'.
> Failed to notify project evaluation listener.
> Can't find /Users/asure/AndroidStudioProjects/PluginDemo/app/build/VAHost/versions.txt, please check up your host application
need apply com.didi.virtualapk.host in build.gradle of host application
> Cannot invoke method onProjectAfterEvaluate() on null object
2.确保在gradle.properties文件中配置android.useDexArchive=false,否则:
* What went wrong:
A problem occurred configuring project ':plugin_one'.
> Failed to notify project evaluation listener.
> Can't using incremental dexing mode, please add 'android.useDexArchive=false' in gradle.properties of :plugin_one.
> Cannot invoke method onProjectAfterEvaluate() on null object
3.确保插件的manifest文件下的包名和applicationId保持一致,否则在生成插件时会找不到R文件
/Users/asure/AndroidStudioProjects/PluginDemo/plugin_one/src/main/java/com/jason/plugin/one/activitis/PluginOneActivity.java:6: 错误: 找不到符号
import com.jason.plugin.plugin_one.R;
^
符号: 类 R
位置: 程序包 com.jason.plugin.plugin_one
/Users/asure/AndroidStudioProjects/PluginDemo/plugin_one/src/main/java/com/jason/plugin/one/activitis/PluginOneActivity.java:18: 错误: 程序包R不存在
setContentView(R.layout.activity_plugin_one);
^
2 个错误
这个坑卡了我好久~~ 就是因为手贱改了一下applicationId
4.插件包需要签名才能被宿主加载
这个也挺坑的~~加载不出来还没有提示~只能跟着源码去看错误,想吐
5.构建插件包最好使用命令./gradlew assemblePlugin ,反正我点击侧边栏的assemblePlugin就没有成功过
6.buildToolsVersion 不支持28.0.0往上,我项目里用的是26.0.2
github地址:(https://github.com/liujun123456/VirtualAPKDemo) 对插件化感兴趣的朋友可以把代码拉下来玩一下,基本能省略掉你很多踩坑的地方