大牛耗时一年:深刻探索 Android 包体积优化,共三万字建议收藏上

因为文章特别长,建议先收藏再阅读。java

在 Android 性能优化的知识体系当中,包体积优化一直被排在优先级比较低的位置,从而致使不少开发同窗对自身应用的大小并不重视。在项目发展的历程中,通常可划分为以下三个阶段:
初创期 => 成长期 => 成熟期
一般来讲,当应用处于成长期的中后阶段时,才会考虑去作系统的包体积优化,所以,只有在这个阶段及以后,包体积优化带来的收益才是可观的。android

那么,包体积优化可以给咱们带来哪些 收益 呢?如何全面对应用的包体积进行 系统分析 及 针对性优化呢?在这篇文章中,咱们将一块儿进行深刻地分析与探索。git

一、瘦身优化及 Apk 分析方案介绍
一、瘦身优点github

咱们首先来介绍下,为何咱们须要作 APK 的瘦身优化?web

主要有 三个方面 的缘由:算法

(1)下载转化率数据库

  • APK 瘦身优化在实际的项目中优先级是比较低的,由于作了以后它的好处不是那么明显,尤为是那些尚未到 稳按期 的项目,咱们都知道,App 的发展历程是从 项目初期 => 成长期 => 稳按期,对于处于 发展初期与成长期 的项目而言,可能会作 启动优化、卡顿优化,可是通常不会作 瘦身优化,瘦身优化 最主要的好处是对应用 下载转化率 的影响,它是 App 业务运营的重要指标之一,在项目精细化运营的阶段是很是重要的。
  • 由于若是你的 App 与其它同类型的 App 相比 Apk 体积要更小的话,那么你的 App 下载率就可能要高一些。并且,包体积越小,用户下载等待的时间也会越短,因此下载转换成功率也就越高。因此,安装包大小与下载转化率的关系 大体是成反比 的,即安装包越大,下载转换率就越小。
  • 一个 80MB 的应用,用户即便点了下载,也可能由于网络速度慢、忽然反悔致使下载失败。而对于一个 20MB 的应用,用户点了下载以后,在犹豫要不要下的时候可能就已经下载完了。
  • 并且,如今不少大型的 App 通常都会有一个 Lite 版本的 App,这个也是出于下载转化率方面的考虑。

2应用市场xcode

  • Google Play 应用市场强制要求超过 100MB 的应用只能使用 APK 扩展文件方式 上传。当使用 APK 扩展文件方式 上传时,Google Play 会为咱们的应用 托管 扩展文件,并将其 免费提供 给设备。
  • 扩展文件将保存到设备的共享存储位置(SD 卡或可安装 USB 的分区;也称为“外部”存储),应用能够在其中访问它们。在大多数设备上,Google Play 会在下载 APK 的同时下载扩展文件,所以应用在用户首次打开时便拥有了所需的一切。
  • 可是,在某些状况下,咱们的应用必须在应用启动时从 Google Play 下载文件。若是您想避免使用扩展文件,而且想要应用程序的下载大小大于100 MB,则应该使用 Android App Bundles 上传应用程序,此时应用程序最多可提供150 MB的压缩下载大小。
  • Android App Bundles 就是 Android 应用程序捆绑包,它可以让 App 以 添加动态功能模块的方式 去解决 APK 大小较大的问题。以下,就是由一个基本模块和两个动态功能模块组成的 Android App Bundle APK 的组成结构图:

三、渠道合做商的要求性能优化

  • 此外,还有一个缘由,当咱们的 App 作大以后,可能须要跟各个手机厂商合做预装,这些 渠道合做商会对你的 App 作详细的要求,只有达到相应的要求后才容许你的 App 预装到手机上。并且,越大的 App 其单价成本也会越高。因此,瘦身也是咱们项目作大以后必定会遇到的一个问题。
  • 体积过大对 App 性能的影响

此外,包体积除了会影响 应用的下载转化率 以外,主要还会对 App 三个方面 的性能有必定的影响,以下所示:服务器

1)安装时间:好比 文件拷贝、Library 解压,而且,在编译 ODEX 的时候,特别是对于 Android 5.0 和 6.0 系统来讲,耗费的时间比较久,而 Android 7.0 以后有了 混合编译,因此还能够接受。最后,App 变大后,其 签名校验 的时间也会变长。

2)运行时内存:Resource 资源、Library 以及 Dex 类加载都会占用应用的一部份内存。

3)ROM 空间:若是应用的安装包大小为 50MB,那么启动解压以后极可能就已经超过 100MB 了。而且,若是 闪存空间不足,极可能出现“写入放大”的状况,它是闪存和固态硬盘(SSD)中一种不良的现象,闪存在可从新写入数据前必须先擦除,而擦除操做的粒度与写入操做相比低得多,执行这些操做就会屡次移动(或改写)用户数据和元数据。

所以,要改写数据,就须要读取闪存某些已使用的部分,更新它们,并写入到新的位置,若是新位置在以前已被使用过,还需连同先擦除;因为闪存的这种工做方式,必须擦除改写的闪存部分比新数据实际须要的大得多。即最终可能致使实际写入的物理资料量是写入资料量的多倍。

二、APK 组成

咱们都知道,Android 项目最终会编译成一个 .apk 后缀的文件,实际上它就是一个 压缩包。所以,它内部还有不少不一样类型的文件,这些文件,按照大小,共分为以下几类:

1)代码相关:classes.dex,咱们在项目中所编写的 java 文件,通过编译以后会生成一个 .class 文件,而这些全部的 .class 文件呢,它最终会通过 dx 工具编译生成一个 classes.dex。

2)资源相关:res、assets、编译后的二进制资源文件 resources.arsc 和 清单文件 等等。res 和 assets 的不一样在于 res 目录下的文件会在 .R 文件中生成对应的资源 ID,而 assets 不会自动生成对应的 ID,而是经过 AssetManager 类的接口来获取。此外,每当在 res 文件夹下放一个文件时,aapt 就会自动生成对应 id 并保存在 .R 文件中,但 .R 文件仅仅只是保证编译程序不会报错,实际上在应用运行时,系统会根据 ID 寻找对应的资源路径,而 resources.arsc 文件就是用来记录这些 ID 和 资源文件位置对应关系 的文件。

3)So 相关:lib 目录下的文件,这块文件的优化空间其实很是大。

此外,还有 META-INF,它存放了应用的 签名信息,其中主要有 3个文件,以下所示:

MANIFEST.MF:其中每个资源文件都有一个对应的 SHA-256-Digest(SHA1) 签名,MANIFEST.MF 文件的 SHA256(SHA1) 通过 base64 编码的结果即为 CERT.SF 中的 SHA256(SHA1)-Digest-Manifest 值。

CERT.SF:除了开头处定义的 SHA256(SHA1)-Digest-Manifest 值,后面几项的值是对 MANIFEST.MF 文件中的每项再次 SHA256(SHA1) 通过 base64 编码后的值。

CERT.RSA:其中包含了公钥、加密算法等信息。首先,对前一步生成的MANIFEST.MF使用了SHA256(SHA1)-RSA算法,用开发者私钥签名。而后,在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,若是相符,则代表内容没有被修改。

代码瘦身方案探索

在讲解如何对 Dex 进行优化以前,可能有不少同窗对 Dex 尚未足够的了解,这里咱们就先详细地了解下 Dex。

一、Dex 探秘
1)Dex 是 Android 系统的可执行文件,包含 应用程序的所有操做指令以及运行时数据。由于 Dalvik 是一种针对嵌入式设备而特殊设计的 Java 虚拟机,因此 Dex 文件与标准的 Class 文件在结构设计上有着本质的区别。

2)当 Java 程序被编译成 class 文件以后,还须要使用 dx 工具将全部的 class 文件整合到一个 dex 文件中,这样 dex 文件就将原来每一个 class 文件中都有的共有信息合成了一体,这样作的目的是 保证其中的每一个类都可以共享数据,这在必定程度上 下降了信息冗余,同时也使得 文件结构更加紧凑。

与传统 jar 文件相比,Dex 文件的大小可以缩减 50% 左右。关于 Class 文件与 Dex 文件的结果对比图以下所示:

若是想深刻地了解 Dex 文件格式,能够参见Google 官方教程 - Dex格式。

Dex 通常在应用包体积中占据了很多比重,而且,Dex 数量越多,App 的安装时间也会越长。因此,优化它们能够说是 重中之重。下面,咱们就来看看有哪些方式能够优化 Dex 这部分的体积。

二、ProGuard
混淆这里就不赘述了,你们应该比较熟悉,原文有比较详细的介绍。

三、D8 与 R8 优化
D8 优化
D8 的 优化效果 总的来讲能够归结为以下 四点:

Dex的编译时间更短。
dex文件更小。
D8 编译的 .dex 文件拥有更好的运行时性能。
包含 Java 8 语言支持的处理。
在 Android Studio 3.0须要主动在 gradle.properties 文件中新增:

android.enableD8 = true
Android Studio 3.1 或以后的版本 D8 将会被做为默认的 Dex 编译器。

R8 优化
R8 官方文档(目前已经开源)

https://r8.googlesource.com/r8

R8 是 Proguard 压缩与优化部分的替代品,而且它仍然使用与 Proguard 同样的 keep 规则。若是咱们仅仅想在 Android Studio 中使用 R8,当咱们在 build.gradle 中打开混淆的时候,R8 就已经默认集成进 Android Gradle plugin 中了。
若是咱们当前使用的是 Android Studio 3.4 或 Android Gradle 插件 3.4.0 及其更高版本,R8 会做为默认编译器。不然,咱们 必需要在 gradle.properties 中配置以下代码让 App 的混淆去支持 R8,以下所示:

android.enableR8=true
android.enableR8.libraries=true

那么,R8 与混淆相比优点在哪里呢?

  • ProGuard 和 R8 都应用了基本名称混淆:它们 都使用简短,无心义的名称重命名类,字段和方法。他们还能够 删除调试属性。

可是,R8 在 inline 内联容器类中更有效,而且在删除未使用的类,字段和方法上则更具侵略性。例如,R8 自己集成在 ProGuard V6.1.1 版本中,在压缩 apk 的大小方面,与 ProGuard 的 8.5% 相比,使用 R8 apk 尺寸减少了约 10%。而且,随着 Kotlin 如今成为 Android 的第一语言,R8 进行了 ProGuard 还没有提供的一些 Kotlin 的特定的优化。

  • 从表面上看,ProGuard 和 R8 很是类似。它们都使用相同的配置,所以在它们之间进行切换很容易。放大来看的话,它们之间也存在一些差别。R8 能更好地内联容器类,从而避免了对象分配。可是 ProGuard 也有其自身的优点,具体有以下几点:

1)、ProGuard 在将枚举类型简化为原始整数方面会更增强大。它还传递常量方法参数,这一般对于使用应用程序的特定设置调用的通用库颇有用。

ProGuard 的屡次优化遍历一般能够产生一系列优化。例如,第一遍能够传递一个常量方法参数,以便下一遍能够删除该参数并进一步传递该值。删除日志代码时,屡次传递的效果尤为明显。ProGuard 在删除全部跟踪(包括组成日志消息的字符串操做)方面更有效。

2)、ProGuard 中应用的模式匹配算法能够识别和替换短指令序列,从而提升代码效率并为更多优化打开了机会。在优化遍历的顺序中,尤为是数学运算和字符串运算可从中受益。

三、最后,ProGuard 具备独特的能力来优化使用 GSON 库将对象序列化或反序列化为 JSON 的代码。该库严重依赖反射,这很方便,但效率低下。而 ProGuard 的优化功能能够 经过更高效,直接的访问方式 来代替它。

R8 优化实战
接下来,咱们就来看看 Awesome-WanAndroid 使用 R8 后,APK 体积的变化,以下图所示:

  • 能够看到,相较于仅使用混淆后的 APK 而言,大小减小了 0.1MB,Dex 部分的优化效果大概为 5%,APK 总体的压缩效果也有 1.5% 左右。
  • 虽然从减小的 APK 大小来看,0.1MB 不多,可是比例并不小,若是你负责的是一个像微信、淘宝等规模的 App,它们的体积通常都将近 100MB,使用 R8 后也能减少 1.5MB 的大小。
  • D8 与 R8 的做用很是强大,而 Jake Wharton 大神最近一年多也在研究 D8 与 R8 的知识,若是想对 D8 与 R8 的实现细节有更多地了解,能够看看他的 我的博客。

四、去除 debug 信息与行号信息
在讲解什么是 deubg 信息与行号信息以前,咱们须要先了解 Dex 的一些知识。

咱们都知道,JVM 运行时加载的是 .class 文件,而 Android 为了使包大小更加紧凑、运行时更加高效就发明了 Dalvik 和 ART 虚拟机,两种虚拟机运行的都是 .dex 文件,固然 ART 虚拟机还能够同时运行 oat 文件。

因此 Dex 文件里的信息内容和 Class 文件包含的信息是同样的,不一样的是 Dex 文件对 Class 中的信息作了去重,一个 Dex 包含了不少的 Class 文件,而且在结构上有比较大的差别,Class 是流式的结构,Dex 是分区结构,Dex 内部的各个区块间经过 offset 来进行索引。

为了在应用出现问题时,咱们能在调试的时候去显示相应的调试信息或者上报 crash 或者主动获取调用堆栈的时候能经过 debugItem 来获取对应的行号,咱们都会在混淆配置中加上下面的规则:

-keepattributes SourceFile, LineNumberTable
这样就会保留 Dex 中的 debug 与行号信息,此时的 Dex 结构图 以下所示:

大牛耗时一年:深刻探索 Android 包体积优化,共三万字建议收藏上
从图中能够看到,Dex 文件的结构主要分为 四大块:header 区,索引区,data 区,map 区。而咱们的 debug 与行号信息就保存在 data 区中的 debugItems 区域。

而 debug_items 里面主要包含了 两种信息,以下所示:

调试的信息:包含函数的参数和全部的局部变量。
排查问题的信息:包含全部的指令集行号与源文件行号的对应关系。
根据 Google 官方的数据,debugItem 通常占 Dex 的比例有 5% 左右,若是咱们能去除 debug 与行号信息,就能更进一步对 Dex 进行瘦身,可是会失去调试信息的功能,那么,有什么方式能够去掉 debugItem,同时又能让 crash 上报的时候能拿到正确的行号呢?

咱们能够尝试直接修改 Dex 文件,保留一小块 debugItem,让系统查找行号的时候指令集行号和源文件行号保持一致,这样任何监控上报的行号都直接变成了指令集行号。

每个方法都会有一个 debugInfoItem,每个 debuginfoItem 里面都有一个指令集行号和源文件行号的映射关系,这了咱们直接把多余的 debugInfoItem 所有删掉,只保留了一个 debugInfoItem,这样全部的方法都会指向同一个 debugInfoItem,而且这个 debugInfoItem 中的指令集行号和源文件行号保持一致,这样无论用什么方式来查找行号,拿到的都是指令集行号。

须要注意的是,采用这种方案 须要兼容全部虚拟机的查找方式,所以 仅仅保留一个 debugInfoItem 是不够的,须要对 debugInfoItem 进行分区,而且 debugInfoItem 表不能太大。

关于如何去除 Dex 中的 Debug 信息是经过 ReDex 的 StripDebugInfoPass 来完成的,其配置以下所示:

{
    "redex" : {
        "passes" : [
            "StripDebugInfoPass",
            "RegAllocPass"
        ]
    },
    "StripDebugInfoPass" : {
        "drop_all_dbg_info" : false,
        "drop_local_variables" : true,
        "drop_line_numbers" : false,
        "drop_src_files" : false,
        "use_whitelist" : false,
        "cls_whitelist" : [],
        "method_whitelist" : [],
        "drop_prologue_end" : true,
        "drop_epilogue_begin" : true,
        "drop_all_dbg_info_if_empty" : true
    },
    "RegAllocPass" : {
        "live_range_splitting": false
    }
}

关于 debuginfo 的实战咱们下面立刻会开始,在此以前,咱们先讲讲 Dex 分包中的另外一个优化点。

五、Dex 分包优化
Dex 分包优化原理
当咱们的 APK 过大时,Dex 的方法数就会超过65536个,所以,必须采用 mutildex 进行分包,可是此时每个 Dex 可能会调用到其它 Dex 中的方法,这种 跨 Dex 调用的方式会形成许多冗余信息,具体有以下两点:

多余的 method id:跨 Dex 调用会致使当前dex保留被调用dex中的方法id,这种冗余会致使每个dex中能够存放的class变少,最终又会致使编译出来的dex数量增多,而dex数据的增长又会进一步加剧这个问题。
其它跨dex调用形成的信息冗余:除了须要多记录被调用的method id以外,还需多记录其所属类和当前方法的定义信息,这会形成 string_ids、type_ids、proto_ids 这几部分信息的冗余。
为了减小跨 Dex 调用的状况,咱们必须 尽可能将有调用关系的类和方法分配到同一个 Dex 中。可是各个类相互之间的调用关系是很是复杂的,因此很难作到最优的状况。

所幸的是,ReDex 的 CrossDexDefMinimizer 类分析了类之间的调用关系,并 使用了贪心算法去计算局部的最优解(编译效果和dex优化效果之间的某一个平衡点)。

https://github.com/facebook/r...

使用 "InterDexPass" 配置项能够把互相引用的类尽可能放在同个 Dex,增长类的 pre-verify,以此提高应用的冷启动速度。

在 ReDex 中使用 Dex 分包优化跨 dex 调用形成的信息冗余的配置代码以下所示:

{
    "redex" : {
        "passes" : [
            "InterDexPass",
            "RegAllocPass"
        ]
    },
    "InterDexPass" : {
        "minimize_cross_dex_refs": true,
        "minimize_cross_dex_refs_method_ref_weight": 100,
        "minimize_cross_dex_refs_field_ref_weight": 90,
        "minimize_cross_dex_refs_type_ref_weight": 100,
        "minimize_cross_dex_refs_string_ref_weight": 90
    },
    "RegAllocPass" : {
        "live_range_splitting": false
    },
    "string_sort_mode" : "class_order",
    "bytecode_sort_mode" : "class_order"
}

为了衡量优化效果,咱们可使用 Dex 信息有效率 这个指标,公式以下所示:

git clone https://github.com/facebook/redex.git
cd redex

若是 Dex 有效率在 80% 以上,就说明基本合格了。

使用 ReDex 进行分包优化、去除 debug 信息及行号信息

下面,咱们就使用 Redex 来对上一步生成的 app-release-proguardwithr8.apk 进行进一步的优化。(macOS 环境下)

https://fbredex.com/docs/inst...

一、首先,咱们须要输入一下命令去去安装 Xcode 命令行工具
xcode-select --install
二、而后,使用 homebrew 安装 redex 项目使用到的依赖库

ANDROID_SDK=/Users/quchao/Library/Android/sdk redex --sign -s wan-android-key.jks -a wanandroid -p wanandroid -c ~/Desktop/interdex_stripdebuginfo.config -P app/proguard-rules.pro -o ~/Desktop/app-release-proguardwithr8-stripdebuginfo-interdex.apk ~/Desktop/app-release-proguardwithr8.apk

须要注意的是吗,2020年2月10号版本源码的 redex 须要的 boost 版本为 V1.71 及以上,当你使用 brew install boost 安装 boost 时可能获取到的 boost 版本会低于 V1.71,此时多是 brew 版本须要更新,使用 brew upgrade 去更新 brew 仓库的版本 或者能够直接从 boost 官网下载最新的 boost 源码 至 /usr/local/Cellar/ 目录下,我当前使用的是 boost V1.7.2源码下载地址 中的 boost_1_72_0.zip。

https://dl.bintray.com/boosto...

从 深刻探索 Android 启动优化 时就说起到了 Redex 的类重排优化,当时卡在这一步,因此一直无法真正完成类的重排优化。

三、接着,从 Github 上获取 ReDex 的源码并切换到 redex 目录下
git clone https://github.com/facebook/r...
cd redex
四、下一步,使用 autoconf 和 make 去构建 ReDex

若是你使用的是 gcc, 请使用 gcc-5

autoreconf -ivf && ./configure && make -j4
sudo make install
五、而后,配置 Redex 的 config 代码
在 Redex 在运行的时候,它是根据 redex/config/default.config 这个配置文件中的通道 passes 中添加不一样的优化项来对 APK 的 Dex 进行处理的,咱们能够参考 redex/config/default.config 这个默认的配置,里面的 passes 中不一样的配置项都有特定的优化。

为了优化 App 的包体积,咱们再加上 interdex_stripdebuginfo.config 中的配置项去删除 debugInfo 和减小跨 Dex 调用的状况,最终的 interdex_stripdebuginfo.config 配置代码 以下所示:

{
    "redex" : {
        "passes" : [
            "StripDebugInfoPass",
            "InterDexPass",
            "RegAllocPass"
        ]
    },
    "StripDebugInfoPass" : {
        "drop_all_dbg_info" : false,
        "drop_local_variables" : true,
        "drop_line_numbers" : false,
        "drop_src_files" : false,
        "use_whitelist" : false,
        "cls_whitelist" : [],
        "method_whitelist" : [],
        "drop_prologue_end" : true,
        "drop_epilogue_begin" : true,
        "drop_all_dbg_info_if_empty" : true
    },
    "InterDexPass" : {
        "minimize_cross_dex_refs": true,
        "minimize_cross_dex_refs_method_ref_weight": 100,
        "minimize_cross_dex_refs_field_ref_weight": 90,
        "minimize_cross_dex_refs_type_ref_weight": 100,
        "minimize_cross_dex_refs_string_ref_weight": 90
    },
    "RegAllocPass" : {
        "live_range_splitting": false
    },
    "string_sort_mode" : "class_order",
    "bytecode_sort_mode" : "class_order"
}

六、最后,执行相应的 redex 优化命令
这里咱们使用 Redex 命令对上一 Dex 优化中获得的 app_release-proguardwithr8.apk 进行 Dex 分包优化和去除 debugInfo,它使用了贪心这种局部最优解的方式去减小跨 Dex 调用形成的信息冗余,命令以下所示(注意,在 redex 的前面可能须要加上 Android sdk 的路径,由于 redex 中使用到了sdk下的zipalign工具):

ANDROID_SDK=/Users/quchao/Library/Android/sdk redex --sign -s wan-android-key.jks -a wanandroid -p wanandroid -c ~/Desktop/interdex_stripdebuginfo.config -P app/proguard-rules.pro -o ~/Desktop/app-release-proguardwithr8-stripdebuginfo-interdex.apk ~/Desktop/app-release-proguardwithr8.apk

上述 redex 命令的 关键参数含义 以下所示:

--sign:对生成的apk进行签名。
-s:配置应用的签名文件。
-a: 配置应用签名的 key_alias。
-p:配置应用签名的 key_password。
-c:指定 redex 进行 Dex 处理时须要依据的 CONFIG 配置文件。
-o:指定生成 APK 的全路径。
使用上面的 redex 命令咱们就能够对优化后的 APK 进行 再签名和混淆,等待一会后(若是你的 APK 的 Dex 数量和体积很大,可能会比较久),就会生成 优化后的 APK:app-release-proguardwithr8-stripdebuginfo-interdex.apk,以下图所示:

大牛耗时一年:深刻探索 Android 包体积优化,共三万字建议收藏上
能够看到,咱们的 APK 大小几乎没有变化,这是由于当前的 APK 只有一个 Dex,而且 第一个 Dex 默认不会优化。为了能实际看到 redex 的优化效果,咱们采用一个新项目来进行实验,项目地址以下所示:

redex 优化 Apk 项目地址

https://github.com/AndroidAdv...

首先,引入一大堆开源库,尝试把 Dex 数量变多一些。而后直接经过 assembleDebug 编译便可。此外,为了能够更加清楚流程,咱们能够在 命令行输入 export TRACE=2 以即可以输出 redex 的日志。最后,咱们输入下面的 redex 命令删除 dex 中的 debugInfo 和减小跨 dex 调用的状况,以下所示:

redex --sign -s ReDexSample/keystore/debug.keystore -a androiddebugkey -p android -c redex-test/interdex_stripdebuginfo.config -P ReDexSample/proguard-rules.pro  -o redex-test/strip_output.apk ReDexSample/build/outputs/apk/debug/ReDexSample-debug.apk

最终,咱们看到先后的 APK 体积对比图以下所示:

能够看到,APK 的大小从 14.2MB 减小到了 12.8MB,优化效果大概有10%,效果仍是比较明显的。此外,若是你的 App 的 Dex 数量越多,那么优化的效果就会越大。

六、三方库处理

  • 实际的开发过程当中,咱们会用到各类各样的三方库。尤为当项目变大以后,开发人员众多,所以引入的三方库也会很是多,好比说,有人引入了一个 Fresco 图片库,而后这个库你可能不熟悉,你会引入一个 Glide,而且另外一我的它可能又会引入他熟悉的图片库 Picasso,因此项目中可能会存在多个相同功能的三方 SDK,这一点,在大型项目当中必定会存在。
  • 所以,咱们在作代码瘦身的时候,须要将三方库进行统一,好比说 将图片加载库、网络库、数据库以及其余基础库进行统一,去掉冗余的库。
  • 同时,在选择第三方 SDK 的时候,咱们能够将包大小做为选择的指标之一,咱们应该 尽量地选择那些比较小的库来实现相同的功能。

例如,对于图片加载功能来讲,Picasso、Glide、Fresco 它们均可以实现,可是你引入 Fresco 以后会致使包大小增长不少,而 Picasso 却只增长了不到 100kb,因此引入不一样的三方 SDK 对包大小的影响是不同的。

  • 这里,咱们可使用 AS 插件 Android Methods Count,安装以后,它会自动在 build.gradle 文件中显示你引入的三方库的方法数。
  • 最后,若是咱们引入三方库的时候,能够 只引入部分须要的代码,而不是将整个包的代码都引入进来。
  • 不少库的代码结构都设计的比较好,好比 Fresco,它将图片加载的各个功能,如 webp、gif 功能进行了剥离,它们都处于单个的库当中。若是咱们只须要 Fresco 的 webp 功能,那咱们能够将除 webp 以外的别的库都给删掉,这样你引入的三方库就很小了,包大小就降下来了。以下所图所示,咱们能够仅仅保留 Fresco 的 webp 功能,其它依赖均可以去掉。

若是你引入的三方库 没有进行过结构剥离,就须要 修改源码,只提取出来你须要的功能便可。

七、移除无用代码
移除无用代码时咱们常常会碰到下面两个问题:

业务代码只增不减。
代码太多不敢删除。
这里,有一个很好的方法能够 准确地判断哪些类在线上环境下用户确定不会用到了。咱们能够经过 AOP 的方式来作,对于 Activity 来讲,其实很是简单,咱们只须要 在每一个 Activity 的 onCreate 当中加上统计 便可,而后到了线上以后,若是这个 Activity 被统计了,就说明它还在被使用。

而对于那些 不是 Activity 的类,咱们能够 利用 AOP 来切它们的构造函数,一个类若是它被使用,那它的构造函数确定会被调用到。例如,下面就是 使用 AspectJ 对某个包下的类进行构造函数切面 的代码:

@After("execution(org.jay.launchstarter.Task.new(..)")
public void newObject(JoinPoint point) {
    LogHelper.i(" new " + point.getTarget().getClass().getSimpleName());
}

其中,new 表示是 切的构造函数,括号中的 .. 表示的是 匹配全部构造参数。此外,咱们也能够直接使用 coverage 插件 来作 线上无用代码分析,须要注意的是,在注册上报数据的时候记得把服务器名改成本身的。

https://github.com/bytedance/...

最后,咱们也能够在线下使用 Simian工具 来 扫描出重复的代码。

https://blog.csdn.net/Love667...

八、避免产生 Java access 方法
access 方法是什么?
为了能提供内部类和其外部类直接访问对方的私有成员的能力,又不违反封装性要求,Java 编译器在编译过程当中自动生成 package 可见性的静态 access$xxx 方法,而且在须要访问对方私有成员的地方改成调用对应的 access 方法。

主要有 两种方式 避免产生 access 方法:

在开发过程当中须要注意在可能产生 access 方法的状况下适当调整,好比去掉 private,改成 package 可见性。
使用 ASM 在编译时删除生成的 access 方法。
由于优化效果不是很明显,这里就很少介绍了,具体的实现细节可参见 西瓜视频 apk 瘦身之 Java access 方法删除,此外,在 ReDex 中也提供了 access-marking 这个功能去除代码中的 Access 方法,而且,在 ReDex 还有 type-erasure 的功能,它 与 access-marking 的优化效果同样,不只能减小包大小,也能提高 App 的启动速度。

九、利用 ByteX Gradle 插件平台中的代码优化插件
若是你想在项目的编译阶段去除 access 方法,这里我更加建议直接使用 ByteX 的 access_inline 插件。除了 access_inlie 以外,在 ByteX 中还有 四个 很实用的代码优化 Gradle 插件能够帮助咱们有效减少 Dex 文件的大小,以下所示:

一、编译期间 内联常量字段:const_inline。
二、编译期间 移除多余赋值代码:field_assign_opt。
三、编译期间 移除 Log 代码:method_call_opt。
四、编译期间 内联 Get / Set 方法:getter-setter-inline-plugin。
https://github.com/bytedance/...

资源瘦身方案探索

  • 众所周知,Android 构建工具链中使用了 AAPT 工具来对资源进行处理,Manifest、Resources、Assets 的资源通过相应的 ManifesMerger、ResourcesMerger、AssetsMerger 资源合并器将多个不一样 moudule 的资源合并为了 MergedManifest、MergedResources、MergedAssets。而后,它们被 AAPT 处理后生成了 R.java、Proguard Configuration、Compiled Resources。以下图左上方所示:

其中 Proguard Configuration、Compiled Resources 的 做用 以下所示:

Proguard Configuration:这是AAPT工具为Manifest中声明的四大组件与布局文件中使用的各类Views所生成的混淆配置,该文件一般存放在 ${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt。

Compiled Resources:它是一个Zip格式的文件,这个文件的路径一般为 ${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_。在通过 zip 解压以后,能够发现它 包含了res、AndroidManifest.xml和resources.arsc 这三部分。而且,从上面的 APK 构建流程中能够得知,Compiled Resources 会被 apkbuilder 打包到 APK 包中,它其实就是 APK 的资源包。

所以,咱们能够 经过 Compiled Resources 文件来修改不一样后缀文件资源的压缩方式来达到瘦身效果的。

可是须要注意的是,resources.arsc 文件最好不要压缩存储,若是压缩会影响必定的性能,尤为是在冷启动时间方面形成的影响。而且,若是在 Android 6.0 上开启了 android:extractNativeLibs=”false” 的话,So 文件也不能被压缩。

文章不易,若是你们喜欢这篇文章,或者对你有帮助但愿你们多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

相关文章
相关标签/搜索