ByteX-shrink_r源码解析

news/2024/5/3 4:58:01/文章来源:https://blog.csdn.net/a1018875550/article/details/128125908

背景

为什么要对R文件内联处理?

这里首先说一下Android R文件的产生,对于Android开发者我们都知道,当我们要使用要使用一些布局文件,drawable等其他资源时,可以直接用 R.id. ``R.drawble.等直接使用,而这个R.java文件类的创建是在Android编译打包的过程中,位于res/目录下的文件,就会通过AAPT工具,对里面的资源进行编译压缩,从而生成相应的资源id,且生成R.java文件,用于保存当前的资源信息,同时生成resource.arsc文件,建立id与其对应资源的值。

最终生成了如下图内容的代码

这里解释一下这个资源id:0x7f0600c9的含义,由三部分组成:PackageId+TypeId+EntryId ,0x7f0600c9 可以拆解为0x7f +06 +00c9

  • PackageId:是包的Id值,Android 中如果第三方应用的话,这个默认值是 0x7f ,系统应用的话就是 0x01 ,插件的话那么就是给插件分配的id值,占用一个字节。
  • TypeId: 是资源的类型Id值,一般有这几个类型:attrdrawablelayoutanimrawdimenstringboolstyleintegerarraycoloridmenu 等。应用程序所有模块中的资源类型名称,按照字母排序之后。值是从1开支逐渐递增的,而且顺序不能改变(每个模块下的R文件的相同资源类型id值相同)。比如:anim=0x01占用1个字节,那么在这个编译出的所有R文件中anim 的值都是0x01
  • EntryId: 是在具体的类型下资源实例的id值,从0开始,依次递增,他占用四个字节。

正常情况下APP的R文件就这样产生结束了,但是当我们的开发是多Module模式开发时问题就来了,module或者aar也会产生R文件,然后打包apk后的R文件格式会产生如下结构

在这里插入图片描述

可以看到每个moudle都有各自的R文件,同时上层R文件会融合下层的R文件资源。但是这会带来一个问题,就是

  • R文件越来越多是否冗余了,导致包大小增大
  • 上层的R文件很容易出现R Field过多,导致MultiDex 65536的问题。(如果miniSDK>21可以忽略)

R文件内联

其实Android Studio在编译时已经为我们做了内联处理,比如我们看一下APP module的smail文件

  • 源代码

[(img-T9C7XRW6-1669856202728)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5207e3f0b1c34b3fb99a412e529ec737~tplv-k3u1fbpfcp-zoom-1.image)]

  • 反编译后smail代码

[(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/daee163327b54ccd984385c6a1653de6~tplv-k3u1fbpfcp-zoom-1.image)]

可以看到源码里的R.layout.activity_main已经被替换成了资源id0x7f08007e

但是我们看一下MoudleA反编译后的smali代码

  • 源代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LONcKLN-1669856202729)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b8d27f127614d08b8b95bc0f2396c52~tplv-k3u1fbpfcp-zoom-1.image)]

  • 反编译后smail代码

在这里插入图片描述

可以看到module工程里的资源文件并没有被内联处理,为什么会这样?这是因为 module的class文件,在主工程编译时,不会再次进行编译,module的class文件原封不动的打包进apk。而资源id为常量是在主工程编译时才形成的,但module生成class时,使用的是上面说到的变量,所以一直被保留了下来。

什么是shrink_r?

ByteX是字节团队开源的一个字节码插桩工具,而shrink_r是其中的一个插件是用来对

  • R文件常量内联,R文件瘦身;
  • 无用Resource资源检查;
  • 无用assets检查。

bytex.shrink_r就是为了解决上述问题中module工程里R文件没有被内联产生的一种方案,他通过ASM操作class文件进行操作对使用到R类变量的地方进行常量值替换,然后删除R文件从而达到减少包大小的目的。

使用收益

下面来看一下使用的前后效果收益对比

  • 使用前

    • 包大小

    • 在这里插入图片描述

    • R文件数量

    • 在这里插入图片描述

  • 使用后

    • 包大小

    • 在这里插入图片描述

    • R文件数量

    • 在这里插入图片描述

Moudle的R文件被删除了,然后module工程的也被内联替换成了资源id

shrink_r源码解析

在这里插入图片描述

由于shrink_r是用bytex框架,所以我们先从ShrinkRFilePlugin.traverse()看起

traverse() -第一次工程遍历

public void traverse(@NotNull String relativePath, @NotNull ClassVisitorChain chain) {super.traverse(relativePath, chain);if (Utils.isRFile(relativePath)) {chain.connect(new AnalyzeRClassVisitor(context));}
}

traverse()方法里判断如果是R文件,则进入R class文件分析类AnalyzeRClassVisitor,该类主要是用来保存需要替换R资源id的,主要看三个方法

  • visitField 访问变量,主要做了两件事

    • 保存需要替换的资源id
    • 保存不需要替换的资源id
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {if (TypeUtil.isPublic(access)&& TypeUtil.isStatic(access)&& TypeUtil.isFinal(access)&& !context.shouldKeep(this.className, name)) {if (TypeUtil.isInt(desc) && value != null) {// 保存,需要替换的资源idcontext.addShouldBeInlinedRField(className, name, value);}} else {discardable = false;// 不需要替换,也保存id,做兜底判断context.addSkipInlineRField(className, name, value);}return super.visitField(access, name, desc, signature, value);
}
  • visitMethod 访问方法,该方法主要就是判断是不是R$styleable类的初始化方法,对于styleable类特别处理
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);if (this.isRStyleableClass && Utils.isClassInit(name)) {if (discardable) {return new AnalyzeStyleableClassVisitor(mv, context);}}return mv;
}
  • visitEnd()所有访问结束,判断当前类是否需要添加到替换的类集合
@Override
public void visitEnd() {super.visitEnd();if (discardable) {context.addShouldDiscardRClasses(className);}
}

transform() - 第二次遍历,字节码转化

就是在该方法里做了对R类变量的地方进行常量值替换,该方法做了两件事

  • 删除白名单外的R文件
  • 替换R类变量
@Override
public boolean transform(@NotNull String relativePath, @NotNull ClassVisitorChain chain) {if (context.discardable(relativePath)) {// 如果是白名单外的R文件,返回false删除context.getLogger().d("DeleteRFile", "Delete R file: " + relativePath);return false;}// 不是R文件,变量类,进行R类变量替换chain.connect(new ShrinkRClassVisitor(context));return super.transform(relativePath, chain);
}

ShrinkRClassVisitor类里对每个方法进行方法,然后对方法里使用R类变量的地方进行常量值替换处理

@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {if (isRClass && context.shouldBeInlined(className, name)/* && !context.shouldKeep(className, name)*/) {// R文件且是删除需要变量,返回null,进行删除context.getLogger().i("DeleteField", String.format("Delete field = [ %s ] in R class = [ %s ]", name, className));return null;} else if (isRClass) {// 白名单的R文件,keep保留context.getLogger().i("KeepField", String.format("Keep field = [ %s ] in R class = [ %s ]", name, className));}return super.visitField(access, name, desc, signature, value);
}

visitMethod()访问方法,对方法里使用R类变量的地方进行常量值替换处理,所有的替换都在ReplaceRFieldAccessMethodVisitor处理了

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);if (!isRClass) {return new ReplaceRFieldAccessMethodVisitor(mv, context, name, className);}return mv;
}

下面看一下ReplaceRFieldAccessMethodVisitor.visitFieldInsn()方法

public void visitFieldInsn(int opcode, String owner, String name, String desc) {// 判断是不是静态的filedif (opcode == Opcodes.GETSTATIC) {Object value = null;try {// 通过集合根据owner和name获取当前是否是R文件的资源id常量value = context.getRFieldValue(owner, name);} catch (RFieldNotFoundException e) {context.addNotFoundRField(className, methodName, owner, name);}if (value != null) {if (value instanceof List) {// 替换styable资源replaceStyleableNewArrayCode((List<Integer>) value);} else if (value instanceof Integer) {// 检查资源是否被使用,resManager.reachResource((Integer) value);// 替换成常量mv.visitLdcInsn(value);}return;}}super.visitFieldInsn(opcode, owner, name, desc);
}
private void replaceStyleableNewArrayCode(List<Integer> valList) {int size = valList.size();visitConstInsByVal(mv, size);mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_INT);for (int i = 0; i < size; i++) {mv.visitInsn(Opcodes.DUP);visitConstInsByVal(mv, i);mv.visitLdcInsn(valList.get(i));mv.visitInsn(Opcodes.IASTORE);}
}

到这里资源id替换成常量就结束了

总结

总共流程如下

  • 第一遍遍历traverse class获取到所有待替换R文件类变量的常量
  • 第二遍遍历所有类,替换所有需要替换的R类变量为常量,并删除R文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_228404.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

易基因科技|单细胞甲基化测序低至2500元/样

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。12月活动来袭&#xff5e; 限时特惠&#xff01;单细胞甲基化测序低至2500元/样&#xff01; 易基因高通量单细胞DNA甲基化测序&#xff1a;单细胞DNA甲基化组学研究很大程度上受制于建库…

定时执行专家 —— 使用网络唤醒功能实现远程开机

目录 ◆ 关于网络唤醒 ◆ 定时执行专家 - 远程开机功能 - 设置方法 ◆ 使用网络唤醒实现远程开机的一些前提条件 ◆ 关于网络唤醒 Wake-on-LAN简称WOL或WoL&#xff0c;中文多译为“网上唤醒”、“远程唤醒”技术。WOL是一种技术&#xff0c;同时也是该技术的规范标准&…

CSS自定义属性与前端页面的主题切换

基于级联变量的CSS自定义属性&#xff0c;已经出来很多年了。 虽然有less、sass等预处理器大行其道&#xff0c;但是自定义属性也有它的特点和用处&#xff0c;诸如在js中读写、作用域设置等等&#xff0c;在处理UI主题切换等功能上也发挥着很大的作用。 自定义属性 CSS自定义…

[附源码]SSM计算机毕业设计学习资源共享与在线学习系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Linux驱动: rtc子系统

1. 前言 限于作者能力水平&#xff0c;本文可能存在的谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 背景 本文分析代码基于Linux 3.10内核&#xff0c;硬件平台为嵌入式ARM32平台. 3. rtc子系统 3.1 相关代码文件列表 drivers/rtc/class.c …

Lactoferrin-PEG-alginate 乳铁蛋白-聚乙二醇-海藻酸钠

产品名称&#xff1a;乳铁蛋白-聚乙二醇-海藻酸钠 英文名称&#xff1a;Lactoferrin-PEG-alginate 纯度&#xff1a;95% 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观:固体或粘性液体&#xff0c;取决于分子量 PEG分子量可选&#xff1a;350、550、750、1k、…

Redis高级篇——Redis的优化

一、Redis的键值设计 1.1key的结构 Redis的Key在自定义时&#xff0c;最好遵循以下三个规则&#xff1a; 基本格式&#xff1a;[业务名称]&#xff1a;[数据名]&#xff1a;[id]长度不超过44字节不包含特殊字符 如&#xff1a;登录业务&#xff0c;保存用户信息的key 定义为…

python常用代码总结2

1、列表的常规追加元素、追加列表操作 (1)列表追加多个元素&#xff0c;比如追加0-9 ls [] ls.extend(list(range(10)))ls Out[20]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (2)列表追加多个相同的元素&#xff0c;比如追加10个0 ls1 [] for i in range(10):ls1.append(0)ls1 Ou…

直播邀请函 | 第12届亚洲知识产权营商论坛:共建创新价值 开拓崭新领域

由香港特别行政区政府、香港贸易发展局及香港设计中心共同举办的亚洲知识产权营商论坛&#xff0c;每年为世界各地知识产权业界专家、商界领袖提供一个理想平台&#xff0c;共同探讨亚洲知识产权市场的最新发展&#xff0c;发掘更多商机。 去年&#xff0c;论坛共邀请70余位国…

聚观早报 | 中国茶申遗成功;特斯拉市值蒸发4个推特

今日要闻&#xff1a;中国茶申遗成功&#xff1b;特斯拉市值蒸发4个推特&#xff1b;iPhone14Pro出货量预期下调&#xff1b;FF91距交付仅剩一步之遥&#xff1b;AI绘画一天新增60万用户中国茶申遗成功 11月29日晚&#xff0c;我国申报的“中国传统制茶技艺及其相关习俗”在摩洛…

[附源码]JAVA毕业设计高速公路服务区管理系统(系统+LW)

[附源码]JAVA毕业设计高速公路服务区管理系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项…

最全面的Spring教程(五)——文件上传与下载

前言 本文为 【SpringMVC教程】文件上传与下载 相关知识&#xff0c;具体将对使用MultipartResolver处理文件上传的步骤&#xff0c;两种文件下载方式&#xff08;直接向response的输出流中写入对应的文件流、使用 ResponseEntity<byte[]>来向前端返回文件&#xff09;等…

智慧城市运营中心建设方案(SCOC)智慧城市的心脏

一、大数据&#xff1a;智慧城市的基础与引擎 中国每天正以消失100个村庄的速度快速步入城镇化&#xff0c;未来10年内将有5亿以上的人涌入城市。这无疑会给城市的建设带来巨大的压力&#xff0c;城市资源有限&#xff0c;规模不可能无限扩张&#xff0c;城市在就业、教育、住房…

HashMap底层数据结构,扩容机制

HashMap的底层结构是数组链表 没有哈希冲突的元素放在数组里面&#xff0c; 哈希冲突的元素用链表串起来 初始数组长度是16&#xff0c;对应源码&#xff1a; static final int DEFAULT_INITIAL_CAPACITY1; 最大的容量为2的30次方&#xff0c;一个很大很大很大的数 还定义了…

多个JDK版本可以吗:JDK17、JDK19、JDK1.8轻松切换(无坑版)小白也可以看懂

多个版本JDK切换 多个JDK&#xff1a;JDK17、JDK19、JDK1.8轻松切换&#xff08;无坑版&#xff09;小白也可以看懂 提示&#xff1a;看了网上很多教程&#xff0c;5w观看、32w观看、几千观看的&#xff0c;多多少少带点坑&#xff0c;这里我就把踩过的坑都给抹了 文章目录多个…

【数据结构】二叉树的运算

********************************************************************************************************* 本文作者科大MF22某班Noah懒羊羊同学&#xff0c;为大家提供一个作业思路&#xff0c;请勿直接copy&#xff01;&#xff01;&#xff01;一起进步学习~ ******…

极值分析:分块极大值BLOCK-MAXIMA、阈值超额法、广义帕累托分布GPD拟合降雨数据时间序列...

全文链接&#xff1a;http://tecdat.cn/?p25348 你们可能知道&#xff0c;实际极值分析有两种常用方法&#xff1a;分块极大值Block-maxima、阈值超额法threshold excess&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。今天&#xff0c;我们将分别介绍这两种…

【毕业设计】10-基于单片机的车站安检门_磁性霍尔传感器系统设计(原理图+源码+仿真工程+答辩论文)

【毕业设计】10-基于单片机的车站安检门/磁性霍尔传感器系统设计&#xff08;原理图源码仿真工程答辩论文&#xff09; 文章目录【毕业设计】10-基于单片机的车站安检门/磁性霍尔传感器系统设计&#xff08;原理图源码仿真工程答辩论文&#xff09;任务书设计说明书摘要设计框架…

【数据结构】Java实现数据结构的前置知识,时间复杂度空间复杂度,泛型类的讲解

文章目录数据结构时间复杂度、空间复杂度包装类、装箱与拆箱泛型擦除机制数据结构 当我们在成为一名程序员的这条道路上努力的时候&#xff0c;我们一定经常听到这个词数据结构。那么究竟什么是数据结构呢&#xff1f;数据结构顾名思义&#xff0c;就是数据结构&#xff0c;数…

【深度学习】详解 CLIP

目录 摘要 一、引言和激励性工作 二、方法 2.1 自然语言监督 2.2 创建一个足够大的数据集 2.3 选择一种有效的预训练方法 2.4 选择和放缩一个模型 2.5 训练 三、实验 3.1 零次迁移 3.1.1 激励 Github&#xff1a;GitHub - openai/CLIP: Contrastive Language-Image …