从Dalvik字节码角度优化安卓编码

news/2025/11/13 22:45:05/文章来源:https://www.cnblogs.com/RainbowC0/p/19081965

目录
  • 静态属性与this指针
  • 字段与局部变量
  • final属性与编译优化
  • 内部类与桥接方法
  • 匿名类与Lambda
  • 小结

安卓开发中,Java/Kotlin等高级语言被编译成.class字节码,之后通过dx/d8r8等工具编译成dex文件(Dalvik字节码),打包到APK中。安卓通过ART或者DalvikVM加载运行Dalvik字节码。因此,对于安卓编码,Dalvik字节码层面相比.class字节码层面更有指导意义。

静态属性与this指针

静态属性(用static关键字修饰,包括字段以及方法)是类的属性,而非静态属性则是实例的属性。由于非静态属性往往跟实例绑定,静态属性的访问不存在实例,需要的参数更少。

DalvikVM是基于寄存器的指令集,每个方法内的pn寄存器都是参数寄存器,而vn都是本地寄存器,非静态方法中的p0表示this指针。

如下代码是读取类a.b.C的非静态字段bool和静态字段sbool,可以看到在读取非静态字段时会从p0(this)进行引用,而读取sbool则不需要。

iget-boolean v0, p0 La/b/C;->bool:Z
sget-boolean v1 La/b/C->sbool:Z

另外,对于代码调用,静态方法使用invoke-static,不需要传入实例本身;而非静态方法使用invoke-virtual调用,即便是从p0(this)调用自己的方法,也需要传入实例。

.method call()V.registers 2invoke-virtual {p0}, La/b/C;->getBool()Zmove-result-object v0invoke-static La/b/C;->sgetBool()Zmove-result-object v0return-void
.end.method getBool()Z.registers 2sget-boolean v0, La/b/C;->sbool:Zreturn v0
.end.method static sgetBool()Z.registers 1sget-boolean v0, La/b/C;->sbool:Zreturn v0
.end

可以看到,在调用getBoolsgetBool时,由于方法是否静态的差别,其调用参数也有差别:虽然二者的字节码指令一致,仅使用寄存器v0,但非静态方法调用时仍需传参p0。

另外,考虑以下调用:

a.b.C obj = null;obj.sgetBool();
obj.getBool(); // NullPointerException

其中obj为一个空值,其在调用非静态方法getBool时必然抛出NullPointerException异常,但却可以安全调用静态方法sgetBool,因为编译器编译后会直接换作obj的类进行invoke-static静态调用,与实例本身无关。

因此,在不考虑子类重写以及使用this时,尽量用static修饰方法

字段与局部变量

前面说到Dalvik字节码是基于寄存器的指令集,经过ART的AOT/JIT后也更方便生成机器码,这与基于堆栈的.class字节码不同。局部变量在生成Dalvik字节码时往往都用寄存器表示,因此,在安卓开发中使用局部变量时直接当作寄存器即可,不必像堆栈型JVM那样考虑堆栈操作的开销。

考虑以下代码:

int a;void compute1() {a = a * a - 1;
}void compute2() {int i = a;a = i * i - 1;
}

对应的Dalvik字节码为:

.field a:I.method compute1()V.locals 2iget v0, p0, LMain;->a:Iiget v1, p0, LMain;->a:Imul-int/2addr v0, v1add-int/lit8 v0, v0, -0x1iput v0, p0, LMain;->a:Ireturn-void
.end method.method compute2()V.locals 1iget v0, p0, LMain;->a:Imul-int/2addr v0, v0add-int/lit8 v0, v0, -0x1iput v0, p0, LMain;->a:Ireturn-void
.end method

对于compute1,读了两次字段a、写了一次;对于compute2,读了一次,写了一次。因此虽然后者的Java代码更多,但是生成的Dalvik字节码要少一句读a的操作,而局部变量i则用寄存器v0表示。另外,这种情况下两次对a的访问操作也不能通过编译优化为一次,因为Java多线程情况下,如果由于线程调度使两次获取a的值不一致,如果编译优化则会使其一致,影响了代码逻辑。

很多人写代码时,针对全局变量或者字段进行访问时,为图代码规整、简洁,大量重复访问同一字段,既使Dalvik字节码变得冗余,又增加了多线程不一致的隐患。建议在需要多次访问一个字段且保持一致性时,先赋值给一个局部变量,后续的访问仅针对这个局部变量进行操作;或者可以用final修饰字段(因为final修饰时多次访问可以被编译优化为一次)。

final属性与编译优化

常量替换与函数内联是一种常见的编译优化手段,用于将一些常量的值或短函数的代码直接嵌入到引用处,减少了寻址、调用栈变动的开销。考虑到JVM的继承与重写特性,可被子类修改的字段以及方法(无private且无final修饰)往往不会被常量替换与方法内联。因此,如果需要对这类字段或方法进行优化,尽量加上final修饰,如下:

boolean testa(int n) {return n != 0;
}
public final boolean testb(int n) {return n != 0;
}
private boolean testc(int n) {return n != 0;
}
void test() {int n = (int)(Math.random() * 100);System.out.println(testa(n));System.out.println(testb(n)); // System.out.println(n != 0);System.out.println(testc(n)); // System.out.println(n != 0);
}

如果编译器支持内联优化,那么:

  • testbtestc均会被内联优化为n != 0,而testa不会,因为privatefinal都保证了这些方法不能被子类重写,在编译阶段就能确认它们具体调用的代码;
  • testa可以被子类重写,因此此处调用testa的具体代码是什么还要看子类是否会重写,因此不能确认具体调用的代码,无法进行内联优化。

同理,仅有被final修饰的全局字面值常量才能进行常量替换。当然也有一些局部变量也可以被替换,但这应归于一种更广义的编译优化:预计算。

内部类与桥接方法

Dalvik字节码中的成员类是如何访问主类的私有属性的?考虑以下代码:

class Main{private int pA;private void pM() {}class Member{Member() {pA = 0;pM();}}
}

如果是在Hotspot中,这不成问题,private属性对成员类自然开放,可直接由invoke族、getfieldputfield等指令进行操作。但是对于Dalvik字节码中,成员类不能直接访问主类的private方法,需要由编译器生成一些桥接方法(Bridge Method)实现。

Hotspot本身支持桥接方法,主要用于子类对父类重写一些方法并改变一些签名类型时使用,比如父类方法签名()Ljava/lang/CharSequence;,而子类重写时改为()Ljava/lang/String;,此时编译器会给子类生成一个桥接方法来实现签名兼容。而在生成Dalvik字节码,通过对主类生成一些访问权限更宽的桥接方法来实现成员类对主类私有方法、字段的访问。上述代码生成的部分Dalvik字节码如下:

# ======== 成员类 ========
.class LMain$Member;.method constructor <init>(LMain;)V.registers 3invoke-direct {p0}, Ljava/lang/Object;-><init>()V        # super();const/4 v0, 0x0invoke-static {p1, v0}, LMain;->-$$Nest$fputpA(LMain;I)V # Main.this.pA = 0;invoke-static {p1}, LMain;->-$$Nest$mpM(LMain;)V         # Main.this.pM();return-void
.end method# ======== 主类 ========
.class LMain;.field private pA:I# 桥接方法:修改pA
.method static bridge synthetic -$$Nest$fputpA(LMain;I)V.registers 2iput p1, p0, LMain;->pA:Ireturn-void
.end method# 桥接方法:调用pM
.method static bridge synthetic -$$Nest$mpM(LMain;)V.registers 1invoke-direct {p0}, LMain;->pM()Vreturn-void
.end method.method private pM()V.registers 1return-void
.end method

可以看到,编译器为主类Main生成了一个桥接方法-$$Nest$fputpA用于修改pA,以及一个桥接方法-$$Nest$mpM用于调用pM,这些桥接方法均为包内可见的静态方法,因此成员类Main.Member可以直接访问之。

因此,当成员类通过桥接方法访问主类时,必然造成调用栈变长。如果主类的某个私有字段已经创造了一些getter/setter方法,但是Java代码中成员类仍显式操作主类字段时,生成的桥接方法就显得多余重复。为减少不必要的桥接方法,尽量不显式访问主类的私有字段与方法。除了用getter/setter间接操作主类字段,也可以protected代替private避免桥接方法生成,因为在DalvikVM中,protected对子类与成员类均可见,可以实现一种较弱的私有化

匿名类与Lambda

匿名类可以实现闭包,编译期会生成一个.class文件;而Lambda编译期不会生成.class文件,而是直接用invokedynamic指令生成一个Lambda实例。但是DalvikVM不支持invokedynamic指令,在生成dex时,Lambda仍会转为匿名类。因此,在安卓上,Lambda只能算一种语法糖

在Lambda作为语法糖的情况下,此处仅讨论匿名类。考虑闭包,匿名类属于非静态成员类,在编译器生成的构造函数中会传入主类的this引用,因此才可以在匿名类中通过Main.this实现对主类字段的访问(主类this前缀可省,也是一种语法糖);另外,为了实现对本地变量的闭包,构造函数中也会自动生成引用的局部变量作为形参。如果不希望再匿名类的形参中出现this,建议直接通过有名的静态成员类或者封装一个静态方法来创建匿名类。

大多数情况下,我们只对接口类创建匿名类,而接口是可以被多实现的,因此可以通过单个类来实现多个接口来减少.class的数量,甚至直接用主类实现。对比以下代码:

interface IA { public void a(); }
interface IB { public void b(); }
class A implements IA, IB {void callThis() {callbackA(this);callbackB(this);}void callLambda() {callbackA(this::a); // Lambda实际转为匿名类,下同callbackB(this::b);}void callbackA(IA ia) {...}void callbackB(IB ib) {...}public void a() {...}public void b() {...}
}

其中callThiscallLambda生成的Dalvik字节码如下:

.method callThis()V.registers 1invoke-virtual {p0, p0}, LA;->callbackA(LIA;)V                      # this.callbackA(this);invoke-virtual {p0, p0}, LA;->callbackB(LIB;)V                      # this.callbackB(this);return-void
.end method.method callLambda()V.registers 2new-instance v0, LA$$ExternalSyntheticLambda0;invoke-direct {v0, p0}, LA$$ExternalSyntheticLambda0;-><init>(LA;)V # v0 = new A$$ExternalSyntheticLambda0(this);invoke-virtual {p0, v0}, LA;->callbackA(LIA;)V                      # this.callbackA(v0);new-instance v0, LA$$ExternalSyntheticLambda1;invoke-direct {v0, p0}, LA$$ExternalSyntheticLambda1;-><init>(LA;)V # v0 = new A$$ExternalSyntheticLambda1(this);invoke-virtual {p0, v0}, LA;->callbackB(LIB;)V                      # this.callbackB(v0);return-void
.end method

可以看到callThis由于直接传入this对象作为callback方法的参数,因此并没有出现匿名类的情况;反观callLambda,由于使用Lambda进行传参,实际生成了A$$ExternalSyntheticLambda0A$$ExternalSyntheticLambda1两个匿名类,并分别创建了两个实例。相较而言,通过单个类(甚至是主类)实现多个接口,有利于减少匿名接口类的数量以及实例的数量,对减少字节码规模、减少堆内存使用、避免内存泄漏(某些回调接口实例)都是有帮助的

当然,通过单个类/主类实现多个接口也是有缺点的:

  • 使主类携带过多接口信息,影响封装性;
  • 主类中用于闭包的字段需要手动回收;
  • 需要为同一接口创建不同代码的实例时往往只能取其一。

小结

本文从Dalvik字节码的五个角度考虑,总结了相应的编码优化方案。文章仅个人观点,希望给广大读者提供一些参考。


原文链接:https://www.cnblogs.com/RainbowC0/p/19081965,未经作者许可禁止转载

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

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

相关文章

基于Java+SSM+Flask家庭理财系统(源码+LW+调试文档+讲解等)/家庭理财/理财系统/家庭财务/家庭财务规划/家庭账目/家庭财务软件/家庭记账/理财器具/财务多元化/资产管理。

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

.NET Conf China 2025:讲师与主题全揭秘

.NET Conf China 2025:讲师与主题全揭秘备受期待的第七届.NET中国峰会——.NET Conf China 2025,即将于11月30日在上海盛大举行。本次大会聚焦性能跃升、AI融合、跨平台开发三大核心方向,邀请了来自国内外知名科技企…

深入解析:洞穴人的仰望:洞穴人隐喻与进步主义的歧途

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

《JIRA:项目管理与敏捷开发实践》

1、非修改序列算法 这些算法不会改变它们所操作的容器中的元素。 1.1 find 和 find_if find(begin, end, value):查找第一个等于 value 的元素,返回迭代器(未找到返回 end)。 find_if(begin, end, predicate):查找…

20232319 2025-2026-1 《网络与系统攻防技术》实验五实验报告

1.实验内容 任务1:DNS域名信息查询 任务2:获取好友IP地址及地理地址 任务3:使用nmap扫描靶机 任务4:使用Nessus扫描靶机 任务5:网络足迹与Google hacking 2.实验过程 2.1 DNS域名信息查询 2.1.1 查询baidu.com的I…

主动交互和情境感知,AI 硬件是脱离手机屏幕掌控的蓝海机会丨硬件和端侧模型专场@RTE2025 回顾

在本届 RTE2025 大会上,来自产业界和学术界的多位专家深入探讨了 AI 硬件、端侧小模型的发展趋势、架构创新、低功耗解决方案以及语音技术与大模型的深度融合。Rokid 全球创新产品、工程和开放生态负责人赵维奇、Folo…

WSL移植小记

前言给团队小家伙培训到 openmp 部分时顺带给他们配置了 wsl。wsl默认安装在 C 盘,随着 wsl 上安装的工具增多其占用空间也会越来越大,挤占 C 盘空间。为了避免以后 C 盘爆掉的问题,因此写了一个 wsl 迁移笔记供其使…

2025年西北数字人厂商最新TOP5评测:引领陕西甘肃智区域能交互新生态

2025年西北数字人服务厂商TOP5评测:引领智能交互新生态随着虚拟数字人技术在文旅、教育、电商等领域的深度应用,市场对专业化服务厂商的需求持续攀升。本榜单基于技术研发实力、区域服务能力、行业适配广度三大核心维…

centos 环境下部署mongodb并设定密码

1、下载mongodb文件,下载地址为:https://www.mongodb.com/try/download/community 2、下载截图3、创建安装目录mkdir -p /home/software/mongodb4、解压mongo安装文件(此处以截图5.0.9.tgz为例)至 /home/software/…

20232317 2025-2026-1 《网络与系统攻防技术》实验四实验报告

1、实验内容 1.1恶意代码文件类型标识、脱壳与字符串提取 对提供的rada恶意代码样本,进行文件类型识别,脱壳与字符串提取,以获得rada恶意代码的编写作者,具体操作如下: (1)使用文件格式和类型识别工具,给出rada…

谷歌起诉网络犯罪团伙Smishing Triad,揭露大规模钓鱼攻击

谷歌正式起诉总部位于中国的网络犯罪团伙Smishing Triad,该团伙使用"Lighthouse"钓鱼即服务工具包实施大规模短信钓鱼攻击,窃取受害者财务信息,影响全球120个国家数百万信用卡持卡人。谷歌起诉网络犯罪团…

PLC与单片机区

PLC与单片机区 PLC是模块化解决问题,不需要额外认证https://gitee.com/powes/,作者:前沿风暴,转载请注明原文链接:https://www.cnblogs.com/Kreos/p/19219748

2025.11.13总结

对心理咨询的新提出的需求进行分析拆解流程 1. 用户注册与孩子信息管理功能流程:用户完成APP基础注册后,系统强制跳转至“孩子信息管理”页面。 核心逻辑:强制填写:用户必须至少填写一位孩子的完整信息(字段完全遵…

.NET+AI | MEAI | .NET 平台的 AI 底座 (1)

.NET 平台 AI 生态发展时间线从提供AI服务统一抽象接口的Microsoft.Extensions.AI,到实现复杂AI工作流编排的Semantic Kernel (SK),再到整合多方优势、专注于智能体(Agent)协作的Microsoft Agent Framework (MAF),…

NOIP2025模拟7

前言: 我菜菜菜菜菜菜,所以只改了两道题。T2:原子(atom) 思路: 建图图图图图。 根据题意我们可以建出来一个完全图,然后求出图中最少有几条链就行。 我们发现,链的数量其实就是每个点的(出度减去入度)的加和…

20232304 2025-2026-1 《网络与系统攻防技术》实验六实验报告

20232304 2025-2026-1 《网络与系统攻防技术》实验六实验报告 1.实验内容 1.对给定Metasploitable2靶机进行发现,以及端口和漏洞扫描; 2.通过下面四个漏洞的渗透攻击从而掌握Metasploit的用法:Vsftpd源码包后门漏洞…

AT_arc104_e Random LIS

啦啦啦睡前小练习,5min 秒了呀。 先 \(O(n^n)\) 枚举 \(n\) 个数的相对大小关系,但是极为不满,\(6\) 个数的实际有效状态只有 \(4683\) 个。 现在只用计数一个上升序列,满足 \(a_i\le b_i,a_{i+1}>a_{i}\)。先将…

kettle从入门到精通 第五十四课 ETL之kettle接收http请求

1、kettle官网没有介绍kettle可以处理http请求,只有讲解kettle如何发起请求。但是kettle处理http请求需求又是实实在在的需求,所以经过一些时间的摸索,终于解开谜题。 2、kettle转换处理http请求,如下图所示:1)通…

P13714 淘汰(Hard ver.)

思路:考虑DP,对于每一位存在关键操作,操作后该位不再变化,之前该位状态无关紧要。设$f_S$表示集合$S$的位未固定,不在$S$的位已固定且与$y$相同的最小花费。预处理$g_S$,即集合$S$上与$y$相同的AND操作的最小费用…

Windows 10 本地部署工作流自动化工具 n8n

参考豆包 deepseek https://docs.n8n.io/ https://zhuanlan.zhihu.com/p/1968808989946016165 https://blog.csdn.net/qq_43499921/article/details/154605297 https://www.n8nclub.com.cn/article/docker_n8n#1ef34a8…