写点什么

滴滴插件化项目 VirtualAPK 开源

  • 2017-06-29
  • 本文字数:3244 字

    阅读完需:约 11 分钟

在 Android 插件化技术日新月异的今天,开发并落地一款插件化框架到底是简单还是困难,这个问题不同人会有不同的答案。但是我相信,完成一个插件化框架的 demo 并不是多难的事,但是要开发一款完善的插件化框架却不是一件容易的事,尤其在国内,各大 Rom 厂商都对 Android 系统做了一定程度的定制,这进一步加剧了 Android 本身的碎片化问题。

我们在 2016 年开始研究这方面的技术,经过半年的开发、测试、适配和线上验证,目前推出了一款比较完善的插件化框架:VirtualAPK。

之所以现在推出来,是因为 VirtualAPK 在我们内部已经得到了很好的验证,我们在迭代过程中不断地做机型适配和细节特性的支持,目前已经达到一个非常稳定的状况,足以支撑滴滴部分乃至全部业务的动态发版需求。目前 VirtualAPK 应用于滴滴乘客端和优步中国 APP 中,大家可以去体验。

为了更好地体现出 VirtualAPK 的价值,我们决定将其开源,请猛击 https://github.com/didi/VirtualAPK

VirtualAPK 也是滴滴公司的首个对外开源项目,欢迎大家 star、发送 pull request,也欢迎大家来使用 VirtualAPK,我们会给予一定的技术支持。

VirtualAPK 能为我们带来什么?

在传统的 APP 发布过程中,总是存在着固定的发版节奏,比如两周或者一个月更新一次,这固然没有问题。但考虑一种情况,如果一个版本刚发布出去,却发现存在大量 crash,这个时候我们会怎么办?大多数公司都会选择立刻发一个紧急版本,然后一批人需要手忙脚乱甚至加班来准备这个版本,所以紧急版本还是越少越好。

其实不仅仅是因为致命 crash 而紧急发版,比如一个早期创业公司,需要通过迅速的“试错”来尝试找准市场的方向,这个时候就需要更加紧凑的发版方式,有些时候甚至想一天发一次版。在正常的发版流程中,这显然是不现实的。但是如果这家创业公司必须要随时可以发版,否则就可能被竞争对手抢占先机,这个时候该怎么办呢?

上述的这两个问题,通过 VirtualAPK,将不再是问题。通过 VirtualAPK 将业务模块插件化,然后就可以随时通过更新插件的方式来发布新功能,不管是修复致命 crash 还是进行业务“试错”,都是一种很爽快的体验。

VirtualAPK 的特性

VirtualAPK 是滴滴出行自研的一款优秀的插件化框架,主要有如下几个特性。

功能完备

  • 支持几乎所有的 Android 特性;
  • 四大组件方面

四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。

  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。

优秀的兼容性

  • 兼容市面上几乎所有的 Android 手机,这一点已经在滴滴出行客户端中得到验证;
  • 资源方面适配小米、Vivo、Nubia 等,对未知机型采用自适应适配方案;
  • 极少的 Binder Hook,目前仅仅 hook 了两个 Binder:AMS 和 IContentProvider,hook 过程做了充分的兼容性适配;
  • 插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。

入侵性极低

  • 插件开发等同于原生开发,四大组件无需继承特定的基类;
  • 精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖;
  • 插件的构建过程简单,通过 Gradle 插件来完成插件的构建,整个过程对开发者透明。

VirtualAPK 和主流开源框架的对比

如下是 VirtualAPK 和主流的插件化框架之间的对比。

为什么选择VirtualAPK

已经有那么多优秀的开源的插件化框架,滴滴为什么要重新造一个轮子呢?

1. 大部分开源框架所支持的功能还不够全面 除了 DroidPlugin,大部分都只支持 Activity。

2. 兼容性问题严重,大部分开源方案不够健壮 由于国内 Rom 尝试深度定制 Android 系统,这导致插件框架的兼容性问题特别多,而目前已有的开源方案中,除了 DroidPlugin,其他方案对兼容性问题的适配程度是不足的。

3. 已有的开源方案不适合滴滴的业务场景 虽然说 DroidPlugin 从功能的完整性和兼容性上来看,是一款非常完善的插件框架,然而它的使用场景和滴滴的业务不符。

DroidPlugin 侧重于加载第三方独立插件,比如微信,并且插件不能访问宿主的代码和资源。而在滴滴打车中,其他业务模块均需要宿主提供的订单、定位、账号等数据,因此插件不可能和宿主没有交互。

其实在大部分产品中,一个业务模块实际上并不能轻而易举地独立出来,它们往往都会和宿主有交互,在这种情况下,DroidPlugin 就有点力不从心了。

基于上述几点,我们只能重新造一个轮子,它不但功能全面、兼容性好,还必须能够适用于有耦合的业务插件,这就是 VirtualAPK 存在的意义。

在加载耦合插件方面,VirtualAPK是开源方案的首选,推荐大家使用

通俗易懂地说

  1. 如果你是要加载微信、支付宝等第三方 APP,那么推荐选择 DroidPlugin;
  2. 如果你是要加载一个内部业务模块,并且这个业务模块很难从主工程中解耦,那么 VirtualAPK 是最好的选择。

抽象地说

  1. 如果你要加载一个插件,并且这个插件无需和宿主有任何耦合,也无需和宿主进行通信,并且你也不想对这个插件重新打包,那么推荐选择 DroidPlugin;
  2. 除此之外,在同类的开源中,推荐大家选择 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);
}

探究原理

基本原理

  • 合并宿主和插件的 ****ClassLoader 需要注意的是,插件中的类不可以和宿主重复
  • 合并插件和宿主的资源 重设插件资源的 packageId,将插件资源和宿主资源合并
  • 去除插件包对宿主的引用 构建时通过 Gradle 插件去除插件对宿主的代码以及资源的引用

四大组件的实现原理

  • Activity 采用宿主 manifest 中占坑的方式来绕过系统校验,然后再加载真正的 activity;
  • Service 动态代理 AMS,拦截 service 相关的请求,将其中转给 Service Runtime 去处理,Service Runtime 会接管系统的所有操作;
  • Receiver 将插件中静态注册的 receiver 重新注册一遍;
  • ContentProvider 动态代理 IContentProvider,拦截 provider 相关的请求,将其中转给 Provider Runtime 去处理,Provider Runtime 会接管系统的所有操作。

如下是 VirtualAPK 的整体架构图,更详细的内容请大家阅读源码和 wiki。

2017-06-29 22:496098

评论

发布
暂无评论
发现更多内容

Web3 极客日报#138

谢锐 | Frozen

区块链 独立开发者 技术社区 Rebase Web3 Daily

程序员的晚餐 | 5 月 13 日 果木鸡丁的夏天

清远

美食

程序员的晚餐 | 5 月 10 日 能让你流泪的不只是洋葱

清远

美食

数据与广告系列一:初识在线计算广告

黄崇远@数据虫巢

互联网 数据 广告

ClickHouse为何如此之快?

nauu

数据库 大数据 OLAP Clickhouse

我肝了一个月,给你写出了这本Java开发手册。

苹果看辽宁体育

Java25周年

一杯茶的时间,上手 Node.js

图雀社区

node.js

严选合伙人(一)

Neco.W

创业 合伙人 初创公司

游戏夜读 | 预测问题的硬核是?

game1night

如何认识更多的朋友扩展社交朋友圈的质量

吃素的左撇子

人生 人脉

Linux 容器化技术的前世今生(虚拟化、容器化、Docker)

Meandni

Docker 云计算 Linux 容器 虚拟机

产品不需要刻意强调创新

Xue Liang

产品 创新突破 PCon

Java 真实笔试题2

旭霁

Java

记一次线上事故

编号94530

Java MySQL 故障分析 事故

这种场景你还写ifelse你跟孩子坐一桌去吧

小傅哥

小傅哥 drools ifelse 复杂代码优化 规则引擎使用

Web3极客日报 #139

谢锐 | Frozen

区块链 独立开发者 技术社区 Rebase Web3 Daily

Java 中的 Mysql 时区问题

张晓辉

回“疫”录(16):管控更加严格了

小天同学

疫情 回忆录 现实纪录 纪实

JVM源码分析之深入分析Object类finalize()方法的实现原理

猿灯塔

JVM

练习英语口语的误区

董一凡

学习

基于环信sdk在uni-app中快速开发多平台社交Demo

DT极客

低代码 .VS. 无代码

Jeff Kit

低代码 零代码

现在的我和未来的我之间的差距原来是态度,而它拉开我们彼此命运的距离。

叶小鍵

Java并发之AQS源码分析

指尖流逝

Java

如何优雅的实现分布式锁

飘渺Jam

redis zookeeper 分布式锁

看完这篇操作系统,和面试官扯皮就没问题了

苹果看辽宁体育

操作系统 计算机基础

手把手带你体验 HTTP/3

清远

程序员的晚餐 | 5 月 11 日 久违的大蒜的味道

清远

美食

个人技术成长与发展

颇风

后端 技术人

Tomcat安全配置

wong

Tomccat security

程序员的晚餐 | 5 月 14 日 虎皮青椒

清远

美食

滴滴插件化项目VirtualAPK开源_移动_滴滴App架构组_InfoQ精选文章