亮网络解锁器,解锁网络数据的无限可能 了解详情
写点什么

Atlas:手淘 Native 容器化框架和思考

  • 2016-10-19
  • 本文字数:4671 字

    阅读完需:约 15 分钟

在刚刚过去的云栖大会上,手淘宣布其移动容器化框架 Atlas 将于 2017 年年初开源,对这个框架,在过去团队对外部做过一些分享,外界也一直对其十分关注,到现在它终于即将开源了。

本文将介绍 Atlas 的设计思路和手淘对容器化、组件化和动态化上的思考,主要内容来自阿里巴巴资深技术专家倪生华(玄黎)在云栖大会上的分享。

Atlas 是什么

2013 年,手淘航母战略的制定,带来了业务和开发人员的翻倍膨胀。从不到 100 人猛增四五倍,同时业务数量大增,整个客户端的架构和发版节奏受到极大挑战,Atlas 作为之前手淘客户端的基础框架,进行了一次大的重构,形成了今天的 Atlas。

Atlas 是一个 Android 客户端容器化框架,主要提供了组件化、动态性、解耦化的支持。支持工程师在工程编码期、Apk 运行期以及后续运维修复期的各种问题。

  • 在工程期,实现工程独立开发,调试的功能,工程模块独立。
  • 在运行期,实现完整的组件生命周期的映射,类隔离等机制。
  • 在运维期,提供快速增量的更新修复能力,快速升级。

Atlas 是工程期和运行期共同起作用的框架,它的特点是尽量将一些工作放到工程期,这样来保证运行期的简单,稳定。

目前,Atlas 在淘系 App 的应用十分广泛,手淘自身超过 60+ 业务组件、20 个协作团队,以及百万行级别代码都在 Atlas 上运行,其快速迭代能力让应用的发布周期从每月到每周再到随时发布,在过去半年里就发布了 446 次。另外 Atlas 本身非常轻量,只有 90 多个类,支持大小型 App 开发,从大型的手淘到相对小型的阿里健康等都是用的这个框架。其稳定性也接受了考验,兼容 Android 4.x 以上系统版本。整体手淘的 Crash 率一直维持在万分之五左右,因为容器导致的 crash 占比小于百分之一。

从这个意义上来说,Atlas 首先要解决的问题是大规模团队的协作问题,诉求包括并行开发、快速迭代、工程解耦,然后解决的问题是客户端动态更新的问题。手淘内部思考的解决方案就是组件化。

Atlas 组件化实现

组件化,业界称为插件化,不过这里 Atlas 的组件化和现在的插件化有一些不一样的地方。组件化是需要去知道组件的功能,设计更规范。

(手淘 APK 包目录结构)

这是一个手机淘宝的 APK 包,第一层目录上与标准的 APK 是完全一样的,在 APP 会有很多的 so 文件,如果解开来看的话,它的结构类似于完整的 APK,但本身并不能独立运行,它跟很多插件化的差别是在运行期,它是运行在整个容器里的,每一个组件都是独立的 Bundle。

从模块来划分,手淘 APK 可以分为两层,上层是经过拆分的业务 Bundle,扫码、评价、详情,各个业务之间可以进行功能的调用,可以通过路由调度到其他业务方。下层是共享的底层中间件,向业务方开放各种能力,如网络库、图片库等,会在容器里进行统一地把控,这样做的好处是包做到尽可能小,第二是性能佳。

这一块是 Atlas 的整体设计,分为五层:

第一层我们称之为 Hack 层,包括 OS Hack toolkit & verifier,这里我们对系统能力做一些扩展,然后做一些安全校验。

第二层是 Bundle Framework,就是我们的容器基础框架,提供 Bundle 管理、加载、生命周期、安全等一些最基本的能力。

第三层是运行期管理层,包括清单,我们会把所有的 Bundle 和它们的能力列在一个清单上,在调用时方便查找;另外是版本管理,会对所有 Bundle 的版本进行管理;再就是代理,这里就是和业界一些插件化框架机制类似的地方,我们会代理系统的运行环境,让 Bundle 运行在我们的容器框架上;然后还有调试和监控工具,是为了方便工程期开发调试。

第四层是业务层了,这里我们向业务方暴露了一些接口,如框架生命周期、配置文件、工具库等等。

最上面一层是应用接入层,就是我们的业务代码了。

所以 Atlas 作为一个框架提供了相对完整的能力,业务层的开发可以在框架生命周期的各个环节做一些自定义的动作,也可以自由的调用系统、框架,乃至其它组件释放的能力。

组件化技术细节

前面讲的是容器层面的比较概要的东西,下面我们会讲一些具体的细节。

关于 Bundle 的生命周期会提供细粒度的节点,比如下面是一个 Bundle 从加载到运行的周期:

  • startInstall:开始加载。这个时候框架会做一些拷贝文件、释放 lib、加载 Bundle 的事情;
  • Installed:加载完毕。这时框架会注入资源路径,创建 class loader;
  • resolved:解析完毕,框架会检查组件配置是否合法,是否能被解析;
  • active:运行组件,即开始运行组件 Bundle;
  • started:运行成功。

组件化涉及到的第一个问题是 Manifest 处理,一个是因为来源很多,有宿主 Manifest、Aar Manifest 以及组件 Manifest,另外不同组件的 Manifest 经常发生变化,要求我们灵活地去处理。这里的做法是在工程期将所有的 Manifest 进行 Merge 操作,这里需要注意的是 Bundle 的依赖单独 Merge,因为这里涉及到依赖仲裁的问题。最后解析各个 Bundle 的 Merge Manifest,得到整包的 BundleInfoList,就是上面我们提到的 Bundle 信息清单。

第二个是类加载,这里利用 Delegate ClassLoader 来动态加载组件的类。Delegate ClassLoader 先查找宿主 Bundle 的 PathClassLoader,然后根据前面的 BundleList 找到对应的 BundleClassLoader.

第三个是资源,我们会用自己的 DelegeteResources 替换掉系统的 resource,Bundle 的资源会逐个在安装的时候添加到 AssertPath,由于添加 Bundle 的顺序非固定,不分区会导致资源查找错乱。

另外,Dalvik 和 ART 上的资源查找过程顺序是不一样的,加上小米等系统会重写自己的 resources,所以我们会适配不同的机型,往后追加 AssetsPath 或者往前追加,系统 AssetManager 是个单例,默认往后追加,如果往前追加,则需要重新创建 AssetsManager 对象,同样主 dex 动态部署的时候要达到替换原有 resource 的目的,必须保证插入顺序与查找顺序一致。

还有需要注意的是,每次更新 resourceTable 的时候,必须保证 apkresource,runtime 的系统 resource,例如 webview,bundle resource 都已经添加成功,而且唯一,顺序正确。

不同 Bundle 的资源可能发生命名冲突,我们是用了一种相对来说简单的方法,将各自的 Bundle 分配成不同的 ID,保证所有的业务资源不会产生冲突,尽量将问题放到工程期解决。在很多代码里,通过反射来调用整个资源,在 5.0 以上的系统是没有问题的,它只找第一个,对业务代码而言,原来是怎么写的,今天还是怎么去写。

关于组件化性能这一部分,我们引入了按需加载,因为手淘 APK 有 70 多个 Bundle,每个用户真正用的时候只需要 5 或 10 个,所以不需要加载所有的 Bundle。Bundle 之间进行隔离,通过 Android 四大原生组件进行交互,这样 Bundle 之间可以比较好的解耦。我们所有调用的入口都是基于 BundleInfolist 去做的,根据这个清单信息,得到组件所在 Bundle,如果需要加载,我们就进行 install、dexopt 等操作。

另外,对于解决组件依赖问题,定义了两种新的组件格式 Awb(业务 Bundle)和 solib(so 库),前者与 AAR 一致,不过不添加本地 lib,在构建的时候做依赖仲裁区分,后者是 Native so 库的依赖。Awb 其实就是 AAR,只是后缀修改了,如果你的包放在宿主 Bundle 就用 AAR,如果是组件 Bundle 就用 Awb。

对于业务 Bundle 的依赖,我们在构建期会将宿主 Bundle 和业务 Bundle 及其依赖分别打包,然后按照最短路径、第一声明原则进行树状仲裁,得到每个 Bundle 需要的依赖,在打包的时候会将依赖库放到各自的 Bundle 里去。

最后是 APK 构建,我们对它做了比较大的调整。上面的图中,其实左边这一部分是一个标准的 APK 的构建过程,包括处理,编译,到签名的过程。我们这个不同的地方是多了 Awb 需要特殊处理,其中 Awb 的资源根据宿主的 resource.ap_ 和包内资源构建,R 文件由 Bundle R 资源和宿主 R 资源合并而来,然后我们对 Aapt 进行了修改,对每个 awb 分配不同的 packageId,然后进行统一混淆,生产各个 AWB 的 Dex,打包为 APK,签名之后复制到 libs,改名为 so 文件,然后合并到 taobao APK. 这就是我们组件化的整个过程。

Atlas 动态化

在一个容器框架内,组件化和动态化是相辅相成的,组件只是解决了解耦的问题,但我们如果想要随时发包,就必须让容器框架具备动态化能力。我们在完成了 Atlas 的组件化之后,做了动态化的支持。动态化的好处一个是包的大小缩减,我们可以将一些包在运行后下载到应用中,另一个是具备动态发版和修复能力。

增量动态化方案

Atlas 提供了动态部署的能力,主要目标是动态业务发布,以及问题修复。它基于手淘自研差量算法,主 Bundle 基于 ClassLoader 机制,业务 Bundle 基于差量 merge,支持全业务类型。

另外,Atlas 也支持 Andfix 作为插件使用,目标是快速故障修复,它的原理基于 Native hook,主要做方法的修改,在实际中可以两个一起用。在工程构建期适配之后,可以做到一套代码两套方案通用。

自研动态部署功能实现原理,首先,对于 Dex Patch 的生成,我们通过修改 Dex 的字节码实现,将 Dex 文件转为 Smali,对其中的 ClassDef 和 ClassDataMethod 结构体进行分析,可以实现删除、新增、修改类,然后通过 Diff 处理得到差量文件,再通过 Merge 处理即生成补丁。

其次是整个资源 Patch 的生成,分为两块,一个是业务 Bundle,本来是一个不断加载的过程,它实现起来会比较简单,通过 Md5 diff/BSDiff 即可得到。对于主 Bundle,因为安卓本身有一个限制,所有的资源必须得在 base 包里,新增一个资源是不生效的。所以一个做法是在打包的时候预留很多空资源。另外更新已有的资源则通过资源覆盖来完成。

最后,如果新加业务的话,会新加 Activity,我们的做法首先在 Manifest 预埋一个 StubActivity,然后在 Instrumentation.execStartActivity() 阶段进行替换,同时配合 Intent setFlag 模拟 Activity launch mode 并继续 startActivity,接着 System_server 进程进行处理,更新 ActivityStack,创建 binder,并通知 ActivityThread 进行实例创建,最后我们在 ActivityThread 的 handler 里面进行拦截,更新 ActivityInfo 等信息,创建目标 Activity。

另外在工程实践上,因为补丁的生成会涉及到 Dex 和资源的基线,我们会在部署的时候,每次发布 APK 包同步发布 AP(基线包)到 Maven,AP 基线包里是所有影响基线的文件,第一是安卓 APK,第二是 Mapping.txt,最后是 Dependency.txt,这样的话整个构建的速度会非常的快。

所以我们这种方式,版本的升级是不同的方式。比如今天手淘的详情要更新,会发布版本,这个版本可能不是到应用市场的版本,而是一个 Patch 包。业务版本的动态部署,我们是同步的,5.3.0 到 5.3.1 到 5.3.2,这样一个好处是只要容器版本没有升级,只要有需求,patch 就可以一直升级,而且是无感知的差量升级。

周边优化点

最后来讲讲我们的周边优化点,为什么到今天才说要开源,做的过程当中还是遇到了不少问题。

第一点是 Bundle 的重复资源合并。因为我们发现,因为宿主问题,必然而然会出现冲突的问题,包括图片资源,我们会放到整个宿主类目中去。

第二是 Bundle 的依赖校验,以前是代码的话,是编译过的,但因为今天是二进制,这个问题会遗留到现场去,所以会看看 API 是否会影响 Bundle。

第三是类库“瘦身”,因为手淘依赖的各种中间件类库太多了,导致手淘本身很臃肿,方法数很大;所以打包的时候对类库有一个裁剪的过程,优化方法数。

第四是依赖导致的,依赖查询库。

第五是做 Dex File 等,进行混淆 Mapping。

最后是开源准备中,我们在工程期、运行期都会去做开源,并且将机制通过云服务的方式提供出来,阿里百川会提供 Atlas 的研发支撑能力,包括快捷的生成,发布,回滚,监控等能力。

点击链接,关注阿里百川更多开源产品。

2016-10-19 09:379957

评论 1 条评论

发布
用户头像
这么牛的操作,就没人点个赞,支持一下吗?!
2020-05-27 12:19
回复
没有更多了
发现更多内容

应用部署初探:3个主要阶段、4种常见模式

SEAL安全

应用部署

新范式+新标准=世界级产品|StarRocks年度总结

StarRocks

数据库

贴合运维场景的告警聚合实现——以Zabbix为例

北海

运维 zabbix 告警 IT运维

1

Doctor Blind

分享一个 HIVE SQL 性能优化点-使用公共表表达式 CTE 替换临时表

明哥的IT随笔

hadoop hive

一文走进多核架构下的内存模

KaiwuDB

多模数据库 多核编程 内存模

全球首个面向遥感任务设计的亿级视觉Transformer大模型

京东科技开发者

CNN 遥感 遥感影像 企业号 2 月 PK 榜 深度视觉

《欧拉开源操作系统行业应用案例集》2023年案例集征集开始!

openEuler

Linux 操作系统 openEuler

面试前必刷!Java高级工程师1380道面试题(附答案)分享

架构师之道

编程 程序员 java面试

mockito入门

i查拉图斯特拉如是说

后端 单元测试

开发互动直播应用很简单:声网 Android Demo保姆级跑通教程

声网

android RTC RTE 教程分享

AIGC的浪潮下,文本生成发展得怎么样了?

澜舟孟子开源社区

人工智能 文本生成 AIGC

手把手教您在PyCharm中连接云端资源进行代码调试

华为云开发者联盟

人工智能 华为云 企业号 2 月 PK 榜 华为云开发者联盟

想找个稳定的工作

MavenTalker

职业素养 职业发展 求职面试

代码实例解读如何安全发布对象

华为云开发者联盟

开发 华为云 企业号 2 月 PK 榜 华为云开发者联盟

比亚迪新能源汽车战略布局研究

不脱发的程序猿

汽车电子 比亚迪新能源汽车战略布局 比亚迪新能源汽车

软件测试/测试开发 | app自动化测试(Android)--App 控件交互

测试人

软件测试 自动化测试 测试开发 appium app自动化测试

RocketMQ源码-NameServer架构设计及启动流程

小小怪下士

Java 源码 程序员 RocketMQ

BSN-DDC基础网络详解(一):基础介绍

BSN研习社

坚持技术or转做管理,我们该如何选择?

石云升

极客时间 1月月更 技术领导力实战笔记

2K字就能理解的async/await原理,还要拖多久?

梁木由

前端 前端开发 校招 前端入门

云时代,好用的数据迁移方案推荐

NineData

数据库迁移 数据校验 数据复制 迁移工具 NineData

windows命令窗口

MEImei

架构实战营第 10 期 - 模块五:微博评论高性能高可用计算架构设计

kaizen

「架构实战营」

OKR之剑·实战篇04:OKR执行过程优化的那些关键事

vivo互联网技术

团队管理 OKR

分层次的电路设计方法

timerring

FPGA

火山引擎DataTester:0代码也能实施A/B测试的实验平台

字节跳动数据平台

大数据 AB testing实战 企业号 2 月 PK 榜

技术管理 之 干系人管理

码猿外

技术管理 干系人管理

Databend Roadmap in 2023

Databend

一看就懂!任务提交的资源判断在Taier中的实践

袋鼠云数栈

比亚迪元EV汽车拆解报告

不脱发的程序猿

嵌入式 汽车电子 比亚迪元EV汽车拆解

Atlas:手淘Native容器化框架和思考_移动_玄黎_InfoQ精选文章