在刚刚过去的云栖大会上,手淘宣布其移动容器化框架Atlas将于2017年年初开源,对这个框架,在过去团队对外部作过一些分享,外界也一直对其十分关注,到如今它终于即将开源了。web
本文将介绍Atlas的设计思路和手淘对容器化、组件化和动态化上的思考,主要内容来自阿里巴巴资深技术专家倪生华(玄黎)在云栖大会上的分享。算法
Atlas是什么安全
2013年,手淘航母战略的制定,带来了业务和开发人员的翻倍膨胀。从不到100人猛增四五倍,同时业务数量大增,整个客户端的架构和发版节奏受到极大挑战,Atlas做为以前手淘客户端的基础框架,进行了一次大的重构,造成了今天的Atlas。网络
Atlas是一个Android客户端容器化框架,主要提供了组件化、动态性、解耦化的支持。支持工程师在工程编码期、Apk运行期以及后续运维修复期的各类问题。架构
在工程期,实现工程独立开发,调试的功能,工程模块独立。app
在运行期,实现完整的组件生命周期的映射,类隔离等机制。框架
在运维期,提供快速增量的更新修复能力,快速升级。运维
Atlas是工程期和运行期共同起做用的框架,它的特色是尽可能将一些工做放到工程期,这样来保证运行期的简单,稳定。工具
目前,Atlas在淘系App的应用十分普遍,手淘自身超过60+业务组件、20个协做团队,以及百万行级别代码都在Atlas上运行,其快速迭代能力让应用的发布周期从每个月到每周再到随时发布,在过去半年里就发布了446次。组件化
另外Atlas自己很是轻量,只有90多个类,支持大小型App开发,从大型的手淘到相对小型的阿里健康等都是用的这个框架。其稳定性也接受了考验,兼容Android 4.x以上系统版本。总体手淘的Crash率一直维持在万分之五左右,由于容器致使的crash占比小于百分之一。
从这个意义上来讲,Atlas首先要解决的问题是大规模团队的协做问题,诉求包括并行开发、快速迭代、工程解耦,而后解决的问题是客户端动态更新的问题。手淘内部思考的解决方案就是组件化。
Atlas组件化实现
组件化,业界称为插件化,不过这里Atlas的组件化和如今的插件化有一些不同的地方。组件化是须要去知道组件的功能,设计更规范。
手淘APK包目录结构手淘APK包目录结构
这是一个手机淘宝的APK包,第一层目录上与标准的APK是彻底同样的,在APP会有不少的so文件,若是解开来看的话,它的结构相似于完整的APK,但自己并不能独立运行,它跟不少插件化的差异是在运行期,它是运行在整个容器里的,每个组件都是独立的Bundle。
从模块来划分,手淘APK能够分为两层,上层是通过拆分的业务Bundle,扫码、评价、详情,各个业务之间能够进行功能的调用,能够经过路由调度到其余业务方。下层是共享的底层中间件,向业务方开放各类能力,如网络库、图片库等,会在容器里进行统一地把控,这样作的好处是包作到尽量小,第二是性能佳。
这一块是Atlas的总体设计,分为五层:
第一层咱们称之为Hack层,包括OS Hack toolkit & verifier,这里咱们对系统能力作一些扩展,而后作一些安全校验。
第二层是Bundle Franework,就是咱们的容器基础框架,提供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的研发支撑能力,包括快捷的生成,发布,回滚,监控等能力。