VirtualAPK是滴滴出行自研的一款优秀的插件化框架,通过将业务模块插件化,可随时更新插件来发布新功能,具备版本随时发布的能力。
四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。
theme
和LaunchMode
,支持透明主题;start
、stop
、bind
和unbind
,并支持跨进程bind插件中的Service;CRUD
和call
方法等,支持跨进程访问插件中的Provider。自定义View
,支持自定义属性和style
,支持动画;PendingIntent
以及和其相关的Alarm
、Notification
和AppWidget
;Application
以及插件manifest中的meta-data
;so
。AMS
和IContentProvider
,hook过程做了充分的兼容性适配;如下是VirtualAPK和主流的插件化框架之间的对比。
特性 | DynamicLoadApk | DynamicAPK | Small | DroidPlugin | VirtualAPK |
---|---|---|---|---|---|
支持四大组件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 |
组件无需在宿主manifest中预注册 | √ | × | √ | √ | √ |
插件可以依赖宿主 | √ | √ | √ | × | √ |
支持PendingIntent | × | × | × | √ | √ |
Android特性支持 | 大部分 | 大部分 | 大部分 | 几乎全部 | 几乎全部 |
兼容性适配 | 一般 | 一般 | 中等 | 高 | 高 |
插件构建 | 无 | 部署aapt | Gradle插件 | 无 | Gradle插件 |
已经有那么多优秀的开源的插件化框架,滴滴为什么要重新造一个轮子呢?
1. 大部分开源框架所支持的功能还不够全面 除了DroidPlugin,大部分都只支持Activity。
2. 兼容性问题严重,大部分开源方案不够健壮 由于国内Rom尝试深度定制Android系统,这导致插件框架的兼容性问题特别多,而目前已有的开源方案中,除了DroidPlugin,其他方案对兼容性问题的适配程度是不足的。
3. 已有的开源方案不适合滴滴的业务场景 虽然说DroidPlugin从功能的完整性和兼容性上来看,是一款非常完善的插件框架,然而它的使用场景和滴滴的业务不符。
DroidPlugin侧重于加载第三方独立插件,比如微信,并且插件不能访问宿主的代码和资源。而在滴滴打车中,其他业务模块均需要宿主提供的订单、定位、账号等数据,因此插件不可能和宿主没有交互。
其实在大部分产品中,一个业务模块实际上并不能轻而易举地独立出来,它们往往都会和宿主有交互,在这种情况下,DroidPlugin就有点力不从心了。
基于上述几点,我们只能重新造一个轮子,它不但功能全面、兼容性好,还必须能够适用于有耦合的业务插件,这就是VirtualAPK存在的意义。
在加载耦合插件方面,VirtualAPK是开源方案的首选,推荐大家使用。
VirtualAPK对插件没有额外的约束,原生的apk即可作为插件。插件工程编译生成apk后,即可通过宿主App加载,每个插件apk被加载后,都会在宿主中创建一个单独的LoadedPlugin对象。如下图所示,通过这些LoadedPlugin对象,VirtualAPK就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。
第一步: 初始化插件引擎
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
第二步:加载插件
public class PluginManager {
public void loadPlugin(File apk);
}
当插件入口被调用后,插件的后续逻辑均不需要宿主干预,均走原生的Android流程。 比如,在插件内部,如下代码将正确执行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
LinearLayout holder = (LinearLayout)findViewById(R.id.holder);
TextView imei = (TextView)findViewById(R.id.imei);
imei.setText(IDUtil.getUUID(this));
// bind service in plugin
Intent service = new Intent(this, BookManagerService.class);
bindService(service, mConnection, Context.BIND_AUTO_CREATE);
// start activity in plugin
Intent intent = new Intent(this, TCPClientActivity.class);
startActivity(intent);
}
Service Runtime
去处理,Service Runtime
会接管系统的所有操作;Provider Runtime
去处理,Provider Runtime
会接管系统的所有操作。如下是VirtualAPK的整体架构图,更详细的内容请大家阅读源码。