赣州新闻网

首页 / 科技

一口气说出6种,@Transactional注解的失效场景

2020-03-20 · 赣州新闻网 · 科技

由于文章特别长,建议先收藏再阅读。

在 Android 性能优化的知识体系当中,包体积优化一直被排在优先级比较低的位置,从而导致很多开发同学对自身应用的大小并不重视。在项目发展的历程中,一般可划分为如下三个阶段:

初创期1 3 % ) A F T c T => 成长期` % k 7 => 成熟期

通常| y B V @ - Z 来说y | a q } 6 l M,当应用处于成长期的中后阶段时,才会考虑去做系统的包体积优化,因此,只有在这个阶段及之后,包体积优化带来的收益才是可观的。

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


1、瘦身优化及 Apk 分析A e { & A 3 J 6 +方案介绍

1、瘦@ I ) K身优势

我们首先来介绍下,为什么我们需要做 APK 的瘦身优化?

主要x m t i有 三个方面 的原因:

(1)下载转化率

Al q e qPK 瘦身优化在实际的项目中优先级是比较低的,因为) a q i ]做了之后它的好处不是那么明显,尤其是那些还没有到 稳定期 的项目,我们都知道,App 的发展历程是从 项目初期 => 成长期 => 稳定期,对于处于 发展初期与成长期 的项目而言,可能会做 启动优化、卡顿优化,但是一般不会做 瘦身优化,瘦身优化 最主要的好处是对应用 下载转化率 的影响,它是 App 业务运营q + F * H -的重要W , ( =指3 f k标之一,在项目精细化运营的阶段是非常重要q - y - h v的。

因为如果你的 App 与其它同类型的 App 相比 Apk 体积要更小的话,那么你的 App 下载率就可能要高一些。i 4 N而且,包体积越小,用户下载等待的时间也会越短,所以下载转换成功率也就越高。所j 4 i P :以,安装包大小与下载转化率的关系 大致是成反比 的,即安装包越大,下载转换率就越小。

一个 80MB 的应用,用户即使点了下载,也可能因为网络速度慢、突然反悔导致下载失败。而对于一个 20MB 的应用,用户点了下载之后,在犹豫要不要下的时候可能就已经下载完了。

而且,现在很多l ! h } } & + !大型的 App 一般都会有一个 Lite 版本的 App,这个也是出于下载转化率方面的考虑。

2应用市场

Google Play 应用市场强制要求( D ^ n [ n f .超过 100MB 的应用只能使用 APK 扩展文件方式 上传 6 n m + h h。当使用 APK 扩展文件方式 上传时,Google Pla) ! . a e a P p { * u `的共享存储位; D z b n . f w置O B ; T $(SD 卡或可安装 USB 的分区;也称为“外部”存储),应用可以在其中访问它们。在大多数设备上,Google Play 会在下载 APK 的同时下载扩展文件,因此应用在用户首次打开时便拥有了所需D l r ; M $ k | G的一切。

但是,在某些情况下,我们的应用必须在应用启动时从 Google Play 下载文件。如果您想避免使用扩展文件,并且想要应用程序的下载大小大于4 H o100 MB,则应该使用 Android App Bundles 上传应用程序,此时应用程序最多可提供150 MB的压缩下载大小。

Androidq y Z G m App Bundles 就是 Android 应用程序捆绑包,它能够让 App 以 添加动态功能模块的方式 去解决 APZ 4 # WK 大小$ X M较大的问题。如下,就是由一个基本模块和两个动态功能模块组成的 Android App Bundle APK 的; m V J ` Q ;组2 m 8 w y成结构图:

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


3、渠道合作商的要求

此外,还有一个原因,当我们的 App 做大之后,可能需要跟各个手机厂q D . P商合作预装,这] ? L V些 渠道合作商会对你的 App 做详细的要求,只有达到相应的要求后才允许你的 App 预装到手机上。而且,越大的W @ 4 } b & O D C App 其单价成本也会越高。所以,瘦身也是我们项目做大之后一定会遇到的一个问题| E J 4 G。

体积过大对 App 性能的影响

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

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


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


3)ROM 空间:如果应用的安装包大小为 50MB,那么启动解压之后很可能就已经超过 100MB 了。并且,如果 闪存空间不足,很可Y _ # J a _ G 5能出现“写入放大”E ( q H f ; : c u的情况t G J d,它是闪存和固态硬盘(SSD)中一种不良的现象,闪存在可重新写入数据前必须先擦除,而擦除操作的粒? Q 4 E 6 # :度与写入操作相比低得多,执行这些操作就会多次移动(或改写)用户数据和元数据。

因此,S m @ Q要改写数据,就需要读取闪存某些已使用的部分,更新它们,并写入到新的位置,如果新位置在之前已被使用过,还需连同先擦除;由于) + P Y ^ = } = d闪存的这种工作方式V * q,必须擦除改写的闪存部分比新数据实际需要的S f I G c大得多。即最终可能导! x L V 2 H {致实际写入的物理资料量是写入资料量的多倍。

2、Q ? y [APK 组成


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


1)代码相关:clas^ N 4 2 : Y D )ses.dex,我们在项目中所编写的 java 文件,经过编译之后会生成一个 .class 文件,而这些所有的 .clap : * s O * L B -ss 文件呢,它最A z G 0 Q ` X A C终会经过 dx 工具编译生成一个 classm v tes.dex。


2)资源相关:res、assets、编译后的二进制资源文件 ru # 9 Kesources.arsc 和 清单文件 等等。res 和 assL u : qets 的不同在于 res 目录下的文件会在 .R 文件中生成对应的- 6 f c资源 ID,而 assets 不会自动生成对应的 ID= V 6 u,而H a c L是通过 AssetManager 类的接口来获取。此外,每当在 res 文件夹下放一个文件时,aapt 就会自动生成对应 id 并保存在 .R 文件中,但 .R 文件仅仅只是保证编译程序不会报错,实际上在应用运行时,系统会根据 ID 寻找对应的资源路径,而 resources.arsc 文件就是用来记录这些P g Q 7 c : y ` ID 和 资源文件位置对应关系 的文件。


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


此外,还有 META-INF,它存S S g 1 n o G u &放了应用的 签名信息,其中主要有 3个文件,如下所示:


MANIFEST.MF:其中每一个资源文件都有一个对应的 SHA-256-Digest(SHA1) 签名,MANIFE0 N W i LST.q O z 3 aMF 文件的 SHA2569 W P 2 5 3 T ,(SHA1) 经过 base64 编码的结果即为 CERTR C 8 4 l u |.SFB g 3 中的 SHA256(SHA1)-Digest-Manifest 值。

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

CERT.RSA:其中包含了公钥、加密算法等信息。首= X T /先,对前一步生成的MANIFESd K 4 J 0T.MF使用了SHA256(SHA1)-RSA算法,用开发者私钥签名。然后,在安装时使用x r w公钥解密。最后,将% H + + Z其{ / F C p 4 1 e #与未加密的摘要信息(MANIFEST.MF文件)进行对比,如O n b , ~ Q 2果相符,则表明内容没有被修改。

代码瘦身方案探索

在讲解如何对 Dex 进行优化之前,可能有很多同学对 Dex 还没有足够的了解,这里我们就先详细地了解下 Dex。


1、( - 4 GDex 探秘

1)Dex 是 Android 系统的可执行文件,包含 应用程序的全部操作指令以及运行时数据。因为 Da} b Flvik 是一种针对嵌入式设备而特殊设计的 Java 虚拟机,所以 Dex 文件与标准的 Class 文件在结构设计上有着本质的区别。


2)当 Java 程序被编译成 clasF s ? V ;s 文件之后,还需要使用 dx 工具将所有的 class 文件整合到一个 dex 文Y M M | Q c 5件中,这样 dex 文件C L r ` t ? c就将原来每个 cz i - xlass 文f a K } 7 .件中都有的共有信息合成了一体,这样做的目的是 保证其中的每个类都能够共享数据,这在一定程度上 降低了信息冗余,同时也使得 文件结构更加紧凑。


与传统 jar 文件相比,Dex 文1 3 q n Y z & q S件的大小能j ^ | Z & & . (够缩减 50% 左右。关于 Class 文件与 Dex 文件的结果对比图如下所示:


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

如果想深入地了解 Dex 文件格式,可以参见Google 官方教程 - Dex格式h e _ Z ^ t。

Dex 一般在应用包体积中占据了不少比重,并且,Dex 数量越多,App 的安装时间也会越长。所以,? o [ N % u r优化它们可以说是 重中之重。下面,我们就来看看有哪些方式可以优化 Dex 这部分R O a &的体积。


2、ProGuard

混淆这里就不赘述了,大家应该比较熟悉,原文有g : 4 W比较详细的介绍。

3、D8 与 Rs m ` a Z8 优化

D8 优化

D8 的 优化效果 总的来说可以归结为如下 四点:

  1. Dex的编译时间更短。
  2. de3 p / Z O + l +x文件更小。
  3. D8 编译的 .dex 文件拥有更好的运行时性能。
  4. 包含 Java 8 语言支持的处理。

在 Android Sg : Z a s Btudio 3.0需要主动在 gradle.properties 文件中新增:

androig M 2 O 8 _d.enableD8&{ : O a $nbsp;= true

Android Studio 3.1 或之后的版本 D8 将会被作为默认的 Dex 编译器。

R8 优化

R8 官方文档(目前已经开# n + # R # L源)

https://r8.googlesource.com/r8

  • R8 是 Pro( Q 3 - - G x c Iguard 压缩与优化部分的替代品,并且它仍然使用与 Proguard 一样的 keep 规则。如果我们仅仅想在 Android Studio 中使用 R8,当我们在 build.gradle6 V w J K o X 中打开混淆的时候,R8 就已经默认集成进 Android GrA Y &adle plugin 中了。

如果我们当前使用的是 Android Studio 3.4 或 Android Gradle 插件 3.4.0 及其更高版本,R8 会作6 7 1 v / W T U为默认编译器。G V 3否则,我们 必须要在 gradle.properties 中配置如下代码让 App 的混淆去支持 R8,如下所F q 5 8 q } b示:

android.enableR8=true
android.enableR8.libraries=t8 - E i 0rue

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

  • ProGuG } 9 S M $ x [ard 和 R8 都应用了基本名称混淆:它们 都使用简短,无意义的名称重命名类,字段和方法。他们还可以 删除调试属性。
  • 但是,R8 在 inline 内联容器类中更有效,并且在删除未使用的类,字段和方法上则更具侵W r K = 2略性。例如,R8 本身集成在 ProGuard V6.1.1 版本中,在压缩 apk 的大小方面,与 ProGuard 的 8.5% 相比,使用 R8 apk 尺寸减小了约 10%。并且,随着 Kotlk y } ^ 8 1 B : xin 现在成为 AndU ~ D Hroid 的第一语言,R8 进行了 ProGuard 尚未提供的一些 Kotlin 的特定的优化。

从表面上看,ProGuard 和 R8 非常相似。它们都使用相同的配置,因此在它们之间进行C 5 + Y # 4 }切换很容易。放大来看的话,它们之间也存在一些差异。R8 能更好地内联容器类,从而避免了对象分配。但是 ProGuard 也有其自身的优势,具体有如下几点:

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

ProGuard 的多次优化遍历通常可以产生一系列优化。例如,第一遍可以传递一个常量方. ` 4 D P法参数,以便下一遍可以删除该参数并进一步传递该值。删除日志代码时,多8 ! V d , a次传递的效果尤其明显。ProGuard 在u c 6 7 { 4 R B 9删除所有跟踪(包括组成日志消息的字符串操作)方面更有效。

2)、ProGuard 中应用的模式匹配算法可以识别和替换短指令序列,从而提高代码效率并为更多优化打开了机会。在优@ * 4 D + b & [ J化遍历的顺序中,尤其是数学运算和字符串运算可从中受益。

3、最后,ProGua ? 6 7 qrd 具有独特的能力来优化使用 GSON 库将对象序列化或反序列化为 JSON 的代码。该库严重依赖反射,这很方便,但效率低下。而 Pro} X m u * Q 0Guard 的优化功能可以 通过更高效,直接的访问I } 4 E q /方式 来代替它。

R8 优化实战

接下W + m 0 0 |B 的大小。

  • D8 与 R8 的作用非常强大,而 Jake Wharton 大神最近一年多也i } 4在研究 D8 与 R8 的知识,如果想对 D8 与 R8T A # K 5 y 0 的实现细节有更多地了解,可以看看他的 个人博客。
  • 4、去除 debI b V @ cug 信息与行号信息

    • 在讲解什么是 deup t I ^ G & 9bg 信息与行号信息之前,我们需要先了解 Dex 的一些知识。
    • 我们都知道,JVM 运行时加载的是 .class[ Y a L U A m 文件,而 Android 为了使包大小更加紧凑、运行时更加高效就发明了 Dalvik 和 ART 虚拟机,两种虚拟机运行的都是 .dex 文件,当然 ART 虚拟机还可以同时运行 oat 文件。
    • 所以 Dex 文件里的信息内容和 Class 文件包含的信息是一样的,不同的是 Dex 文件对 Class 中的信息做了去重,一个 Dex 包含了很多的 Class 文件,并且在结构上有比较大的差异,Class 是流式的结构,N { Y w ADex 是分区结构,Dex 内部的K Y - b I X X = x各个区块间通过 offset 来进行索j Q l Y N引。

    为了在应用出现问题时,我们能在调试的时候去显示相应的调试信息或者上报 craY k 3 F X U Y o (sh 或者主动获取D O ^ x调用堆栈的时候能通过 debugItM , [ n Nem 来获取对应的行号,我们都会在混淆配置中加上下面的规则:

    -keepattributes SourceFile, LineNumberTable

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


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

    从图中可以看到,Dex 文件的结构主要分为 四大块:header 区,索引区,data 区,map 区。而我们的 debug 与行号信息q ; d 1 [就保存在 data 区中的 debugItems 区域。

    而 debug_items 里面主要包含了 两种信息,如下g 0 ? D n : G s b所示:

    1. 调试的信息:包含函数的参数和所有的局部变量。
    2. 排查问题的信息:包含所有的指令集行号与源文C G [件行号的对应关系。

    根据 Google 官方的数据,debugItem 一般占 Dex 的比例有 5% 左右,如果我们能去除 debug 与行号信息,就能更进一步对 D| P J } + I fex 进行瘦身,但是会失去调试信息的功能,那么,有什么方式可以去掉 debugItem,同时又能让 crash 上报的时候能拿到正确的行号呢?

    我们可以尝试直接修改 Dex 文件,保留一小块 debugItem,让系统查找E { ;行号的时候指令集行号和源文件行号保持一致,这样任何监控上报的O k行号都b u q - #直接/ + j + M b变成了指令集行号。

    每一个方法都会有一个 debugInfoItem,每一个 debuginfoItem 里面都有一个指令集行号和源文件行号的映r { g 射关系,这了我们直接把多余的 debugInfoItem 全部删掉,只保留了一个 debugInfoItem,4 m h 2 = B ;这样所有的方法都会指向同一个 debugInfou 7 X k 9 I OItem,并且这个 debugInfoItem 中的指令集L k $ | D ? j行号和源文件行号保持一致,这样不管用什么方式来查找行号,拿到的[ : D Y都是指令集行号。

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

    关于如何去除 Dex 中的 Debug 信息是通过 ReDex 的 StripDeb) I A N n 2 @ugInfoPass 来完成的,其配置如下g 9 w E所示:


    {
        \"redex\" :&nbR t . 3 ( m ` 7 [sp;5 L 2 I v h n w {
     &n/ F V J h ` $bsp;      \"passes\" : 4 c D[
       &nG o n ~ A ! z op;     ]
     l 0 v K J n : 7 M&nP O ! 6 n Ebsp;  },
        \"StripDebugInfoPass\" : {
            \"drR Q Dop_all_dbg_info\" : falsq 2 = ( n ! We,
      &nh ! Y = 2 * gbsp;&n8 l ` G d s dbsp;    \"drop_locq 0 l * m h o 8 al_variables\" : true,
            \"drop_lin% i ze_numbers\" : false,
          &nbsV p . F yp; \"drop_src_files\" : falF u ~ L :se* % 9 ^,
    &A L . d B C 1 ` Dnbsp;      &u / l A g tnbsp;\"use_whitelist\0 M x 7 C y k i" : false,
     &@ } . x 6 0 $ d dnbsp;      \"cls_whitelist\"&R K - 2 Xnbsp;: ,
            \"method_whiteli~ 1 n M ; K / Dst\" : ,
         w N , t * )    \"drop_prologuy . S +e_end\" : true,
          &nbb C g 4 zsp; \"drop_epilogue_begin; _ p\" : true,
            \"drop_all_dbg_info_if_empty\" : true
        z p v 1 + (},
        \"RegAllocPass\" : {
            \"live_range_splitting\": false
       &A e J D 0 ; q Ynbsp;}
    }

    关于 debuginfo 的实战我们下面马上会开始,在此之前,我们先讲讲 Dex 分包中的另一个优化点。

    5、Dex 分包优化

    Dex 分包优化原理

    当我们的 APK 过大时,Dex 的方法数就会超过65536个,因此,必须采用 mutildex 进行分包,E V s但是此时每一个 Dex 可能会调用到其它 De) a g P 8 1 mx 中的方法,这s 6 1 0 F & S种 跨 Dex 调用的方式Z h - Y ? ~ U e会X B I a ^ (造成许多冗余G [ p h N + ! n D信息,具体有如下两点:

    1. 多余的 methoN * 7 ; Ud id:跨 Dex 调用会导致当前dex保留被调f t i A S 8 #用dex中的方法id,这种冗余会导致每一个dex中可以存放的classD g 5变少,最N t v J | C终又会导致编译出来的dex数量增多,而dex数据的增加又会进一步加重这个问题: l t z H K `。
    2. 其它跨dex调用_ N G v *造成的信息冗余:除了需要多记录被调用的method id之外,还需多记录其所属类和当前方法的定义信息,这会造成 string_ids、type_ids、proto_ids 这几部分信息的冗余。

    为了减少跨 Dex 调 C a D t M用的情况,我们必须 尽量将有调用关系的类和方法分配到同一个 Dex 中。但是各个类相互之间的调用关系是非常复杂的,所以很难做到最优的情况。

    所幸9 L e U J的是,ReDex 的 Cy ; f I xrossDexDefMinimizer 类分析了类之间的调用关系,并 使a a +用了贪心算法去计算局部的最优解(编译效果和dex优化效果之间的某一个平衡点)P o N。

    https://github.com/facebooR 4 o w M ^ n 7 _k/redeO X ) Dxw 3 Y X z n X t/blob/master/opt/inte` 0 3 w Qrdex/CrossDexRefMinimizer.cpp

    使用 \"InterDej & x LxPass\" 配置项可以把互相引用的类尽量放在同个 Dex,增加类的 pre-verify,以此提升应用的冷启动速度。

    在 ReDex 中使用 Dex 分包优化 s ) U & b T A [跨 dex 调用造成的信息冗余的配置代码如下所示:


    {
        \"redeU % 3 $ f Ux\" : _ & f z i;{
    &7 + N ; I Snbsp;    &nbsn l K Xp;  \"passes\" : [
       &nbs% s j +p;     &n. K T c T 3 D bsp;&n$ 7 _ K @
     &n] 0 1 0 ` y Jbsp;  },
    &nb+ 1 x 7 Y A Q Ssp;&np p ( 2 C obsp;  \"InterDexPass\" :&nbs* / e Q K Bp;{
        &nI 7 O _ bbsp;  &M r p ? m 5 $ 6 6nbsp;\"minimize_cross_dex_refs\": true^ : p Z [,
            X [ ( P ` \"minimize_cross_dexv I l D 5 0_refs_method_ref_weight\": 100,
      &/ h d H a % 4nbsp;     \"minime S r [ I 8 c X size_cross_dex_refs_field_ref_weight\": 90,
       &P h A E R 9 M K qnbsp;    \"minimizo - 9 ue_z ) Q !cross_dex_refs_typ, p Ie_ref_weight\": 100,
     & R c;   &x a gnbsp;   \"minimize_cross_dex_refs_string_rX X e l 0 N 8ef_weight\": 90
      X j + u;&nb7 i / + d B { ? Esp; },
     n k H } i ^ b a&nb) o _ Z Osp;  \"RegAllocPask % ss\" : {
     &nbe q ,sp;      \"live_range_splitting\": false
        },
    &ns $ ( p Q 7 pbsp;  v 4 2 o - c 7 $; 9 [ ! } " :&nbK % 8 ` }sp;\"class_order\"
    }


    为了衡量优化效果,我们可以使用 Dex0 P ? $ 信息有效率 这个指标,公式如下所示:


    git&nbsS q d ?p;clone https://github.com/facebook/redex.git
    cL m $ | G Q i 6d&nz g ! c O Ebsp;redeU c c _ I Px

    如果 Dex 有效率在t @ ! 80% 以上,就说明基本合格了。

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


    下面,我们就使用 Redex 来对上一步生成的 app-relv K [ H k + q g eease-proguardwithr8.apk 进行进一I T L步的优化。(macOS 环境下)

    https://f| W q H *bn B B Credex.com/docs/installation

    1、首先,我们需要输入一下命令去去安装 Xcode 命令行工具

    xcode-select --install

    2、然后,使用 homebrew 安装~ i b O redex 项目使用到的依赖库

    ANDROID_SDK=/Users/quchao/Library/Android/s7 8 k 6dk redex --sign -s b [ * j 3 j & z&nk e # 0 ^ x Obsp;wan-android-key.jks -a wananc d f g / l * jdroid -p wanandroid -c @ h ?;~/Desktop/interdex_stripdebuginfo.c8 5 0 u a .onfig H B b  O z V i x载最新的 b, [ | 4oov @ Nst 源码 至 /usr/local/Cellar/ 目4 f [ ( x _录下,我q e k P当前使用的是 boost V1.7.2源y N `码下载地址 中的 boost_1_72_0.zip。

    https://dl.bintray.coU @ } { Gm/boostorg/release/1.72.0/sourT P C u Z r Xce/

    从 深入探索 Android 启动优化 时就提及到了 Redex 的类重排优化,当时卡在这一步,所以一直没法真正完成类的重排优化。

    3、接着,从 Github 上获取 ReDex 的源码并切换到 redex 目录下

    git clone https://github.com/facebook/red^ _  q Vex.git
    cd redex

    4、下一步,使用 autoconf 和 make 去构建 Re+ 0 z * a pDex

    # 如果你使用的是 gcc, 请使用 gcc-5
    autoreconf -ivf && ./configure&7 B znbsp;&& make -j4
    sudo make ? X y W O s c B 1;install

    5、然后,配置 Redex 的 cn $ #onfig 代码

    在 Redex 在运行的时候,它是根= E Y @ t t 5 :据 redex/config/default.config 这个配置文件中的通道 passes 中添加不同的优化项来对 APK 的 Dex 进行d h处理的,我们可以参考 redex/config/default.config 这个默认的配置,里面的 passes 中不同的配置项都有特定的优化。

    为了优化w U U ? L { App 的包体积,我们再加上 interdex_stripdebugio t & k 4 = F ` nfo.config 中的配置项去删除 debugInfo 和减少跨 Dex 调用的情况,最终的 interdex_stripdS j ` 1 ( S 8 Bebuginfo! ( / , 1 ..config 配置代码 如下所示:

    {
       b C n } 6; \"redex\"7 D N : {
            \"passes\" :  7 X X # ^ & e[
    &nA / Ibsp;          &nb 2 q Ssp;\"StripDebuga z RInfoPass\",
        &nbsx 7 u Q 5 up;    = 9 y k Q @  &F & o K
    &nbR I (sp; &. [ u * / t t P `nbsp; },
        \"StrG c J g U G ZipDebugInfoP7 G { W d Xass\" : {
         { j 9 & , X B `;   \"drop_al? & U e . pl_dbg_info\" : false,
            \"dQ v K irop_local_variables\" : true,
            \"drop_line_numbers\" :&nbsd i 3 S zp;false,
        &C l d 9nbsp;   \"drop_src_files\" : false,
     &n9 p N [ K i y Ibsp;     &nbZ | z , C # T a nsp;\"use_whitelist\" :v z 8 j s ^ k false,
     a $ 2 X h + L V b;       \"c 5 x y k J 6 Zcls_whitelist\" :&nC H P :bsp;,
           v t T % | 1 o;&n1 : , p % I b |bsp;\"method_whitelist\" : ,
    &1 L F ( D V I M -nbsp;    B + B N x d 1   \"drop_prologue_end\"&nO ? ^bsp;:0 ~ g g x % 2 7 u true,
    &n3 H o g O Cbsp;     K z ) q { G V O  W { ~ m }\"dropK n R B p @_epilogue_begiV t t H t : Y Bn\" : true,
        &nb@ ! 1 M Q |sp;   \"drop_all_dbg_info_if_emptyl b % o\" : true
        },
     &nb4 ? c w r Vsp;  \"InterDexPass\" : {
            \"minimize_cr2 Y F K i 3oss_dex_refs\": true,
            \"minimq ( H 0 k 1 Kize_cross_dex_refs_method_ref_wey i { A ? . ;ight\": 100,
           &nbJ ` ^sp;\"mind V x / limc 6 ( 3 z Xize_cross_dex_refs_field_ref_weight\": 90,
            \"minimize_cross_dex_refs_type_ref_8 ( T Bweight\":&nbs8 _ c n F }p;100,
     &nb` ; sp;     &@ } ! U T s y ynbb Z G 6 H Gsp;\"minimize_cross_dex_refs_string_ref_weight\": 90
     &nbsY = 3p;  },
        \"RegAllocPass\" : {u 5 $ Q
       &= t `nbsp;    \"live_range_splittinq + i ` H Mg\": false
    &nb0 $ / ] nsp;   },
        \"string_sort_mode\" : \"class_order\",
    &nbs. _ R dp; &nb~ H Y ] Dsp;&nT s r # 4bsp;\"bytecode_sort_mode\" : x d 7\"class_ordeM U - 4r\"
    }

    6、最后,执行相{ H ,应的 redex 优化命令

    这里我们使用 Redex 命令对上一 Dex 优化中得到的 app_rZ A R / Aelease-proguardwithr8.apk 进行 Dex 分包优化和去除 debugInfo,它使用了贪心这种局部最优解的方式去减少跨 Dex 调用造成的信息冗余,命令如下所示(注意,在 rede^ . | 9 e hx 的前面可能5 A &需要加上 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/interde; { - o = 4x_stripdebuginfo.config -P app/pro+ b J = P B ] $guard-rules.pro -o ~/Desktop/app-release-proguardwithr8-stripdebuginfo-interdex.apk ~* # 5 d ~ [ n e G/Desktop/app-release-proguardwithr8.apk


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


    • --sig4 e . Z #n:对生成的apk进行签名。
    • -s:配置应用的签名文件。
    • -a: 配置应用签名的 key_alias。
    • -p:配置应用签名的 key_password。
    • -c:指定 redex 进行 Dex 处理时需要依据的 CONF1 1 ^ X F . 4 1 ?IG 配置文件。
    • -o:指定生成 APK 的全路径。

    使用上面的 redex 命令我们就可以对优化g ? ? 3 E h Z后的 APK 进行 再签名和混淆,等待一会后(如* Y D K S y @ y果你的 APK 的 Dex 数量和体积很大,可能会比较久),就会生成 优化后的 APK:app-release-proguaj F p r S *rdwithr8-stripdebuginfo-interdex.apk,如下图所示:

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

    可以看V Z x到,我们T J U @ c R V的 APK 大小几乎没有变化,这是因为当前的 APK 只有一个 Dex,并且 第一个 Dex 默认不会优化。为了能实际看到 redex 的优化效果,我们采用一个新项t N F n = & |目来进P G ~ ! A行实验,项目地址如下所示:

    red/ ; # 6 L q G sex 优化 Apk 项目地址

    https://github.com/AndroidAd! = / VvanceWithGeektime/Chapter22

    首先,引入一大堆开源库,尝试把 Dex 数量变多一些。然后直接通过 assembleDebug, ~ $ q l # i ? 编译即可。此外,为了可以更加清楚流= O * ( B E m程,我们可以在 命令行输入 export TRAP ( i + d $ #CE=2N 输出 redex 的日志。最后,我们输入下面的 redj ( k : i n yex 命令删除 dex 中的 debugInfo 和减少跨 dex 调用的情况,如下所示:


    redex --sign -s ReDexSample/keystore/debug.keystore -a androiddebugkey -p android K 6 2 K / 5 k;-q * | g 9 R D -c redex-test/interdex_stripdebuginfo.config -P ReDexSample/progW c ] Juard-rules.pro  -o redex-test/strip_output.apk ReDexSample/build/outputs/apk/debug/ReDexSample-debug.apk

    最终,我们看到前后的 APK 体积对0 ^ 0 D O F S w |比图z O Z如下所示:

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

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

    可以看到,APK 的大小从 14.2MB 减少到了 12.8MB,优} 9 G y化效果大概有10%,效果还是比较明显的。此外,如果你的 Ap x * 9 Z jp 的 Dex 数量越多,那么优化的效果就会越大。

    6、三方- ^ T库处理

    • 实际的开发过程中,我们会用到各种各样的三方库。尤其当项目变大之后,开发人员众多,因此引入的三方R A K ; l Z a p库也会非常多2 = 0 T +,比如说,有人引入了一个 Fresco? _ H H 图片库,然后这D G +个库你可能不熟悉,你会引入一个 Glide,并且另一个人它可能又会引入他熟悉的图片库 Picasso,所以项目中可能会存在多个相同功能的三方 SDK,这一点,在大型项目当中一定会存在。
    • 因此,C { _ E t Z g `我们在做代码瘦身的时候,需要将三方库进行统一,比如说 将图片加载库、网络库、( @ o数据库以及其他基础库进行统一,去掉冗余的库。
    • 同时,在选t T C择第三方 SDK 的时候,我们可以将包大小作为选择的指标之一,我们应该 尽可能地选择那些比较小的库来实现相同的功能。
    • 例如,对于图片加载功能来说,Picasso、Glide、Fresco 它t J s $ M 6 调用对应的 access 方法。

      主要有 两种方式 避免产生 acs 1cess 方法:

      • 在开发过程中需s ] z I - 0要注意在可能产U M T生 access 方法的情况下适当调整,比如去掉 private,改为 package 可见3 K 4 o = Y F u性。
      • 使用 ASM 在编译时删除生y } N 7 x { j l j成的 access 方法。

      因为优化效果不E 0 q T 6 + w是很明显,这里就不多介绍了,具体的实现细节可参见 西瓜视频 apk 瘦身之 Java access 方法删除,此外,在 ReDex 中也提供了 access-ma` u _rking 这个功能去除代码中的 Access 方法,并且,在 ReDex 还有 type-erasure 的功能,它 与 access-marking 的优化效果一样,不仅能减少包大{ 9 #小,也能提升 App 的启动速度。

      9/ g ~ } %、利用 ByteX Gradle 插件% p 2 | & L _平台中的代码优化插件

      如果你想在项目的编译阶段去除 access 方法,这里我更加建议直接使用v b 4 + q ! r J u ByteX 的 access_inline 插件。除了 access_inlie 之外,在 ByteX 中i j @ 3 ( ` e还有 四个 很K M - ( O x H实用的代码优化 GraV ; 3 i Ndle 插件可以帮助我们有效减小 Dex 文件的大小,如下所示:

      • 1、编译期间 内联常量字段:const_inline。
      • 2、编译期间 移除多余赋值代码+ Z $ 4 ? z f:field_assign_opt。
      • 3、编译期间 移除 Log 代码:method_call_opt。
      • 4、编译期间 内联 Get / Set 方法:getter-setter-inline-plugin。

      https://github.com/bytedance/W P 2 e M I 8 |ByteX

      资j ; r M * H源瘦身P I W D方案探索

      众所周知,Android 构建工M T b k s &具链中使用了 AAPT 工具来对资源进行处理,Manifest、Resources、s { ? )Assets 的资源经过相应的 ManifesMerger、Resoa 0 D b |urcesMerger、AssetsMerger 资源合并器将多个不同 moudule 的资源合V 8 x n m L并为了 Me! S k 1 l x v OrgedManifest、MergedResources、MergedAssets。然后,它们被 AAPT 处理后生成了 R.java、Proguard Configuration、Compiled Rek a & : osources。如下图左上方所示:

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

      其中 Proguard Configuration、Compiled Re9 5 Q 6 5 b e E $ O F P L e Uard Configuration:这是AAPT工具为Manifest中声明的四大组件K e ~ @与布局文件中使用的各种Views所生成的混淆配置,该文件通常存放在 ${project.builL / ; 5 q 3 [ |dDir}/${AndroidPe x J % B #roject.FD_INTER m DMEDIATES}/res/resouR e $ ! . = #rces-${flavorName}-${buildType}-stripped.ap_。在经过 zip 解压之后,可以发现它? 9 I c 包含了res、AndroidManifest.xml和resources.arsc 这三部分。并且,从上面的 APK 构建流程中可以得知,Compiled Resources 会被 apkbuilder 打包到 Al a Y , | -PK 包中,它其实就是 APKp d } 的资源包。

      因此,我们可以 通过 Compiled Resources 文件来修改不同后缀文件资源的压缩方式来达到瘦身效果的D f 9 1 m o F J。

      但是需要注意的是,resourca & V 5 i m Mes.arsc 文件最好不要压缩存储) V ` i,如果压缩会影响一定的性能,尤其是在冷启动时间方面造成的影响。并且,如果在 A8 r * ? Q y m vndroid 6.0 上开启了2 L O android:ext% h - |ractNativeLibs=”false” 的话,So 文件也不能被压缩。

      文章不易d 4 Z .,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的g - S l g Q [。绝对干货!!!

    项目发展视频工作细化ACT会昌说出场景