查看原文
其他

开源|Zucker:Android APP模块化大小自动分析统计工具

胡昊 58技术 2022-03-15




开源项目专题系列(四)
1.开源项目名称:Zucker2.github地址:

https://github.com/wuba/Zucker

3.简介:Zucker是基于APP模块的,一个简单无侵害计算AAR独有大小的工具。




APP模块化大小自动分析工具是用来解构Apk大小构成的一种分析方式,它可以在不侵入用户代码的情况下,自动分析出当前项目的构建方式、依赖结构,通过计算独有依赖的大小从而精准的计算出一个模块在Apk中所占的大小。

Zucker于2020年3月份开源,它在行业内的特点如下:

  • 首款从代码结构模块化分析统计Apk大小的工具;

  • 利用用户自身的运行环境即可运行,具有一定的通用性;

  • 使用脚本即可实现,无需入侵用户代码,灵活性高、扩展性强;

  • 对比原有的统计方法,通过自动化,实现提效;

  • 控制Apk大小的措施工作左移,在集成阶段,甚至在更早的需求开发阶段发现问题,把控APP的质量风险;


为什么要使用Zucker

一款Android APP,应用市场的评分是用户衡量下载意愿的关键参考标准,绝大部分用户会在同类产品中愿意优先选择评分较高的APP。然而,调查显示:APP评分越高,用户下载量越高;APP安装包体积越小,用户下载量越高。那么,如何有效的控制APP安装包体积也是提高用户下载意愿的措施。下图是58APP近两年来的版本变化:





图一 58APP大小变化趋势

业界内的有很多人都致力于研究在当前的APP状态下,对其进行缩减优化,效果也很显著,典型的有:使用TinyPNG或者WebP优化图片资源、使用lint对于无用代码进行检测、AndResGuard7Zip压缩等等。我们发现,业界的普遍解决思路:在既有的APP上想办法缩减。

那么有没有什么方法,是可以在APP正式产出之前,就能对其监控呢?业内的一个典型的案例:大图片检测。对整个Apk进行解析,将图片资源按大小进行排序,找到前50个大图片,再使用TinyPNG或者WebP等优化手段。但是,仔细想一下,这个图片来源于哪个需求?这个需求来源于哪个模块?这个模块现在在Apk中的占比是多少?所以,仅仅从拆解Apk的角度去分析还远远不够。

我们知道,导致APP增大的最直接因素是需求的迭代,在现有的需求开发中,一个需求可能涉及到一个或者多个模块,比如:各个业务线,又或者是登录、认证、微聊、视频、定位等模块。假如认证模块引入了一个导致APP增大5MB的第三方库,当我们尝试使用业内普通的解决思路时,对其优化可能收效甚微。但是使用Zucker,由于它对于认证模块进行监控,它可以在该需求正式被打包进APP之前发出警告。从根源监控,将这个5M的第三方库用其他更小的库替代或者通过自实现的方式,都可以减少它的大小。

针对这个问题,我们也尝试了很多种办法,我们现在已经有基于需求层面来分析Apk的组成,它已经在58APP服役两年多的时间,监控了近30多个版本,反响非常好。

现在我们更进一步,利用模块化的思路,把Apk的统计分析工作进一步完善,将控制Apk大小的措施工作左移,在集成阶段,甚至更早的需求开发阶段提早发现代码质量问题,从而更好地保证APP的质量。


整体架构

Apk依赖的模块目前大多是由AAR组成,统计Apk中模块的大小本质上是在统计AAR的大小。由于Apk在打包过程中,会利用AIDLAAPT进行代码、资源压缩、混淆等操作,所以,只计算AAR的大小是不准确的,实际上需要统计的是AARApk中进行压缩、混淆之后的实际大小。此外,一个模块可以包含多个AAR,这时候需要对整个APP的依赖关系进行分析,从而找到目标AAR的独有依赖关系。最后,为了实现模块的自动分析统计,我们利用Python实现了对于项目工程轻量级、微侵入的统计方式。

Zucker的整体架构划分如下:自动化打包统计、依赖分析、目标AAR模拟:





图二 Zucker项目整体架构


自动化打包统计

对一个给定的目标工程避免产生侵害和改动,需要对源码工程做拷贝处理,然后利用该拷贝的工程计算依赖关系推理出独有依赖。再克隆一个工程用于替换独有依赖的目标AAR,再对克隆工程进行打包。最后,通过打出的两个克隆工程的包的大小差值,可以得到目标AAR的大小。


1. 克隆工程

为了实现无侵害计算,需要修改工程的gradle脚本来实现自动模拟AAR以及计算包大小,将原有工程进行克隆再分析模拟AAR及打包统计。使用Python脚本文件需要放置目标工程的同级目录,运行脚本在同级目录产生一个output目录,统一存放拷贝的工程文件。





图三 工程克隆

2. 编译过程

整个编译过程包括以下步骤:初始化工程→自动寻路查找工程入口→清除flavors→插入计算脚本→执行打包命令。通过此流程,可完整实现自动化寻路APP入口并修改配置来实现基础包打包和计算包体积大小任务。





图四 打包编译过程分析

依赖分析

在上文中提到获取用户输入的AAR,分析工程中各模块依赖关系。获取AAR的相互依赖关系,对于计算AAR大小起到关键作用。首先明确两个依赖关系概念。

  • 独有依赖:对于某一AAR,内部引用库仅被当前AAR所依赖,再无其他依赖关系,则这个库被称为AAR的独有依赖。

  • 公有依赖库:不同于独有依赖,一个库可能被多个AAR所引用,则这个库被称为公有依赖。

回到上面说的,若要统计AAR在APP包大小中的占比,除了它自己还不够,还要分析该AAR的独有依赖才能正确计算引入该AAR后的大小。


1. 依赖树转化

在项目的Gradle文件中可以找到该项目的依赖引用,项目执行./gradlew dependencies命令后会获取当前项目的依赖树结构,为了方便处理,将项目中的依赖库简化成A,B,C来表示,如图所示,A,A1,A2表示项目直接引用的AAR依赖。B,C是A的子依赖,同时C,D是A1的子依赖。根据上文所述,A的内部依赖B没有其他的被依赖关系,因此称B为A的独有依赖。另外的由于C同时被A和A1依赖,因此称C为公有依赖。





图五 依赖关系分析

理解依赖树结构特点,可将依赖树形结构转化成列表来显示。由上述分析可知一个依赖库可能被多个库依赖,产生依赖关系。同时依赖关系是单向有序的,箭头指向表示A依赖B:A是B的父依赖,B是A的子依赖。现挑出A,A1,C作为例子分析,将A,A1作为一个父节点,节点内部同时维护两个列表,父节点列表(parents)和子节点列表(children)。依次就可以将依赖树中所有的依赖关系放置在节点类型的数据结构中。另外root节点是工程总节点,它是A,A1和A2的父节点。通过此步骤,得到依赖列表,记录工程中所有依赖节点:





图六 依赖关系处理

2. 独有依赖分析

统计目标AAR大小时,不仅要统计目标AAR还要包括它的独有依赖。通过上文步骤获取到了项目所有AAR列表,在输入目标依赖名称后,我们在列表中遍历找到该目标,检查其子依赖然后获取最后的独有依赖,检查流程如下:

  1. 罗列目标依赖下的所有子依赖,并且遍历子依赖的子依赖,将它们记录到新列表中;

  2. 采用深度便算法来逐一去判断新列表中的依赖,判断其父依赖是否仅在该列表中,如果父依赖全部都在该列表中则保留,否则从列表中可删除该依赖;

  3. 经过步骤2后的筛选,剩下的节点则为目标依赖的独有依赖。

下面我们以上文图中的节点A1为例,将A1节点所有的子节点记录到列表。首先独有依赖包括它自身,故A1保留。C节点的父父节点有A和A1,A不在当前列表中,因此C不是独有依赖,将C移除。D的父节点仅有A1且在列表中,因此D是独有依赖。以此类推,在判断I节点时,由于C节点已被移除不在列表中,因此I也不是A1的独有依赖。节点独有依赖寻找流程:





图七 独有依赖的获取


目标AAR模拟

在项目打包生成Apk程中,会利用Gradle缓存特性,工程编译前,获取用户输入的目标AAR,脚本通过目标AAR名称在缓存目录下自动寻找。然后将本地的目标AAR文件进行模拟处理,打包时将该模拟后的AAR打入Apk中。模拟替换目标AAR流程:





图八 目标AAR的模拟

 我们知道AAR是二进制归档文件,也是压缩文件,只不过它是AAPT打包命令中的一个结果,通常会压缩:资源文件、类文件、系统文件等。所以找到该AAR后,我们进行“解剖”,步骤如下:

  1. 将目标AAR在当前目录下备份一份;

  2. 将AAR文件重命名变成.zip文件并进行解压缩;

  3. 遍历解压缩文件目录,当目标是文件时,判断其文件类型是否为.xml或.9.png,是则跳过;否则,将其文件大小置为0KB;

  4. 解压缩文件完全按照步骤3处理完成后,将其重新压缩为一个模拟的AAR文件,参与打包计算;

  5. 打包完成后,为了不影响后续打包任务,删除模拟的AAR将备份的AAR文件恢复;

修改build.gradle文件,使用Gradle的打包特性配置所有all*.exclude移除对应的group和module;

 

未来规划

目前已使用zucker统计了最近三个版本的模块大小,统计耗时已大幅度减少。基于上述流程可以快速完成统计目标AAR及其独有依赖在Apk包体积大小占比,接下来还有些方面亟需提高。

  1. AAR大小列表展示,基于图表形式展示,一目了然;

  2. 版本具体需求功能结合展示,并形成相关性文档;

 

如何贡献&问题反馈

本次开源只是Zucker贡献社区的一小步,我们诚挚地希望开发者向我们提出宝贵的意见和建议。您可以使用如下方式向我们反馈建议和问题:

https://github.com/wuba/Zucker提PR或者Issue。


作者介绍

胡昊,Android资深开发工程师-负责Zucker项目整体架构、技术选型,主要参与目标AAR的模拟替换;

曾鹏,Android资深开发工程师-负责Zucker项目工程自动化打包统计;

李贺,Android高级开发工程师-负责Zucker项目中依赖关系转化与Gradle缓存处理;

杨文蛟,Android高级开发工程师-负责Zucker项目数据统计与调优;


想了解更多开源项目信息?
与项目成员零距离交流?
扫码加小秘书微信
一切应有尽有




微信号 : jishu-58
添加小秘书微信后由小秘书拉您进项目交流群

END


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存