写点什么

滴滴插件化项目 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:496577

评论

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

密码学系列之:IDEA

程序那些事

IDEA 密码学 程序那些事

CampusBulider(模模搭)学习笔记6:室内搭建

ThingJS数字孪生引擎

3D可视化 数字孪生

《Spring 手撸专栏》| 开篇介绍,我要带新人撸 Spring 啦!

小傅哥

spring 后端 小傅哥 面经 手撸Spring

针对 Restful 协议下的接口测试平台设计

GrowingIO技术专栏

RESTful

看见技术的价值 | 音视频技术有奖征文来啦~

InfoQ写作社区官方

音视频 声网 热门活动

上手 WebRTC DTLS 遇到很多 BUG?浅谈 DTLS Fragment

阿里云CloudImagine

阿里云 音视频 WebRTC SRS流媒体服务器 视频云

三大业界大佬的DevOps解决方案

码界行者

DevOps

Dubbo Stub与Mock

青年IT男

dubbo

变电站运维推陈出新?无人值守却更胜一筹

一只数据鲸鱼

数据可视化 3D可视化 智慧电网 变电站

玩转直播系列之RTMP协议和源码解析(2)

vivo互联网技术

RTMP 直播技术

SimpleDateFormat线程不安全的5种解决方案!

王磊

Java 后端 5月日更

LeetCode题解:341. 扁平化嵌套列表迭代器,DFS,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

比物理线程都好用的C++20的协程,你会用吗?

华为云开发者联盟

事件驱动 嵌入式 事件 主循环 C++20

长夜漫漫,聊聊synchronized锁的打怪升级路

码农参上

synchronized 签约计划第二季

五行合一,微服务运行态建设的“内功心法”

BoCloud博云

微服务

如何让研发新同学快速 Onboarding

GrowingIO技术专栏

研发管理

人工智能基础1 - DAY8

Qien Z.

人工智能 5月日更

Rust从0到1-集合-字符串

rust string 集合 字符串 Collections

开发者测试:你必须知道7件事

华为云开发者联盟

软件工程 测试 工程师 代码 开发者测试

JavaScript实现:如何写出漂亮的条件表达式

华为云开发者联盟

代码 JavaScrip 条件表达式 多条件语句 多属性对象

SpringCloud微服务架构实战:Feign+Hystrix实现RPC调用保护

小Q

Java 学习 面试 微服务 spring cloud alibaba

Docker 存储调整的几种方法

运维研习社

Docker 5月日更 存储管理

【LeetCode】二叉树的堂兄弟节点Java题解

Albert

算法 LeetCode 5月日更

模块4作业-Redis方案

高亮

架构训练营

极光开发者周刊【No.0514】

极光GPTBots-极光推送

开发者

做个开发喜欢的产品

MavenTalker

产品经理 产品设计 系统开发

浅入浅出 MySQL 索引

leonsh

MySQL 索引

缓存数据一致性 - 架构师峰会演讲实录

万俊峰Kevin

缓存 微服务 cache 分布式缓存 Go 语言

构建可靠的磁盘系统:故障判定和自动化处理

焱融科技

云原生 运维自动化 高性能 分布式存储 超融合

「打碎质疑」,阿里云未来何止600亿?

ToB行业头条

云计算 阿里云 盈利模式

由一张精益MVP图所浮想联翩

Man

敏捷开发 研发管理

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