V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
alibaichuan
V2EX  ›  问与答

云栖大会上宣布即将开源的手淘 Atlas 什么来头?

  •  
  •   alibaichuan · 2016-10-25 11:09:17 +08:00 · 1306 次点击
    这是一个创建于 2954 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在刚刚过去的云栖大会上,手淘宣布其移动容器化框架 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 的组件化和现在的插件化有一些不一样的地方。组件化是需要去知道组件的功能,设计更规范。

    图片描述

    http://img.blog.csdn.net/20161021155855392

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

    http://img.blog.csdn.net/20161021155931128

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

    http://img.blog.csdn.net/20161021160005128

    这一块是 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 信息清单。

    http://img.blog.csdn.net/20161021160034950

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

    http://img.blog.csdn.net/20161021160109614

    第三个是资源,我们会用自己的 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 里去。

    http://img.blog.csdn.net/20161021160141037

    最后是 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 ,主要做方法的修改,在实际中可以两个一起用。在工程构建期适配之后,可以做到一套代码两套方案通用。

    http://img.blog.csdn.net/20161021160209528

    自研动态部署功能实现原理,首先,对于 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 的研发支撑能力,包括快捷的生成,发布,回滚,监控等能力。

    2 条回复    2016-10-25 20:34:05 +08:00
    oott123
        1
    oott123  
       2016-10-25 12:45:11 +08:00
    怪不得手淘那么卡。
    alibaichuan
        2
    alibaichuan  
    OP
       2016-10-25 20:34:05 +08:00
    @oott123 亲,手淘性能已飞速提升啦
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1216 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 18:29 · PVG 02:29 · LAX 10:29 · JFK 13:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.