干货 | 掌控 Android 编译利器,携程火车票AAR 编译速度优化实践

本文介绍了携程火车票App如何通过模块AAR方案优化Gradle编译速度,旨在将全量编译时间降低至原时间的一半以下。文章详细阐述了编译速度的重要性,模块AAR的改造过程,包括构建测量、模块划分、依赖管理、aar发布、资源冲突解决等关键步骤,以及自动化构建方案的选择。通过实施这些优化措施,编译速度得到了显著提升,提高了开发和测试效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者简介

小明,携程移动开发工程师,专注于移动端框架和基建开发;

黄拉欢,携程移动开发经理,专注于移动端框架建设及性能调优。

一、 背景

Android 项目一般使用 Gradle 作为构建打包工具,随着业务需求的不断迭代,代码量提升的同时,Gradle 编译耗时也在不断的增长,而编译速度会直接决定开发流程效率的高低,影响面主要涉及到开发和测试阶段。 

对于火车票项目,经过长期的迭代过程导致模块众多工程庞大,优化前一次干净的全量编译时间可达到10m39s,造成开发和测试都需要长时间等待编译出包,严重影响到开发和测试的效率。因此对火车票 App 进行编译速度优化是件亟待解决的事情。

本次编译速度优化采用的方案是模块AAR方案, 优化目标为: 优化后一次干净的全量编译时间缩减为原来编译时间的50%以下。

二、 模块AAR方案介绍

Google 官网提供了优化构建速度的几种方案,但基本上效果都不明显。业内常用的编译加速方案一般有模块aar、控制增量编译、transform 优化、dexBuilder 优化等,其中有些方案侵入性太强或者 ROI 不高,模块aar方案侵入性低,预计收益显著,并且在大厂已经成为标配方案,可以作为本次编译优化的主方向。

AAR(Android ARchive)文件为 Android 库编译产物,其中包括 class、资源和清单文件,可用作 Android 应用模块依赖项。在Android 的构建流程中,源码编译时会将依赖的 aar 库和应用模块合并,在通过 apkbuilder 输出 apk 产物。

a8e477136e791d9c618befa7a8af0b83.jpeg

 图1:android的构建流程图

Android 项目大多都是多模块 Gradle 项目,构建项目时所有的子模块需要参与编译,导致全量编译时间冗长。实际上,对于大部分的开发人员来说,并不需要关注所有的模块,只需要负责功能相关的业务模块即可,因此这也提供了模块aar方案的切入点。

一般来说,aar方案都大同小异,涉及到模块aar发布,project和module依赖的切换,传递依赖的处理等几个方面。依赖切换我们采用的是 Gradle 官方文档中直接自定义依赖项的解析方案,通过定义依赖替换规则来更改依赖项;aar的发布使用的是maven-publish插件;传递依赖使用的mavenPom文件管理。

火车票项目经过多次模块化的重构后现有20个子模块,可将这些子模块独立打包aar并发布至远程maven仓库中,在需要打包时用aar替换项目的构建,就能将编译的时间给节省下来,以提高打包效率。 

530a9d1d7ddb0ed90d74fa46422e1ddd.jpeg

图2: 模块aar开发示意图

三、改造过程和遇到的问题

3.1 构建测量指标

良好的优化基于数据的呈现,首先第一步需要做的是分析构建的性能。此处采用的是官方提供的Gradle --profile选项的方式统计数据。基本步骤为:

  • gradlew clean在每个build之间执行干净编译,确保剖析完整的构建流程;

  • gradlew--profile--offline--rerun-tasks assembleFlavorDebug为某个变种task启用性能剖析,并确保gradle使用的是缓存依赖项,且强制gradle重新运行所有任务;

  • 构建完成后,会生成html格式的性能剖析报告,可用于分析构建信息和前后构建对比;

3.2 模块aar的改造

因存在单个模块发布的过程,模块间需要尽量的解耦,而解耦首先要进行模块的划分;其次,存在依赖关系的模块间不能互相使用资源,这会造成编译的失败,所以理清模块间的关系是当务之急。

同时,我们需意识到当我们完成 Gradle 脚本的编写后,我们得到的应该是一个app壳+其他模块的依赖树组合,可通过以下步骤进行改造:

1)模块间依赖树重构

首先分析项目各模块依赖关系, 可使用开源脚本工具projectDependencyGraph.gradle生成依赖图,示意图如下:

d144439641b3e85d5ef4fe8fe7f86b56.jpeg

 图3: 模块间依赖树混乱示意图

可直观的看出模块间依赖关系冗余,模块间的依赖不清晰,且基础库一般固定却呈现网状结构。故需对网状结构进行整理,将不经常改动的库直接发布远程,如下:

57e75e81ff64a7967f8ae0e1569f75b3.png

 图4: 模块间依赖树整理示意图

2)历史Gradle脚本重构

因历史原因,火车票项目的 Gradle 逻辑冗余繁琐,无统一管理略显臃肿。为提高 Gradle 的扩展性和灵活性,需要重构 Gradle 代码:

  • 对现有 Gradle 脚本按功能职责拆分成单个Gradle文件,需要使用的地方通过apply关键字应用即可;

  • 基于原插件com.android.application和com.android.library建立两套基准Gradle文件,统一控制不同类型的模块;

  • 抽取共有基础依赖项作为独立的 Gradle 文件,在subprojects中让所有模块都应用这些依赖项; 

  • 整理libs目录中本地的aar文件,移动至一个目录,统一依赖本地aar;

  • 建立app_bundle.json文件记录所有模块项目的信息及类型,便于模块项目是源码还是aar的切换控制;

  • 开发一套模拟参数机制,支持合并环境输入参数和local.properties参数 以便于 Gradle 功能的控制;如开启某个transform,指定哪些模块为aar等功能;

  • 重构 flavor 变体功能,动态控制 flavor 的创建。

3)发布和依赖aar版本的控制

当我们发布aar至远程仓库后,需要一个文件记录已发布模块aar的版本号,打包时通过此文件查找对应版本的aar,此处使用的是 MCD 平台采用 json 格式的 versionPath 文件。示意图如下:

892ff0fc4dedb193e1dacdfdf9814d22.jpeg

 图5:通过versionPath文件控制版本

模块aar的核心在于对依赖项的控制。我们想要的是一个app壳+部分源码模块+部分aar模块+其他依赖的结构,因此下面几点是要考虑到的:

  • 在所有子模块发布 maven 成功后,本地编译使用aar模块且环境参数没有传递 versionPath 参数时,构建脚本会自动拉取Mcd最新的versionPath.json链接作为默认的模块aar信息表。

  • 开发阶段需切换源码/aar,可对项目中所有模块进行分类,分别为:入口模块,源码模块,aar发布模块,可移除模块。另外,合并 gradle 环境参数和local.properties参数共同作为控制参数,然后修改app_bundle.json指定模块类型以达到灵活切换的目的。例如,定义一个sourceProject参数指定哪些模块作为源码构建。

  • 为模块之间的依赖关系建立一个json文件aar_config.json,便于查看和修改模块间依赖关系。

整个模块aar打包流程图如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值