记录一次 AGP 调研过程中的思考,我从一个事故搞出了一个故事!
背景
看过我博客的老铁应该知道,我在 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 中,这块不清楚的可以研究下我背景信息里提到的之前做的小项目。
现在我有几个灵魂拷问想问你:
- 资源生成的 R 文件格式是怎么样的?不同 module 下又有什么区别?(答不上的去看我那个小项目的 REDME 吧)
- 使用官方 multidex 方案情况下还会存在 method 或者 field 超过 65535 的情况吗?本质原因是为什么?
- App 资源个数(包括 string 个数等)是否存在上限?为什么?
- 我自己编写了一个 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文件中的常量池》
灵魂拷问的答案
到此我们整明白了来龙去脉和问题的本质,那我们现在来深度回答下一开始卖关子的问题。
资源生成的 R 文件格式是怎么样的?不同 module 下又有什么区别?
去看 https://github.com/yanbober/app-tiny-R-gradle-plugin REDME 吧,很深度解析了。
使用官方 multidex 方案情况下还会存在 method 或者 field 超过 65535 的情况吗?本质原因是为什么?
很明显,使用官方 multidex 方案情况下不会出现 method 或者 field 超过 65535 的情况,因为 JVM 这一层就已经限制了玩法规则。本质就是上面隐秘的真相。
App 资源个数(包括 string 个数等)是否存在上限?为什么?
存在的,单一类别(string/anim/drawable等)资源最终主 module 合并时生成的单个 class 文件内常量池总数不能超过 65536(不是资源 id,一个 class 还有其他东西占用常量池的),否则无法生成对应的 R,因为 ASM 字节码在生成合并 R 时常量池爆炸了。
我自己编写了一个 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 调研过程中的思考,我从一个事故搞出了一个故事!相关推荐
- 小米是一个幼儿园老师,每学期的泥塑课上,她都会给每个学生发不超过 250250 立方厘米的等量橡皮泥,教大家做泥塑。在上课过程中,她发现每个班都恰好有一个小朋友会去抢另一个小朋友的橡皮泥
小米是一个幼儿园老师,每学期的泥塑课上,她都会给每个学生发不超过 250 立方厘米的等量橡皮泥,教大家做泥塑.在上课过程中,她发现每个班都恰好有一个小朋友会去抢另一个小朋友的橡皮泥,于是她决定,在正式 ...
- [项目过程中所遇到的各种问题记录]部署篇——项目部署过程中那些纠结的问题-SQLServer...
前一篇文章说了些有关IIS的,这篇则是说SQLServer的,相比IIS来说,SQLServer的配置过程中问题就少了许多,而且都比较有针对性,下面开始记录: 注:由于实际项目的开发都是基于SQL20 ...
- 记录qt窗口在拖动过程中出现的问题
问题描述: 在窗口拖动的过程中刚开始可以流畅的拖动窗口,但是一小会儿之后出现窗口拖不动的现象,或者按下鼠标在拖动区域内可以流畅拖动,但是按下鼠标朝一个方向拖动后,释放鼠标,按照此操作操作几次后,出现窗 ...
- 记录开发移动端项目过程中的各种问题、插件及教程(不定时更新)
本文为整理记录本人开发移动端项目的过程中,针对项目中业务需求所碰到的各种坑.用到的各种插件及教程整理?. 以下为教程整理: 1.再聊移动端页面的适配_Layout, 布局, mobile, CSS 教 ...
- 记录 ESIM 安装、使用过程中遇到的问题
参考 事件相机(Event-based camera)模拟器ESIM配置及使用指南_zkk9527的博客-CSDN博客,记录一下自己遇到的问题. 1.安装 ROS 我的环境是 UBUNTU 18.04 ...
- 【问题记录】启动 Navicat 的过程中,遇到:Missing required library sqlite.dll,998
问题描述 好久没有打开 Navicat 了.闲来无事,打开一看,呃!打不开了! 也不是第一次遇到这种情况了. 很多解决方法都说要把本机的一些杀毒软件关掉.其实,也没必要! 其实这个挺常见的.一般这种情 ...
- 记录装禅道XAMPP过程中 遇到的端口问题(1)
问题描述 这里碰到了三个问题 一是Apache启动失败: 20:23:28 [Apache] Error: Apache shutdown unexpectedly. 20:23:28 [Apache ...
- 学习bert过程中的思考,少走弯路
向AI转型的程序员都关注了这个号???????????? 机器学习AI算法工程 公众号:datayx 最近参加了一个nlp的比赛,做文本情感分类的.发现传统神经网络的效果的确赶不上bert.就研究 ...
- 剪辑过程中的思考与总结(持续更新ing)
目录 一.碎碎念 1. 视频画面暂停一段时间,再继续播放的剪辑方法 2. 绿幕暂停素材的使用,如何更加丝滑? 3. 保存效果套装,便于下次使用 二.字幕相关 1. 剪切字幕时的多帧问题 一.碎碎念 1 ...
- 说说项目从0-1过程中的那点事儿
俗话说项目不止,问题不止.产品就是用来解决问题的,那么在项目过程中都会遇到哪些问题呢,写下来记录那些套路.也希望跟大家交流一下,大家都是怎么解决的. 1.项目初期---产品(战略层)规划,需求收集阶段 ...
最新文章
- python 3389爆破机
- 深度学习核心技术精讲100篇(三十九)-医疗健康领域的短文本理解
- SAP系统和微信集成的系列教程之十:如何在SAP C4C系统里直接回复消息给微信用户
- 程序员三年的门槛该如何跨过去?
- 基于tomcat5.5的数据库连接池环境设置(省的以后找系列)
- 电商设计师套用PSD分层模板,玩转详情页的!
- 内网ip 设置_我的天,大牛黑客轻而易举打穿三层内网,吃惊
- gulp mysql_关于MySQL索引的一点小见解
- 解决输出顺时针螺旋数组问题【寻路算法】
- SPSS 相关分析(图文+数据集)【SPSS 023期】
- robocopy 备份_Windows 7系统强大的复制命令robocopy的操作方法介绍
- 【软件应用】word等office软件中好用的数学公式编辑器插件
- lavas一定是个不错的架构,利用VUE的PWA。是否可以取代APP?
- 如何用excel做正交分析_excel表格分析正交数据处理-excel中怎么对正交试验进行F值检验...
- 计算几何(一) by 邓俊辉老师
- 投资的收益与风险的数学建模
- matlab局部放大找交点,11.matlab找两条离散曲线的交点
- dflow入门2——Slices
- 三国演义人物词频统计 -- Python
- 【以太网交换安全】---端口安全及MAC地址飘移防止与检测