从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

栏目: 编程语言 · XML · 发布时间: 6年前

内容简介:【51CTO.com原创稿件】从大团队并肩作战到小团队带头冲锋,高效的研发模式使得 App 本身的整体崩溃率始终维持在 0.02% 以下。从大团队并肩作战到小团队带头冲锋,高效的研发模式使得 App 本身的整体崩溃率始终维持在 0.02% 以下。

【51CTO.com原创稿件】从大团队并肩作战到小团队带头冲锋,高效的研发模式使得 App 本身的整体崩溃率始终维持在 0.02% 以下。

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

简介

从大团队并肩作战到小团队带头冲锋,高效的研发模式使得 App 本身的整体崩溃率始终维持在 0.02% 以下。

本着以用户为中心、以开发者为出发点,根据现有开源方案取长补短,苏宁易购移动开发部于 2017 年初自主研发出了新型插件化技术——APNP(Android Plugin And Play),旨在让研发更敏捷,让发布更灵活,最终满足用户对产品的极速体验、按需下载、动态更新。

需求分析

技术的引入来自于实际业务场景对技术的需求,插件化亦是如此,那么到底是什么原因推动了苏宁易购 App 的插件化,又是什么原因让苏宁易购开发者走上自研插件化的道路?

为什么苏宁易购 App 需要插件化?

发布周期长,产品迭代跟不上市场需求

对于一个电商 App,不同的时间、地点,伴随着用户千变万化、稍纵即逝的消费需求,谁能在第一时间满足这些需求,谁就能把握住需求带来的销量。

而传统的 App 开发模式周期过长,我们需要更敏捷的发布方案,所以我们做了插件化,这也是苏宁易购对插件化最原始的需求。

单线研发,管理、协作成本过高

随着苏宁易购业务的不断拓展、项目参与人员数量的增多,单线 App 开发模式所隐藏的问题日益凸显:一面从需求分析到研发测试,需要监管的内容越来越多;另一方面从方案决策到流程审批,协作沟通越来越频繁,成本越来越高。

因此我们需要多线、小团队的研发模式,这让我们进一步确认了插件化。

安装包过大,运营推广效率走低

需求在日益增长的同时,安装包体积也在同步膨胀。面对耗时、耗流量的安装包下载,新用户体验、老用户升级的阻力也越来越大。

为了解决这个问题,我们需要拆包、需要动态下载、需要局部更新,因此我们正式引入了插件化。

什么选择自研而不是使用开源方案?

没有完美无暇的开源方案,却有层出不穷的接入问题

移动团队在一开始选择过几种开源方案,个别方案的可用性也比较高,但是在接入之后,测试环节总会出现些疑难杂症,修复起来相对困难,一方面是源码本身的掌握成本较高,另一方面就是开源方案本身存在的缺陷。

要么另起炉灶,要么藕断丝连

现有的开源方案,要么就是对现有工程的改造较大,开发有心无力;要么就是插件工程和宿主工程相互依赖,牵一发而动全身,成本、风险都很高。

插件方案选型

基于上述种种原因,我们决定自研插件化技术,于是就有了 APNP。APNP采用的插件方案是直接加载 APK 文件(APK 格式不变,内容有所修改),原因有如下几点:

兼容性高

APK 文件的格式非常稳定,它包含一个 App 正常运行的必要资源;任何版本的 Android 系统,都应该正常解析并运行任意版本匹配的 APK 文件,无论这个 APK 文件是何时产生的;我们只要正确模仿系统加载 APK 的行为,就可以加载任意一个插件 APK 文件。

研发隔离

既然是直接加载 APK 文件,所以在 APNP 的设计方案中,每个插件都是一个单独工程,这就意味着除了最后的集成测试阶段,插件对应的整个软件生命周期都是独立的,既降低了管理、协作成本,又促使插件产品的发布更加灵活。

接入简单

既然是独立工程,无论现有的工程是如何运转的,开发者唯一要做的就是把插件工程从现有工程中抽离出来。

核心手段及原理

如果直接加载一个原始插件的 APK 文件,绝大多数情况下是无法运行的,如 Class pre-verified 异常、ResourceNotFound 等。

因此 APNP 在保持 APK 格式不变的情况下,对APK里面的内容做了针对性修改,核心手段如下:

共同 Dependency 剔除

在研发过程中,经常需要第三方依赖 Library(Dependency),而当插件工程(以下简称 Plugin)和宿主工程(以下简称 Host)包含相同的 Dependency 时,就需要剔除 Plugin 中的 Dependency。

这样一方面可以避免 Class pre-verified 异常,另一方面也可以减少插件包的大小,提升插件包的下载 & 加载速度。

实现方案(以下都默认 IDE 为 Android Studio):通过 Gradle 插件,在 Plugin 的 Transform 过程中,剔除与 Host 相同 Dependency 的所有资源。

Package ID 修改

在 Android 系统中,App 对应的 Package ID 是固定的,也就是说如果我们不人为干预,Plugin 和 Host 打包生产的 APK 文件中,所有 Resource ID 中的 Package ID 都是一样的,即(0x7f)。

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

这时如果直接加载 Plugin APK,必然会出现 Resource 类型不匹配、显示错乱等异常,所以我们修改 Plugin 的 APK 的 Package ID。(Package ID 满足 0x01 < PID < 0x7f 即可)

实现方案:业内关于修改 Package ID 的方案有很多(如修改 aapt 源码等),APNP 采用的方案是直接修改最后生成的 APK 文件。

先看下一个简单 APK 的文件结构,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

涉及 Package ID 修改的地方有 3 处:

  • R.class(classes.dex)
  • resources.arsc
  • xml 文件

修改 R.class 中的 Package ID

在 Gradle assemble 之后,会在 build/generated/source/r 文件下生成相应的 R.java 文件,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

此时我们将 R 文件中的 0x7f 直接替换成目标 Package ID ,然后继续交给 Gradle 做后续操作。

这样在最终的 classes.dex 中,里面的 R.class 文件对应的 Package ID 就是我们想要设置的 Package ID。

修改 arsc + xml 中的 Package ID

不同于上面通过 Hook Gradle 过程修改中间生成的 R 文件,arsc + xml 则是直接解压最终的 APK 并修改目标文件,然后重新签名。

首先我们需要对 APK 文件中的 arsc + xml 的文件格式有一定的认识,Android 为了充分减少 APK 自身的大小,在编译的过程中会对所有的资源进行重组 + 压缩。

例如 values 文件下的内容会被统一收集到 arsc 文件中,而不再以文件的形式存在;而 xml 也不再是原始的 xml 文件,xml 中内容会被进一步整合 + 复用。

而无论是 arsc 文件,还是 xml 文件,在它们内部都是通过一个个固定数据结构的 ResChunk 以嵌套 + 组合的形式各自存储着。

资源信息:每段 ResChunk 的内容都以 8 字节的 ResChunk_header 开头,用于描述 ResChunk 的类型 + 长度,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

也就是说 arsc + xml 中的所有内容都可以被反向解析出来,当充分了解每个 ResChunk 的数据结构以及掌握 ResChunk 中哪些内容是需要修改的 Package ID,就可以相对轻松的完成对二进制文件的修改。

Library Chunk 插入

修改完 Package ID 后,还需要在 resources.arsc 文件中插入一段 Library Chunk ,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

Library Chunk 在 Android 5.0 之后才出现,对应的类型如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

那么为什么需要插入 Library Chunk?原因是在 Android 5.0 之后,ResTable在获取资源信息时,如果资源含有 Parent(ResTable_map_entry,如 style),会验证 Parent 的 Package ID 是否合法,也就是 Package ID 是不是已经注册过了。

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

如果 lookupResourceId 没有返回 NO_ERROR,则报错,继续跟进,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

由于在上一环节中,我们已经把 Plugin 中的 Package ID 修改了,上图中的局部变量 packageId 肯定不会是 APP_PACKAGE_ID 。

最后会在 mLookupTable 数组中寻找 packageId 是否有对应的值,如果没有(值为 0)则抛出异常。

所以我们需要把自定义的 Package ID 注册到 mLookupTable 数组中,那么注册的动作是在什么时候发生的?

在 App 启动并资源首次加载时,会调用下面的方法去解析 arsc 文件,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

如果发现是 Library Chunk 会调用 addMapping,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

在 addMapping 中,我们会把 Library Chunk 中的 packageId 在 mLookupTable 数组中进行注册(就是简单的赋值),注册之后我们自定义的 Package ID 才会被系统认可,从而确保含有 Parent 资源的正常解析。

Attr ID 替换

完成上面 3 步后,APK 就已经可以加载了,但是插件涉及到自定义属性的 View ,自定义属性总是无法正常赋值,这又是什么原因呢?

以 ConstraintLayout 为例,假如 Plugin 和 Host 的 Layout 布局中都包含 ConstraintLayout,并同时都设置了 ConstraintLayout 的自定义属性如下:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

那么在 App 运行的过程中,ConstraintLayout 会通过 android.support.constraint.R.class 获取解析自定义属性,如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

而 android.support.constraint.R.class 在内存中只有一份,并且会优先加载 Host,因此在实际的运行内存中,R.class 来自于 Host。

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

由于 Host 中 R.class 里面的 Package ID 都是 0x7f ,而在上面第二节的修改 Package ID 环节中,Plugin 里面的 xml 的 Package ID 已经被我们修改了(包括 xml 中所引用的 Attr ID)。

因此当 Plugin 通过 Host 的 R.class 去解析 ConstrainLayout 时,必然会出现自定义属性无法正常解析的异常。

所在 APNP 在修改 Plugin xml 文件 Package ID 的同时,如果发现 Attr 同样存在于 Host 中,会把 Plugin xml 文件中的 Attr ID 替换成 Host 中的 Attr ID,从而保证 Plugin 中所有的自定义属性能够正常解析(红框:0x7f01000f,0x7f010022,0x7f010026,0x7f01002b),如下图:

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践

否则直接修改 Package ID(蓝框:0x7e010000,0x7e010001,0x7e010002)。

实践成果

在 APNP 实际应用之后,通过苏宁云迹平台的实时数据监控,并没有发现因 APNP 本身引起的明显异常。

从大团队并肩作战到小团队带头冲锋,高效的研发模式也使得 App 本身的整体崩溃率始终维持在 0.02% 以下。

相比之前的研发流程,APNP 带来的影响主要有下面几个点:

产品更精细、定位更明确 

APNP 是产品精细划分和明确定位的技术支撑,小团队的研发模式,加快了产品的落实与升级。

研发周期短、发布更灵活

插件可单独升级、可独立发布的特征,配合独立团队的专人专职,单一产品可以达到一周一版本。

管理更轻松、研发更高效

同步产品线的精细划分,研发团队也趋于更小、更独立,管理也显得更加轻松;同时明确的责任分工,也促进了研发人员的工作效率。

发展前景

Google 在今年的 IO 大会上推出了一种动态加载方案——App Bundles,可以让用户通过 Google Play 动态下载应用功能,而且 App Bundles 采用的方案也是直接加载 APK 文件。

通过分析最终的 Feature Master APK 文件,你会发现 App Bundles 对 APK 做了和 APNP 相同的操作:剔除 Dependency、修改 Package ID、插入 Library Chunk、替换 Attr ID。

这看似巧合实则必然,因为 App Bundles 和 APNP 都是直接加载 APK 文件,所以面对的问题都是一样的。

在方案上 APNP 可以说有了官方保障,而且 APNP 在后面还可以借鉴 App Bundles 的实现细节,让插件加载更加高效、安全。

而由于 Android P 对隐藏 API 调用的限制,很多人认为插件化方案将不再可行。

但是 Google 也同时提供了 light greylist,并且国内手机厂商肯定不允许让主流 App 无法使用的情况发生,所以我觉得在很长一段时间内,插件化依然可以正常运转。

作者:李呈武

简介:苏宁易购前端技术专家,资深Android 开发者,深度掌握 Android 虚拟机、插件化、Weex 等技术,熟悉移动网络的特质,对移动端的架构设计有独特的见解,一直致力于通过优秀的架构设计,减少开发成本,提升开发质量。

【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】

从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践


以上所述就是小编给大家介绍的《从大团队并肩作战到小团队带头冲锋,苏宁App插件化应用实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Design Programs, 2nd Edition

How to Design Programs, 2nd Edition

Matthias Felleisen、Robert Bruce Findler、Matthew Flatt、Shriram Krishnamurthi / MIT Press / 2018-5-4 / USD 57.00

A completely revised edition, offering new design recipes for interactive programs and support for images as plain values, testing, event-driven programming, and even distributed programming. This ......一起来看看 《How to Design Programs, 2nd Edition》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具