由本章节开始,咱们将从支付宝客户端的架构设计方案入手,细分拆解客户端在“容器化框架设计”、“网络优化”、“性能启动优化”、“自动化日志收集”、“RPC 组件设计”、“移动应用监控、诊断、定位”等具体实现,带领你们进一步了解支付宝在客户端架构上的迭代与优化历程。android
本节将介绍支付宝 Android 容器化框架设计的基本思路。程序员
随着 Android 应用程序所能实现的功能愈来愈强大和复杂,随之而来的是:api
此外,移动客户端一般须要面对动态化开发的挑战;Bug 紧急修复等运维需求;同时也有一些在线运营的需求,如动态下发广告,推送接入活动等。若是每次有运维、运营需求,都须要一次客户端发版,那将是传统的开发人员的梦魇。安全
Android 开发者们深切体会到一个稳健可靠、可扩展的、支持大规模并行开发的客户端开发框架对于平台级别的客户端 App 的重要性。事实上,客户端框架设计的健壮性和扩展性,在面对上述需求和解决困难上,每每能达到事半功倍的效果,尤为是 Android 客户端开发人员将深受其利。网络
那么,做为平台级别的 Android 客户端 App 究竟该如何的进行框架设计,才能知足变幻无穷的移动互联网时代的困难和需求?架构
No. | 问题 | 描述 |
---|---|---|
1 | 项目工程复杂度高,开发、编译、测试、集成都很是困难 | 支付宝 App 代码 200W 行+ |
2 | 平台级App的内部微应用(团队)很是多,并行开发要求高 | 内部多达几十个应用 |
3 | APK size 庞大 | 支付宝 App60+M,致使在某些厂商 Rom 中安装不上 |
4 | 线上版本出现各类问题 | 如:发版后,UCSDK 被乌云平台暴露出安全漏洞 |
5 | 线上活动运营需求 | 春节红包扫福活动,预案要动态推送新 so 文件到客户端 |
咱们能够概括为:平台级客户端框架必需要解决的是模块化
和 动态化
这两大核心问题。app
(本篇文章咱们着重关注模块化
相关内容,后续咱们经过其余文章分析 动态化
的能力。)框架
为了解决上述模块化的问题,咱们要遵循如下原则去设计客户端框架:运维
Quinox 客户端框架是类 OSGi( like-as)框架的实现。Quinox 一词来源于著名的 OSGi 框架的实现 Equinox。分布式
基于此框架的客户端 App,都是由一个个的积木搭建而成,这些积木被称之为:Bundle。
Bundle 是 OSGi 规范的模块化基本单位,与 Android 里的 android.os.Bundle 是两个彻底不一样的概念。 OSGi 里的 Bundle 指的是 Java 应用程序的基本单位,它是一个模块单元(Jar 格式),也是上文 Quinox 简介里提到的积木。 基于 Quinox 容器框架开发的应用程序也是由众多的 Bundle(APK 格式)构成。
本章节将从项目开发的三个不一样的时期对 Bundle 的形态进行阐述:
时期 | 形态 |
---|---|
开发期 | Bundle 工程 |
构建期 | Bundle 包 |
集成期 | 集成客户端的 Bundle 基线 |
常规的 Android 项目开发,代码工程一般有两种(两级)类型
工程类型 | Library | Application |
---|---|---|
工程输出 | Aar | Apk |
基于 Quinox 容器框架开发的 Android 项目,代码工程则有三种(三级)类型
工程类型 | Library | Bundle(工程包) | Application(测试包/安装包/Final APK) |
---|---|---|---|
工程输出 | Aar | Apk(.jar) | Apk |
关于 Bundle 工程,咱们须要了解如下三点:
关于 Bundle 工程的结构图请参考:
如上所述,Bundle 工程的输出也是 APK 文件。
在 OSGi 规范中,Bundle 是有不少属性的。Bundle 工程输出的 APK 与常规的 APK 有一个不一样点,mPaaS 插件会将 Bundle 的全部属性生成一个特殊的文件放在这个 APK 中,供容器去解读。
除了 APK 文件以外,Bundle 工程的构建结果还包含:
Bundle 包文件,在构建完成以后,一般要 deploy 到本地/远程的 mvn 仓库中,以供其余 Bundle 工程引用,或是被 Portal 工程集成。
前面已经讲述过,构建 Final APK 其实主要就是将不少的 Budnle APK 合并成最终的 APK 的过程,而这些众多的 Bundle APK 们都存放于 mvn 仓库中。
所以咱们将这些 Bundle 的 GAV(GroupId,ArtifactId,Vesion)的集合,称之为基线。
当某一个团队/我的开完一个 Bundle 工程的新功能,并通过测试达到可发布状态,就能够更新基线里的版本号。咱们将这个过程称之为进基线。咱们认为:基线里打出来的 APK 是稳定可运行的;没有稳定 Bundle 工程包不该该进基线。
Bundle 基线机制能够很好的隔离了模块之间的相互影响,保障了不一样团队间开发环境的和谐与稳定,达到了咱们以前的设计的初衷,所以能够很好的支持多团队并行开发。
关于 Bundle 属性,咱们能够参考 OSGi 的 Bundle 属性。Quinox 容器框下定义的 Bundle 属性要简单的多。
下表将列举 Bundle 的全部属性以及配置方法:
名称 | 说明 | 配置办法 |
---|---|---|
Bundle-Name | Bundle 的名称,做为 key 值存在。同一个客户端 apk 中,不容许同名的 Bundle 存在 | 由 mPaaS 插件根据 Bundle 工程的 GAV 的 GroupId、ArtifactId ,以必定的规则生成而来。 |
Bundle-Version | Bundle 的版本号,各个 Bundle 的接口包必须作到 API 版本向下兼容。 | 由 mPaaS 插件根据 Bundle 工程的 GAV 中的 Version 生成而来 |
Init-Level | 已废弃 | 配置为 1 便可 |
Package-Name | 已废弃 | 配置为 '' 便可 |
Component-Name | Bundle 中声明的 Android Component。它跟 Export-Pacakges 属性同样,是 Bundle 的入口类。 | 由 mPaaS 插件根据 AndroidManifest.xml 文件中定义的 Activity,Service,BroadcastReceiver,ContentProvider 等生成 |
Package-Id | Bundle 工程的资源的 packageid,具体技术细节请参考4.2章节 | 必须由开发同窗在 Bundle 工程中设置属性 packageId,其值的设置区间为【27, 127】,若是没有资源,则设置为 127,若是 Bundle 为 Bundle 依赖关系树上根节点的 Bundle,则设置为27。 |
Contains-Dex | 此 Bundle 中是否包含代码(classes.dex) | 由 mPaaS 插件根据 Bundle 文件中是否包含 classes.dex 节点判断得来。备注:静态连接的 Bundle 因为 classes.dex merge 到了主 apk 中,因此该属性会被修正为 false |
Contains-Res | 此 Bundle 中是否包含资源(resources.arsc) | 由 mPaaS 插件根据 Bundle 文件中是否包含 resources.arsc 文件判断得来。 |
Native-Library | 此 Bundle 中是否包含 native so(lib/xxx/libxxx.so) | 由 mPaaS 插件根据 Bundle 文件中 native so 文件判断得来。备注:在构建最终 apk 时,Bundle 中全部的 so 文件在构建 Final apk 时,会 merge 到 Final apk 中,因此该属性会被修正为 null |
Required-Bundle | 此 Bundle 依赖的 Bundle 列表:为 Budnle-Name@Bundle-Version 的格式。 | 由 mPaaS 插件根据Bundle项目工程依赖其余Bundle接口包,来生成此属性。 |
Export-Pacakges | Bundle 导出包(请参考 OSGi 的导出包概念)。Quinox 容器将根据导出包,从对应的 Bundle 中加载类。 | 必须由开发同窗在 Bundle 工程中设置属性 exportPackages。例如:某个非静态链接的 Bundle 提供了类: com.alipay.android.phone.framework.api.A 做为接口给其余 Bundle 使用,则须将com.alipay.android.phone.framework.api 配置为导出包。(反射被使用的类也应归入导出包)。有多个导出包的用','隔开。为了性能考虑,导出包不该设置太多,或者范围太广。 |
做为 Android 开发人员,咱们知道经过 android.content.res.Resources 对象能够获取字符串、布局、图片、动画等资源。
在 Quinox 容器化的框架内,原生的资源管理确定没法实现多 Bundle 的资源管理,这时候,咱们就构建了 Bundle 资源管理器,来专门处理各个 Bundle 的资源的加载、调用等工做,替代了 Android 原生的资源管理逻辑。
可是,因为全部 Bundle 包都是独立编译的,它们中的资源很可能存在着相同的资源 id。所以,当存在相同资源时,就可能存在冲突,那么如何解决资源 id 的冲突呢?
做为 Android 程序员,咱们都知道资源 id 是一个 int 值,它包含4个 byte。它是在构建 APK 工程时,由 aapt 工具生成,定义在 R 文件中。
示例代码:
public final class R {
public static final class drawable {
public static int xxx_bg=0x1e020000;
}
public static final class id {
public static int xxx_id=0x1e050001;
}
public static final class layout {
public static int xxx_layout=0x1e030000;
}
public static final class string {
public static int xxx_str=0x1e040001;
}
}
复制代码
这四个 byte 含义以下:
以下图所示:
到这里,不少读者应该已经理解到了,Quinox 容器框架关于资源 id 冲突的解决方案是,让 mPaaS 打包插件使用改造过 aapt 工具,对每个 Bundle 工程都指定不一样 packageId,进行分区隔离,从而确保不一样的 Bundle 之间资源 id 是不会重复的。这也是为何 Bundle 工程里须要指定 packageId 的缘故。
关于 Quinox 容器化这里,因为目前为止,Quinox 暂未开源,因此本章节内,咱们暂时不涉及到源码分析。
上面咱们聊了不少关于 Bundle 的话题,那么整个容器化的核心,也是如何管理各个 Bundle。这时就要引出咱们的容器管理器了,容器管理器的主要工做就是协调各个 Bundle,对各类信息进行增删改查。
在应用启动后,咱们的容器管理器会读取配置信息,生成各 Bundle 的信息实例。
Quinox 容器框架的目标是解决 Android 客户端 App 模块化和动态化这两大核心问题。增、删这两项能力,更多的是用来实现动态化能力的,方便容器对各个 Bundle 进行动态添加、删除。因为本文着重描写模块化的能力,因此这部分,咱们后续单开专题来分析容器的动态化能力。
关于容器管理器的改的能力,Quinox 主要是利用改的能力,作一些启动性能的优化,以及机型适配上的工做,这里涉及源码较多,咱们不作过多分析。
容器管理器使用最频繁的功能接口应该是查询接口:
经过管理器的查询接口,咱们进行各个 Bundle 之间的协调、通讯,完成容器化的功能。
除了容器管理器,还有一个重要的点就是组件的启动器。Quinox 容器定制类原生 Android Activity 的启动流程,从而自主管理 Activity 的建立以及生命周期。
同时,因为 Activity 是咱们自主的启动器进行的建立,咱们还能够对 Activity 进行一些定制化的改造,方便其更好的适配容器这套体系。好比说给 Activity 赋予咱们自定义的资源管理器,管理 Activity 堆栈并对外提供接口,对 Activity 各生命周期作一些切面工做等等。
定制化的组件启动器,还有一个好处就是能够作到 Activity 的动态运行。所谓动态运行,是指运行出厂未注册在 Manifest 中的 Activity。这块功能,更可能是为了支持容器动态化的能力。
因为 Activity 动态运行的实现逻辑涉及较多的核心技术点,因此咱们暂时不进行具体实现的剖析。
经过本节内容,咱们已经初步了解了 mPaaS 在安卓端容器化框架的设计思路和相应模块。因为篇幅限制,不少技术要点咱们没法一一展开。
欢迎你们上手体验 mPaaS。关于安卓端容器化框架的设计思路和具体实践,一样期待大家的反馈,欢迎一块儿探讨交流。
往期阅读
《开篇 | 模块化与解耦式开发在蚂蚁金服 mPaaS 深度实践探讨》