加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 业界 > 正文

9102年了,还不知道Android为什么卡?

发布时间:2019-08-15 06:32:16 所属栏目:业界 来源:吃猫猫的鱼
导读:导读 最近华为方舟编译器要开源了,笔者去看了下发布会PPT,发现作为一名Android开发者,PPT中所介绍的知识点我居然不能完全看懂?于是乎恶补了下PPT中的内容,整理成本文。 本文将用通俗的语言从底层介绍Android卡顿的历史原因和谷歌与之斗争的过程 阅读完

DVM是Google开发的Android平台虚拟机,可读取.dex的字节码。上文中所说的从字节码解释成机器码的过程在Java虚拟机中,在Android平台中虚拟机指的就是这个DVM。在Android1.0时期,程序一边运行,DVM中的解释器(翻译机)一边解释字节码。可想而知,这样效率绝对低下。一个字,卡。

② Android 2.2 DVM+JIT

其实解决DVM的问题思路很清楚,我们在程序某个功能运行前就解释就可以了。

在Android2.2时期,聪明的谷歌引入了JIT(Just In Time)机制,直译就是即时编译。

举个例子,我经常去一家餐馆吃饭,老板已经知道我想吃什么菜了,在我到之前就把菜准备好了,这样我就省去了等菜的时间。

JIT就相当于这个聪明的老板,它会在手机打开APP时,将用户经常使用的功能记下来。当用户打开APP的时候立马将这些内容编译出来,这样当用户打开这些内容时,JIT已经将'菜'准备好了。这样就提高了整体效率。

虽然JIT挺聪明的,且总体思路清晰理想丰满,但现实是仍然卡的要死。

存在的问题:

打开APP的时候会变慢

每次打开APP都要重复劳动,不能一劳永逸。

如果我突然点了一盘之前从来没点过的菜,那我只好等菜了,所以如果用户打开了JIT没有准备好的'菜',就只能等DVM中的解释器去边执行边解释了。

③ Android 5.0 ART+AOT

聪明的谷歌又想到个方法,既然我们能在打开APP的时候将字节码编译成机器码,那么我们何不在APP安装的时候就把字节码编译成机器码呢?这样每次打开APP也不用重复劳动了,一劳永逸。

这确实是个思路,于是谷歌推出了ART来替代DVM,ART全称Android Runtime,它在DVM的基础上做了一些优化,它在应用被安装的时候就将应用编译成机器码,这个过程称为AOT(Ahead-Of-Time),即预编译。

但是问题又来了,打开APP是不卡了,但是安装APP慢的要死,可能有人会说,一个APP又不是会频繁安装,可以牺牲下这点时间。但是不好意思,安卓手机每次OTA启动(即系统版本更新或刷机后)都会重新安装所有APP,无奈吧!绝望吧!对,还记得那两年,被安卓版本更新所支配的恐惧吗!

④ Android 7.0 混合编译

谷歌最终祭出了终极大招,DVM+JIT不好,ART+AOT又不好。行,我把他们都混合起来,那总可以了吧!

于是谷歌在Android7.0的时候,发布了混合编译。即安装时先不编译成机器码,在手机不被使用的时候,AOT偷偷的把能编译成机器码的那部分代码编译了(至于什么是能编译的部分,下文字节码的编译模板详述)。其实就是把之前APP安装时候干的活偷偷的在手机空的时候干了。

如果来不及编译的话,再把JIT和解释器这对难兄难弟叫起来,让他们去编译或实时解释。

不得不佩服谷歌这粗暴的解决问题的方式,这样一来确实Android手机从万年卡顿慢慢的坑中出来了。

⑤ Android 8.0 改进解释器

在Android8.0时期,谷歌又盯上了解释器,其实纵观上面的问题,根源就是这个解释器解释的太慢了!(什么JIT,AOT,老夫解释只有一个字,快)那我们何不让这个解释器解释的快一点呢?于是谷歌改进了解释器,解释模式执行效率大大提升。

⑥ Android 9.0 改进编译模板

这个点会在下文字节码的编译模板中详述。

这边简单而言就是,在Android9.0上提供了预先放置热点代码的方式,应用在安装的时候就能知道常用代码会被提前编译。(借用知乎@weishu大神的原话)

2.JNI——Java和C互相调用慢

JNI又称为 Java Native Interface,翻译过来就是Java原生接口,就是用来跟C/C++代码交互的。

如果不做Android开发的可能不知道,Android项目里的代码除了Java,很有可能还有部分C语言的代码。

这个时候有个严重的问题,首先上图 (图片参考方舟编译器原理PPT):

9102年了,还不知道Android为什么卡?

在开发阶段Java源代码在开发阶段打包成.dex文件,C语言直接就是.so库,因为C语言本身就是编译语言。

在用户手机中,APK中的.dex文件(字节码)会被解释为.oat文件(机器码)运行在ART虚拟机中,.so库则为计算机可以直接运行的二进制代码(机器码),两份机器码要互相调用肯定是有开销的。

下面就来阐述下为什么两份机器码会不同。

这边需要深入理解字节码->机器码的编译过程,在图上虽然都被编译成了机器码,都能被硬件直接调用,但是两份机器码的性能,效率,实现方式相差甚多,这主要是由以下两个点造成的:

  • 编程语言不同导致编译出的字节码不同导致编译出的机器码不同。
  • 举个例子,针对同样是静态语言的C和Java,对int a + b 的运算
  • C语言可以直接加载内存,在寄存器中计算,这是由于C语言是静态语言,a和b是确定的int对象。

在Java中虽然定义对象我们也要明确的指出对象的类型,例如int a = 0,但是Java拥有动态性,Java拥有反射,代理,谁也不敢保证a在被调用时还是int类型,所以Java的编译需要考虑上下文关系,即具体情况具体编译。

所以连字节码已经不同了,编译出的机器码肯定不同。

运行环境不同导致编译出的机器码不同

图中明显看到由Java编译而来的机器码包裹在ART中,ART全称Android RunTime,即安卓运行环境,跟虚拟机差不多是一个意思。而C语言所在的运行环境不在ART中。

RunTime提供了基本的输入输出或是内存管理等支持,如果要在两个不同的RunTime中互相调用,则必然有额外开销。

举个例子,由于Java有GC(垃圾回收机制),在Java中的一个对象地址不是固定的,有可能被GC挪动了。即在ART环境中跑的机器码中的对象的地址不固定。可是C语言哪管那么多幺蛾子,C就直接问Java要一个对象的地址,但万一这个对象地址被挪动了,那就完蛋了。解决方案有两个:

把这个对象在C里再拷一份。很明显这造成了很大的开销。

告诉ART,我要用这个对象了,GC这个对象的地址你不能动!你先一边呆着去。这样相对而言开销倒是小了,但如果这个地址如果一直不能被回收的话,可能造成OOM。

(此处参考知乎@张铎在华为公布的方舟编译器到底对安卓软件生态会有多大影响?中的回答)

3. 字节码的编译模板——未针对具体APP进行优化

我们举个例子来理解编译模版,“Hello world”可以被翻译为“你好,世界”,同样也可以被翻译为“世界,你好”,这个差别就是编译模版不同导致的,

①. 统一的编译模版(vm模版)

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读