哔哩哔哩官网下载最新版本(哔哩哔哩Android打包优化与云编译)

首页教程更新时间:2023-05-19 03:56:56

本期作者

夏秋垒-移动技术部工程效率组资深开发工程师

01 背景介绍

B站使用大仓模式进行源码依赖管理,大仓模有优势也有挑战,截止目前为止 Android 仓库子模块有620 ,开发人员150 。

本地开发存在编译慢、机器发热、卡死、阻塞开发等问题。介于此前移动端已有庞大的 CI 构建集群,我们探索出一种新的开发编译方式——云编译。

02 原理简介

俗话说性能不够硬件来凑,得益于公司的高配置服务器资源,我们移动端可以很方便地使用云端资源提高编译速度。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(1)

通过 git 同步本地与远端代码基点,结合 diff 文件还原本地开发中产生的差异改动,然后编译出与本地无差别的 APK 产物。

03 开荒时代

使用 Docker 自定义的 Android 构建镜像,可快速复制多个打包机器实例。

开发同学本地使用提供的云编译命令行工具执行编译动作,命令行工具开始计算 commit、生成 diff,然后合成打包请求发送到远端。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(2)

远端收到编译请求以后,开始解析指令、下载(同步)代码、应用补丁、执行编译、返回执行结果与编译产物。

编译成功后,本地下载编译产物(APK文件),然后安装并启动。

整个流程与本地开发的差别为本地的代码需要同步到远程,打包的操作放在远程,远程执行成功后需下载产物。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(3)

新开发方式有优势也有不足。

优势:

不足:

04 持续优化与VIP模式

前期构建实例数为10个,可满足一部分人使用,一段时间后大家觉的这种模式还不错,相对于本地编译,编译速度还是有明显的提升。

随着使用人数开始增多,开始出现机器竞争、机器繁忙、任务等待等问题。大家吐槽调侃希望可以开通 VIP 模式,独占某一台机器或者提高任务优先级。

原先的架构,客户端与服务器之间只有一层 SLB 做反向代理,进行随机转发。前后两次打包任务可能分配的是不同机器,导致需要重新下载代码,增加打包时间,也无法复用上一次的增量编译缓存。

于是我们针对原先的架构模式,做了以下调整,并对打包流程和速度进行了优化。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(4)

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(5)

优化打包速度,首先必须掌握整个打包流程与机制;其次需要衡量维度以及数据统计记录,方便后期数据对比,指导优化方向;最后为了满足日常问题的排查,需要一个管理后台记录打包日志、监控实例状态、修改配置与维护。

4.1 流程分析

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(6)

打包流程主要包括打包环境准备、服务启动、任务执行。以下针对各个阶段列举具体的优化措施。

4.2 优化措施

前期每个服务配置为 10C50G,可以保证单人独占,效果明显。随着使用人数增多,会出现机器繁忙问题,增加机器数量与提供并发量迫在眉睫。

后期改成高低配两种服务,10C50G 为单人模式,30C100G 为多人模式,最大可以支持3人并发打包,多人模式也可以共享缓存,加速效果明显。

Docker 镜像下载,Android SDK & NDK, Gradle,Maven 等可以使用国内源或者公司内部源来加速,效果显著。

举例,项目中一般会有 Gradle 各个版本下载,可以放在公司内部存储,内网速度一般为千兆网络,下载速度较快。

默认 Docker 实例会直接启动打包,每次更新服务都需要更新 Docker 镜像版本,服务实例重启会导致所有的代码、编译缓存、SDK 等丢失。对打包速度较大,所以尽可能的减少服务重启次数。

但是如果遇到线上问题,发布版本是不可以避免,减少服务重启明显不科学。通过流程优化,使服务支持热更新,从而避免了重启Docker 实例,相关缓存也不会丢失,可继续使用。

服务启动时,可以先挂起,不对外提供服务,系统内部进行预热处理,如预先下载或者更新,执行打包若干次,等预热完成后,再进行打包,也打包速度会相对较高的提升。

相对于 CI 服务,每次编译都会拉取代码,然后再进行编译。但是云编译不不适合此方式,B站大小仓代码总量大概为5个G,按照千兆网络来算,全部拉取也需要几分钟。

可使用 git 提供 worktree 的模式,可以预先拉去所有代码,当需要打包时,可以快速切换代码。

云编译根据用户名、机器设备号、本地工作目录三个维度计算一个hash,映射远程工作目录路径,这样每次可以快速还原本地代码,执行打包操作,完成后不删除代码供下次使用。

Gradle 提供一种 Remote Cache 机制,需要一个缓存服务器,第一次编译完成后,上传到缓存服务器,再次打包,如果代码没有修改,可以直接使用下载并使用缓存。

云编译提供管理后台与网关,可以根据用户打包频次,合理分配机器。用户每次执行打包,都会分配到指定机器的指定目录,提高缓存使用率,避免机器出现抢占情况。

项目开发一般为 debug 模式,APK 是未经优化的大小约为150M,开发同学使用的是 MacBookPro,大部分使用的是 Wi-Fi(百兆网络),则下载需要15s左右。切换成有线网络(千兆网络),则下载只需1-2s即可。

4.3 优化结果

随机模式:冷机打包 5-10min, 热机打包 3-7min,平均打包速度 5min。

VIP模式:冷机打包 4-8min, 热机打包 1.5-4min,平均打包速度 3min,极端情况20s可出包。

4.4 系统展示

打包记录

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(7)

实例列表

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(8)

在线日志

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(9)

05 分布式编译

5.1 需求分析

随着业务发展子模块变多,部分任务执行时间越来越长,影响整体编译时长,编译时间具有劣化的趋势。常见耗时任务有 DexBuild 与 DexMerge,如下图所示为某次首次冷编译(本机无缓存),其中 dex 相关任务时长约占 1/3。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(10)

再次编译的时候,DexBuild 有明显的下降,但是 DexMerge 任然需要不少的时间。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(11)

从官网的编译流程图来,dex文件就是从jar或class文件通过指令转换而来,同时 Android Sdk 中也提供 d8 命令来手动执行。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(12)

云编译系统是一个编译集群,每次一个编译记录只能占用一台主机,是否可以把一些比较耗时长的任务拆分到其他空闲机器协同来编译,然后再回传编译结果。

以下为 AGP 中源码,通过传入的参数进行赋值准备,最后执行 D8.run(), 而 D8.run() 可以在SDK d8 工具中找到。

package com.android.builder.dexing; // 部分代码有删减处理,不代表全部源码 final class D8DexArchiveBuilder extends DexArchiveBuilder { @Override public void convert( @NonNull Stream<ClassFileEntry> input, @NonNull Path output, @Nullable DependencyGraphUpdater<File> desugarGraphUpdater) throws DexArchiveBuilderException { D8DiagnosticsHandler d8DiagnosticsHandler = new InterceptingDiagnosticsHandler(); try { D8Command.Builder builder = D8Command.builder(d8DiagnosticsHandler); // .... // 部分代码有删减处理,不代表全部源码 // .... if (dexParams.getWithDesugaring()) { builder.addLibraryResourceProvider(dexParams.getDesugarBootclasspath().getOrderedProvider()); builder.addClasspathResourceProvider(dexParams.getDesugarClasspath().getOrderedProvider()); if (dexParams.getCoreLibDesugarConfig() != null) { builder.addSpecialLibraryConfiguration(dexParams.getCoreLibDesugarConfig()); if (dexParams.getCoreLibDesugarOutputKeepRuleFile() != null) { builder.setDesugaredLibraryKeepRuleConsumer( new FileConsumer(dexParams.getCoreLibDesugarOutputKeepRuleFile().toPath())); } } if (desugarGraphUpdater != null) { builder.setDesugarGraphConsumer(new D8DesugarGraphConsumerAdapter(desugarGraphUpdater)); } } else { builder.setDisableDesugaring(true); } D8.run(builder.build(), MoreExecutors.newDirectExecutorService()); } catch (Throwable e) { throw getExceptionToRethrow(e, d8DiagnosticsHandler); } } }

可以在此增加一个 Hook 点,把需要执行 DexBuild 操作的文件分发到空闲的机器上面,然后远程执行 d8 命令,执行成功回传文件,然后再放在目标位置。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(13)

Hook部分代码

/** * @see com.android.builder.dexing.D8DexArchiveBuilder.convert */ private fun hookBuilder() { val dst = pool.get("com.android.builder.dexing.D8DexArchiveBuilder") if (dst.isFrozen) { log.error("clazz ${dst.simpleName} is frozen") return } dst.getDeclaredMethod("convert").aopReplace(object : MethodInvokeCallback { override fun invoke(self: Any, method: String, args: List<Any?>) { // XbuildDexBuilder 再调用 MyD8DexArchiveBuilder XbuildDexBuilder().convert( self, args[0] as Stream<ClassFileEntry>, args[1] as Path, args[2] as DependencyGraphUpdater<File>?, ) } }) } /** * @see com.android.builder.dexing.D8DexArchiveBuilder */ public final class MyD8DexArchiveBuilder extends DexArchiveBuilder { @Override public void convert( @NonNull Stream<ClassFileEntry> input, @NonNull Path output, @Nullable DependencyGraphUpdater<File> desugarGraphUpdater) throws DexArchiveBuilderException { try { // .... // 部分代码有删减处理,不代表全部源码 // .... // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DexBuildArgs args = new DexBuildArgs(dexParams, input, output, entryCount.get(), entrySize.get()); args.getEntryList().addAll(list); MyD8DexArchiveBuilderProxy.run(builder, MoreExecutors.newDirectExecutorService(), args); // D8.run(builder.build(), MoreExecutors.newDirectExecutorService()); // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> } catch (Throwable e) { throw getExceptionToRethrow(e, d8DiagnosticsHandler); } } }

经过测试,在常规使用情况下(非高峰,否则无空闲机器),可以有效的降低任务时长。

同理 DexMerge 也可以做类似的 Hook,只不过 merge 操作是把 m 个 dex 文件合并成 n 个 dex文件。

项目中的 merge 的输入文件将近1000个,并且每次修改代码,就有可能导致整个 merge 任务重新执行,无法复用缓存。

基于实际场景,采用分治法,将输入文件采用取余方式分组,每组再分发到远程机器上执行,执行成功后再回传结果。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(14)

Merge 操作经过分组合并有效的降低了任务执行时间,同时分组后支持自定义缓存。

实际项目中分组为21个,一般情况下,开发同学只是修改局部部分文件,再次编译的时候,只有其中1-2个分组有变动,只需要重执行有过变动的分组即可,提高了执行效率。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(15)

相关日志

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(16)

图中所示,合并的文件有971个,分成21组,其中19组使用了缓存,本次执行消耗5.8s。

编译任务与远程任务

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(17)

5.2 其他

实操过程中发现大部分情况文件越大 dex 执行时间越长,网络传输是有损耗的,所以并不是所有的 dex 操作都值得分发到远程,只有超过一定阈值的时候,才会分发到远程。

通过统计与计算 build 过程输入文件需要大于1M, merge 过程输入文件需大于 3M,满足这样条件分发到远程编译会有不小的提速收益。

根据上一条,dex执行时间与文件大小相关,实操过程发现部分jar文件非常大,比如R.java合并后的 jar 有将近200M, 可以通过切片方式,把一个大的jar文件分割成若干较小的文件,然后再进行 d8 处理,消耗时间会短很多。

d8 实际上是一个 shell 执行 jar文件的方式,可以通过 GraalVM 来转成本地可执行文件,也能有一定幅度的性能提升。

原生 DexMerge 任务缓存命中率差,并且执行缓存过程也消耗不少时间,可以选择性设置禁止缓存。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(18)

如上所述,部分原生Gradle缓存机制效果差,DexBuild 与 DexBuild 操作可以采取自定义缓存方式,远程在收到编译任务可以先判断是否有缓存,再做具体执行,同时再把执行结果缓存起来用来复用。

实操过程中,d8 编译的结果有可能会有一些异常情况,可以采取单独配置代码目录与 GradleUserHome目录,正常编译模块与分布式编译模式分开管理,方便区分以及快速降级。

前期功能不稳定,需手动开启。经过一个月测试功能比较稳定,已经默认开启。如需关闭,手动主动关闭。

5.3 结果

经过一段时间观察,目前功能稳定,有效的的解决dex执行缓慢问题,同时整体编译速度维持在正常水平。

06 功能演示

本地打包命令为 ./gradlew :app:assembleDebug -q -s,云编译也类似 hub -b ":app:assembleDebug -q -s" --vip。

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(19)

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(20)

哔哩哔哩官网下载最新版本,哔哩哔哩Android打包优化与云编译(21)

06 未来规划

通过服务器强大的性能,模拟多个模拟器或设备,用于开发、调试、测试。云端设备可以快速复制与销毁,可以用于兼容性测试与兼容性开发。

最近推出Fleet, 以及 Visual Studio Code 和 IDEA 的 Remote Development,似乎远程开发是个趋势。结合云端设备,或许也会有着不一样的开发体验。

作者:夏秋垒

来源:微信公众号:哔哩哔哩技术

出处:https://mp.weixin.qq.com/s/xls_AL9IyR3580zz8CfCOA

,
图文教程
相关文章
热门专题
推荐软件
奇热小说
奇热小说
下载
QQ2019手机版
QQ2019手机版
下载
王者荣耀
王者荣耀
下载
百度浏览器迷你版
百度浏览器迷你版
下载
2345浏览器手机版
2345浏览器手机版
下载
网易邮箱
网易邮箱
下载
爱奇艺
爱奇艺
下载
网易云音乐
网易云音乐
下载
WPSOffice
WPSOffice
下载
优酷
优酷
下载
谷歌浏览器(Chrome)
谷歌浏览器(Chrome)
下载
迅雷看看播放器
迅雷看看播放器
下载
UC浏览器
UC浏览器
下载
QQ音乐
QQ音乐
下载
阿里旺旺买家版v9.12.10C官方版
阿里旺旺买家版v9.12.10C官方版
下载
360安全卫士v12.1官方版
360安全卫士v12.1官方版
下载
猜你喜欢
21亿级变态传奇
21亿级变态传奇
下载
移动岛
移动岛
下载
海悦家用菜谱
海悦家用菜谱
下载
养生固本健康测试
养生固本健康测试
下载
杰拉网咖app
杰拉网咖app
下载
火柴人联盟2测试版
火柴人联盟2测试版
下载
收获日2汉化补丁v21.0
收获日2汉化补丁v21.0
下载
兼职狐
兼职狐
下载
二战战略指挥官九游版
二战战略指挥官九游版
下载
宾阳吧
宾阳吧
下载
宅鸟生活
宅鸟生活
下载
NetworkPacketRetransmissionTool(网络数据包重定向工具)v1.1.0.1中文版
NetworkPacketRetransmissionTool(网络数据包重定向工具)v1.1.0.1中文版
下载
维多利亚2中文版绿色完整版
维多利亚2中文版绿色完整版
下载
卡普的奇妙生活
卡普的奇妙生活
下载
靓妆频道
靓妆频道
下载
TextItv1.0.12官方版
TextItv1.0.12官方版
下载