pycharm调试如何返回上一步_我是如何一步一步爬上64K限制的坑

分享初衷

分享这个填坑的记录,主要是身边很多 Androider 都会遇到难以解决的难题并重复走旧路。

大部分人都会按照这样的步骤处理:

  1. 遇到一个 BUG ,优先按照自己经验修复;
  2. 修复不了,开始 Google(不要百度,再三强调),寻找一切和我们 BUG 相似的问题,然后看看有没有解决方案;
  3. 尝试了很多解决方案,a 方案不行换 b 方案,b 方案不行换 c 方案,直到没有方案可以尝试了,开始怀疑人生;
  4. 如果影响不大,那就丢在项目里(估计也没人发现),如果影响很大,那只能寻找别人帮助,如果别人也给不了建议,那就原地爆炸了。

无论 BUG 影响多大,丢在项目里总不太好。当别人帮助不了的时候,真的只有代码能帮你。尝试过很多方案不可行,很多时候是因为每个方案的背景不一样,包括开发环境背景如 Gradle 版本,编译版本 ,API 版本场景差异化。我遇到的这个问题也是如此。希望通过以下的记录能帮助你在面对无能为力的 BUG 时更坚定地寻找解决方案。

问题背景

在我们项目最近的一个版本中,QA 测试 Feature 功能时反馈  4.4 设备上 APP 全 Crash!由于反馈该问题时已经快周末了,按 PM 的流程我们需在下周一封包给兼容测试部门做质量测试,这个问题就必须在周一前解决。

第一反应GG,感觉是个大坑。立刻借了两台 4.4 的机型对发生 Crash 场景进行调试,发现都是 「java.lang.NoClassDefFoundError」。这个crash表明找不到引用的类,这类原本该在 主 Dex 文件中,但是主 Dex 文件中却没有提供这个类。第一反应就是 「“难道我们没有 keep 住这个类吗?”」  经过排查确定是构建工具已经把执行了「打包该类的逻辑」,却因为某些原因没有被打进去。我尝试使用 「mutilDexKeepProguard keep」 住这个类,然后编译直接不通过了。收到的异常为:

D8: Cannot fit requested classes in the main-dex file (# methods: 87855 > 65536 ; # fields: 74641 > 65536)

当然有了 LOG 信息就有了解决问题的希望了。

定位问题

Dex 文件规范明确指出:「单个 dex 文件内引用的方法总数只能为 65536」。而这个限制来源于是 davilk 指令中调用方法的引用索引数值,该数值采用 16 位 二进制记录,也就是 「2^16 = 65536」,方法数包括了 Android Framework 层方法,第三方库方法及应用代码方法。

所谓 「主dex」,其实就是 「classes.dex」。还可能存在 「classes1.dex」,「classes2.dex」...「classesN.dex」。因为完整项目可能包含超过 「65536」 个方法,所以需要对项目的 class 进行分 dex 打包。「主dex」 会被最先加载,必须包含启动引用所需要的类及“依赖类”(后面会有详细介绍)。而我所遇到的问题就是 “包含启动引用所需要的类及“依赖类包含的方法数” 超过 65536 个,构建系统就 “罢工” 不干了。

事实上,在 「minsdkVersion >= 21」 的应用环境下是不会出现这种异常的。因为构建apk时方法数虽超过 「65536」 必须分包处理,但由于使用 ART 运行的设备在加载 APK 时会加载多个 dex 文件。其在安装时执行预编译,扫描 「classesN.dex」 文件,并把他们编译成单个.oat 文件。所以 “包含启动引用所需要的类及“依赖类”  可以散落在不同的 dex 文件上。

但是 「minsdkVersion < 21」 就不一样了,5.0 以下的机型用的是 Dalvik 虚拟机,在安装时仅仅会对 主dex 做编译优化,启动时直接加载 「主dex」。如果必要的类被散落到其他未加载的dex中,则会出现crash。也就是开头所说的   java.lang.NoClassDefFoundError

关于这个 exception 和 java.lang.ClassNoFoundError 很像,但是有比较大的区别,后者在 Android中常见于混淆引起类无法找到所致。

寻找解决方案

明白了上述背景之后,就要想办法减少 「主dex」 里的类且确保应用能够正常启动。

但是官方只告诉我们 「“如何 Keep 类来新增主 dex 里面的类”」,但是没有告诉我们怎么减少啊 !卧槽了...

于是开始 Google + 各种 github/issue 查看关于如何避免 「主dex」 方法爆炸的方案,全都是几年前的文章,这些文章出奇一致地告诉你。

「“尽量避免在application中引用太多第三方开源库或者避免为了一些简单的功能而引入一个较大的库”」

「“四大组件会被打包进 classes.dex”」

首先我觉得很无奈,无法知道构建系统是如何将四大组件打包进 classes.dex,项目内的代码无从考证。其次在版本 feature 已经验收完毕之下我无法直接对启动的依赖树进行调整,且业务迭代很久的前提下删除或者移动一个启动依赖是风险很大的改动。

我非常努力且小心翼翼地优化,再跑一下。

D8: Cannot fit requested classes in the main-dex file (# methods:87463 > 65536 ; # fields: 74531 > 65536)

此时的我非常绝望,按照这样优化不可能降低到 65536 以下。

在这里,我花费了很多时间在尝试网上所说的各种方案。我很难用 “浪费” 来描述对这段时间的使用,因为如果不是这样,我可能不会意识到对待这类问题上我的做法可能是错误的,并指导我以后应该这样做。

“被迫”啃下源码

既然是从 「.class」 到生成 「.dex」 环节出现了问题,那就只能从构建流程中该环节切入去熟悉。项目用的是 「AGP3.4.1」 版本,开始从 「Transform」 方向去尝试解惑:从 Gradle 源码 中尝试跟踪并找到一下问题的答案。主要要解决的问题有:

  1. 处理分包的 Transform 是哪个,主要做了什么
  2. 影响 maindexlist 最终的 keep 逻辑是怎么确定的 ?构建系统本身 keep 了哪些,开发者可以 keep 哪些?
  3. 从上游输入中接受的 clasee 是怎么根据 keep 逻辑进行过滤的
  4. maindexlist 文件是什么时候生成
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值