扫描件太大怎么缩小到1m以下,扫描件扫出来太大怎么改小

首页 > 经验 > 作者:YD1662022-10-26 12:33:48

系统提供以下接口获取类相关的信息,就是通过分析相关的系统注解来实现的

如果代码中不存在使用这些接口获取类信息的逻辑,就可以安全的移除这部分注解,从而达到缩减包大小的目的。

RenameClassesPass

该 Pass 通过缩减类名的字符串长度来减小包体积

比如把类名从La/b/c/d/e;改为LX/a;,可以类名字符串的长度,从而达到包大小缩减的目的。实际上 Proguard 本身已经提供类似的功能: -repackageclasses 'X',效果如下:

扫描件太大怎么缩小到1m以下,扫描件扫出来太大怎么改小(5)

但是-repackageclasses 'X'的处理会影响 ReDex 的 InterDexPass 的算法逻辑(InterDexPass 可以参考下文),导致收益缩减

本质原因在于 Proguard 重命名后,影响了 InterDexPass 函数引用权重分配,导致 InterDex 收益被回收

权重算法优化相对来说比较复杂,同时存在众多不可确定性,比如潜在的跟其他优化的冲突,所以我们采取了第二种解决方案。

这里需要解决的一个关键点在于如何确定一个类名是否可以被安全的重命名,我们采取了一个比较取巧的方式,ReDex 会分析 Proguard 传递上来 mapping.txt 文件,只要我们保持跟 Proguard 类重命名优化一样的处理策略,就不会引发反射/native 调用/序列化等一系列问题。

扫描件太大怎么缩小到1m以下,扫描件扫出来太大怎么改小(6)

但是执行起来还是碰到各种千奇百怪的问题,比如 Signature 系统注解失效问题。Signature 注解的内容是非标准的类名格式,所以类重命名后简单回写字符串或者更新 Type 类型会导致 Signature 注解失效,最后通过深入解析 Signature 格式规避了这个问题。

StringBuilderOutlinerPass

该 Pass 是针对 StringBuilder 的 CallSites 进行分析缩略的优化,与死代码删除搭配使用可以有不错的优化效果。

为何要优化 StringBuilder 呢?在 Java 的代码开发过程中,字符串操作几乎是我们最经常做的一件事情,无论是实际处理字符串拼接还是各种不同数据类型之间的拼接操作。而这些拼接操作都会被 Java 的 de-sugar 优化为 StringBuilder 操作。比如:var log = "A" 1 "B" 1.0f other_var; 会被优化为:

1 StringBuilder builder = new StringBuilder(); 2 builder.append("A"); builder.append(1); 3 builder.append("B"); builder.append(1.0f); 4 builder.append(other_var); 5 builder.toString();

因此我们对 StringBuilder 的所有 Callsites 进行分析,在最好情况下多个方法调用可以被优化为一个调用,这个方法是一个 outline (外联)方法,具体的参数拼接和 toString 被隐藏在函数内部:

1 invoke-static {v1, v2, v3} Outline;.bind:([Ljava/lang/Object)Ljava/lang/String;

优化步骤可以被简单的分为如下几个步骤:

  1. 生成一个泛型的外联方法、以及数个特定参数的方法:我们可以认为生成的方法大概是这样的

1 @Keep 2 public static String bind(Object... args) { 3 StringBuilder builder = new StringBuilder(); 4 for (int i = 0; i < args.length ; i ) { 5 builder.append(args[i]); 6 } 7 return builder.toString(); 8 }

  1. 收集 StringBuilder 的 CallSites :通过抽象解释和不动点分析,分析所有的 StringBuilder 操作,对 append、new-instance、和 init 方法分类。判断每次 append 的参数是不是 immutable 操作,如果增加的 insn 少于减少的 insn 即会减少代码,就对这里进行处理。
  2. 生成外联方法调用:由于我们使用了泛型方法来接受参数,因此我们要对基础类型生成 ValueOf 的转换操作、并且删除 append 方法前为了防止被错误优化我们还需要插入 move 指令来 copy 原有参数(这些 move 指令会被后续优化正确删除)、如果参数个数还在我们生成的特定 outline 方法范围内我们就可以使用特定方法来生成外联函数,其余的将使用泛化的外联来接受。
DEX 格式优化InterDexPass

该 Pass 是针对跨 DEX 引用的优化。

跨 DEX 引用是指当一个 DEX 需要“使用”到另一个 DEX 中的类/方法/变量时,需要在本 DEX 中保存一份对应的类/方法/变量的 id,如果 2 个 DEX 用到了相同的字符串,那么这个字符串在 2 个 DEX 都需要进行定义。所以,改变类/方法/变量和字符串在 DEX 中的分布,可以减小引用的数量,从而减小 DEX 的体积。从原理中也可以看出,该优化对单 DEX 的应用是无效的。

扫描件太大怎么缩小到1m以下,扫描件扫出来太大怎么改小(7)

从上图可以看到,进行类重排后,DEX0 的类引用和方法引用数量都减少了,DEX 的体积也会因此减小。

具体的优化过程是:

  1. 收集每个类涉及的所有引用,按照引用数量和类型计算出类的权重
  2. 根据权重计算出每个类的优先级
  3. 根据优先级选取一个类放入 DEX 中,然后调整剩余类的优先级,重复此步骤直到所有类都被处理
ReBindRefsPass

该 Pass 是针对方法引用的优化,其原理同 InterDexPass。

在字节码中,invoke-virtual/interface指令需要一个方法引用,在很多情况下,这个引用指向的是子类或者实现类的引用,把这个引用替换成父类和接口的方法引用不会影响运行时逻辑,同时会减少 DEX 中方法引用的数量。在生成 DEX 的时候,方法引用的 65536 限制通常是最先遇到的瓶颈,该优化也可以缓解这种情况。

扫描件太大怎么缩小到1m以下,扫描件扫出来太大怎么改小(8)

上一页123下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.