背景

看过我博客的老铁应该知道,我在 18 年五月写过一个小 gradle 插件https://github.com/yanbober/app-tiny-R-gradle-plugin,其作用就是将 app 生成的 R 常量进行内联操作。对,就是前不久很火的滴滴 booster 和字节跳动 ByteX 提供的 R 资源 inline 原理。

这两天因为项目要升级适配 AGP4.1.0 版本,顺手要调研 AGP 4.1.0 构建对子 module 及合成最终 app 的 intermediates 产物 R 变化问题。这个过程中却意外发现了一个有趣且有深度的事情,细思极恐,越想越有趣。好的事故往往都能成为好的故事。

先卖个关子

我们都知道,Android App 构建过程中 aapt2 会将资源生成对应R.class文件(AGP4.1.0 中间产物直接成为 jar,位于compile_and_runtime_not_namespaced_r_class_jar目录下),然后最终合并打包到 dex 中,这块不清楚的可以研究下我背景信息里提到的之前做的小项目。

现在我有几个灵魂拷问想问你:

  1. 资源生成的 R 文件格式是怎么样的?不同 module 下又有什么区别?(答不上的去看我那个小项目的 REDME 吧)
  2. 使用官方 multidex 方案情况下还会存在 method 或者 field 超过 65535 的情况吗?本质原因是为什么?
  3. App 资源个数(包括 string 个数等)是否存在上限?为什么?
  4. 我自己编写了一个 field 超过 65535 的类会有问题吗?能在官方 multidex 场景下使用吗?

上面这四个灵魂拷问你能深入回答下吗?不能的话就请继续往下看,带你玩波有趣的东西。

还原现场

上哪去搞那么多资源能一把搞炸 65535 个 field 呢?懒惰的我来波骚操作,打开我的 IDE,新建一个createR.sh文件,内容如下(不要在意用的原始 echo,因为懒惰,能用就行):

#!/bin/bash
# encoding=utf-8
# 【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】echo "start generate string_r.xml";file_name="string_r.xml";echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>" >> $file_name;
echo "<resources>" >> $file_name;for((i=1;i<=65536;i++));
doecho "<string name=\"public_r_$i\">TEST-$i</string>" >> $file_name;
doneecho "</resources>" >> $file_name;
echo "generate string_r.xml success!";

保存,敲下回车,执行一把等待中,去厕所带薪拉屎一会,回来文件 OK 了。内容如下,总共 65536 个 string 资源:


为了一次暴露所有问题,直接把这个资源文件扔到主模块的 values 下吧,接着点击一把 Android Studio 的运行,真相了:


上面报错可能出乎你意料了吧?为什么会出现这个错误呢?从 task 执行顺序可以确认,此时还未执行 javac 操作,还在进行 aapt2 的处理,资源合并 task 时抛出了异常,这个资源合并其实会做很多事,其中一个重要的事情就是通过 ASM 生成合并后的 R 文件。

你可能会问,哪里看出来是通过 ASM 生成的?我说我从 AGP 下载时的依赖看到的你信吗?其实很容易验证,你在执行构建时加上-s就行了,这样出错时会有详细调用栈,你能清晰的看到调用关系是 ASM 在生成字节码。

Class too large 是个什么鬼?你是不是一上来也觉得是类似 AGP 构建时对 multidex 的判断那样,在 AGP 源码里做了一个判断(官方埋雷?)。明确告诉你,不是的,不信你去 AGP 源码搜下,啥也搜不到。那它到底是咋回事呢?

隐秘的真相

现在我们一步一步来揭盖 Class too large 是什么!在执行构建时我们加上-s可以看到如下堆栈:

这货是在 task 使用 ASM 生成字节码时报的错,所以如上图,直接去 ASM 里面搜一下,果然搜到了哈。ASM 为什么要限制不能超过 0xFFFF 个呢?其实答案很明显了,如果你对 JVM 基础不熟悉的话,不妨继续往下看,我们看下这段 ASM 源码的注释:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】package org.objectweb.asm;/*** A {@link ClassVisitor} that generates classes in bytecode form. More* precisely this visitor generates a byte array conforming to the Java class* file format. It can be used alone, to generate a Java class "from scratch",* or with one or more {@link ClassReader ClassReader} and adapter class visitor* to generate a modified class from one or more existing Java classes.* * @author Eric Bruneton*/
public class ClassWriter extends ClassVisitor {/*** Index of the next item to be added in the constant pool.*/int index;/*** Returns the bytecode of the class that was build with this class writer.* * @return the bytecode of the class that was build with this class writer.*/public byte[] toByteArray() {if (index > 0xFFFF) {throw new RuntimeException("Class file too large!");}......}
}

可以看到,index 有个关键的注释Index of the next item to be added in the constant pool.,能 get 到问题原因了吗?constant pool啊,哈哈,这特么就真相了。

还不懂?那去补补 JVM 基础吧,周老师的神书前几章就足矣!

这里给两个直达链接简单科普常量池的:

《Java Class文件结构:常量池》
《Java Class文件中的常量池》

灵魂拷问的答案

到此我们整明白了来龙去脉和问题的本质,那我们现在来深度回答下一开始卖关子的问题。

  1. 资源生成的 R 文件格式是怎么样的?不同 module 下又有什么区别?

    去看 https://github.com/yanbober/app-tiny-R-gradle-plugin REDME 吧,很深度解析了。

  2. 使用官方 multidex 方案情况下还会存在 method 或者 field 超过 65535 的情况吗?本质原因是为什么?

    很明显,使用官方 multidex 方案情况下不会出现 method 或者 field 超过 65535 的情况,因为 JVM 这一层就已经限制了玩法规则。本质就是上面隐秘的真相。

  3. App 资源个数(包括 string 个数等)是否存在上限?为什么?

    存在的,单一类别(string/anim/drawable等)资源最终主 module 合并时生成的单个 class 文件内常量池总数不能超过 65536(不是资源 id,一个 class 还有其他东西占用常量池的),否则无法生成对应的 R,因为 ASM 字节码在生成合并 R 时常量池爆炸了。

  4. 我自己编写了一个 field 超过 65535 的类会有问题吗?能在官方 multidex 场景下使用吗?

    会有问题,无法编译通过,不符合 JVM Class 规范。既然无法编译通过,所以不存在在官方 multidex 场景下的使用,因为到不了分 dex 那一步就阵亡了。

会不会遇到世界末日

你以为故事到这就结束了?到这里不由得虎躯一震反思一下: “这锅会不会和当年 multidex 一样在未来航母级 app 某个时刻翻车呢?” 答案是有可能,但是短期不会,因为想要到达这个瓶颈就需要我们单一类型资源越界,这个其实目前还少有 app 到这个体量,即便航母 app 也不容易达到,除非你杠精一下。

这个问题的本质其实就回到了 google 官方一直对这个 R 的态度了,这玩意一直小变动,却总是不想办法从根源治理。远古 apt 时代子 module 的 R 里面 field 也是 static final 的常量,后来 google 为了加速构建,子 module 搞成非 static final(导致一些注解框架自己造一个 R2),主 module 合成,然后主 module 搞一份 static final 的,同时保留子 module 的非 static final,一直至今都叫R.java,然后 AGP4.1.0 版本这玩意直接不再出现R.java,而是一步到位R.jar的 class jar 了,而且子 module 的非 static final 的属性也不再给随机安插一个数值了,直接不赋值了。玩到这个版本还是没根治啊。

假若将来某一天真的重蹈覆辙 multidex 的道路怎么办?能想到的好方法就是 google 出马优化掉这玩意。否则我们作为三方 app 可能只能骚操作了,目前想到的两个骚操作就是:

  • 方案一:类似插件化,把越界资源编成多个 apk,hook 资源加载骗过呗,不过这玩意只是我先 YY 下,因为鬼知道骗过了 Class 常量池上限,会不会资源加载那块也埋雷了,这样就不好玩了。
  • 方案二:自己类似 java resources 一样造一套资源,不再参与 aapt2 编译,而是直接参与 java 编译打包,然后自己拖过映射多语言啥的场景,对外保留一个 nameId 获取和 android 的资源管理类对接。这个看起来是可行的,只是包大小和性能一定有影响,不然 google 当年也不会把它搞成resources.arsc和 R 索引了。

如上纯属自己的 YY,看看就好,别较真。

总结

歪打正着,本来是要去看别的问题的,一下被带到思考了一下这个问题,还行。可以看到,其实问题不复杂,也不难,稍微跟一下代码就能知道咋回事了,就一点,做事还得静下来,这样才能深度思考,然后才能若有所思。

日拱一卒,功不唐捐。今日已拱,哈哈。

【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】

记录一次 AGP 调研过程中的思考,我从一个事故搞出了一个故事!相关推荐

  1. 小米是一个幼儿园老师,每学期的泥塑课上,她都会给每个学生发不超过 250250 立方厘米的等量橡皮泥,教大家做泥塑。在上课过程中,她发现每个班都恰好有一个小朋友会去抢另一个小朋友的橡皮泥

    小米是一个幼儿园老师,每学期的泥塑课上,她都会给每个学生发不超过 250 立方厘米的等量橡皮泥,教大家做泥塑.在上课过程中,她发现每个班都恰好有一个小朋友会去抢另一个小朋友的橡皮泥,于是她决定,在正式 ...

  2. [项目过程中所遇到的各种问题记录]部署篇——项目部署过程中那些纠结的问题-SQLServer...

    前一篇文章说了些有关IIS的,这篇则是说SQLServer的,相比IIS来说,SQLServer的配置过程中问题就少了许多,而且都比较有针对性,下面开始记录: 注:由于实际项目的开发都是基于SQL20 ...

  3. 记录qt窗口在拖动过程中出现的问题

    问题描述: 在窗口拖动的过程中刚开始可以流畅的拖动窗口,但是一小会儿之后出现窗口拖不动的现象,或者按下鼠标在拖动区域内可以流畅拖动,但是按下鼠标朝一个方向拖动后,释放鼠标,按照此操作操作几次后,出现窗 ...

  4. 记录开发移动端项目过程中的各种问题、插件及教程(不定时更新)

    本文为整理记录本人开发移动端项目的过程中,针对项目中业务需求所碰到的各种坑.用到的各种插件及教程整理?. 以下为教程整理: 1.再聊移动端页面的适配_Layout, 布局, mobile, CSS 教 ...

  5. 记录 ESIM 安装、使用过程中遇到的问题

    参考 事件相机(Event-based camera)模拟器ESIM配置及使用指南_zkk9527的博客-CSDN博客,记录一下自己遇到的问题. 1.安装 ROS 我的环境是 UBUNTU 18.04 ...

  6. 【问题记录】启动 Navicat 的过程中,遇到:Missing required library sqlite.dll,998

    问题描述 好久没有打开 Navicat 了.闲来无事,打开一看,呃!打不开了! 也不是第一次遇到这种情况了. 很多解决方法都说要把本机的一些杀毒软件关掉.其实,也没必要! 其实这个挺常见的.一般这种情 ...

  7. 记录装禅道XAMPP过程中 遇到的端口问题(1)

    问题描述 这里碰到了三个问题 一是Apache启动失败: 20:23:28 [Apache] Error: Apache shutdown unexpectedly. 20:23:28 [Apache ...

  8. 学习bert过程中的思考,少走弯路

    向AI转型的程序员都关注了这个号???????????? 机器学习AI算法工程   公众号:datayx 最近参加了一个nlp的比赛,做文本情感分类的.发现传统神经网络的效果的确赶不上bert.就研究 ...

  9. 剪辑过程中的思考与总结(持续更新ing)

    目录 一.碎碎念 1. 视频画面暂停一段时间,再继续播放的剪辑方法 2. 绿幕暂停素材的使用,如何更加丝滑? 3. 保存效果套装,便于下次使用 二.字幕相关 1. 剪切字幕时的多帧问题 一.碎碎念 1 ...

  10. 说说项目从0-1过程中的那点事儿

    俗话说项目不止,问题不止.产品就是用来解决问题的,那么在项目过程中都会遇到哪些问题呢,写下来记录那些套路.也希望跟大家交流一下,大家都是怎么解决的. 1.项目初期---产品(战略层)规划,需求收集阶段 ...

最新文章

  1. python 3389爆破机
  2. 深度学习核心技术精讲100篇(三十九)-医疗健康领域的短文本理解
  3. SAP系统和微信集成的系列教程之十:如何在SAP C4C系统里直接回复消息给微信用户
  4. 程序员三年的门槛该如何跨过去?
  5. 基于tomcat5.5的数据库连接池环境设置(省的以后找系列)
  6. 电商设计师套用PSD分层模板,玩转详情页的!
  7. 内网ip 设置_我的天,大牛黑客轻而易举打穿三层内网,吃惊
  8. gulp mysql_关于MySQL索引的一点小见解
  9. 解决输出顺时针螺旋数组问题【寻路算法】
  10. SPSS 相关分析(图文+数据集)【SPSS 023期】
  11. robocopy 备份_Windows 7系统强大的复制命令robocopy的操作方法介绍
  12. 【软件应用】word等office软件中好用的数学公式编辑器插件
  13. lavas一定是个不错的架构,利用VUE的PWA。是否可以取代APP?
  14. 如何用excel做正交分析_excel表格分析正交数据处理-excel中怎么对正交试验进行F值检验...
  15. 计算几何(一) by 邓俊辉老师
  16. 投资的收益与风险的数学建模
  17. matlab局部放大找交点,11.matlab找两条离散曲线的交点
  18. dflow入门2——Slices
  19. 三国演义人物词频统计 -- Python
  20. 【以太网交换安全】---端口安全及MAC地址飘移防止与检测

热门文章

  1. 服务器硬盘红灯常亮_硬盘指示灯一直亮
  2. 【python】【爬虫】爬取电子书《红星照耀中国》
  3. idea中设置EcmaScript6
  4. 智能化酒糟池测温技术方案
  5. Mathtype 花体字 Euclid math one/two 不能显示的问题
  6. 数据库原理和应用(2)—— 数据库系统的组成
  7. 两个路由器如何通过一根网线组建局域网(非wifi桥接方式)
  8. 设置<hr>标签的样式
  9. 想成为一名数据科学家?你得先读读这篇文章
  10. 海门中等专业学校计算机,江苏海门中等专业学校2021年有哪些专业