ART前世今生

虽然Android应用大多用Java/Kotlin编写,但是实际上Android虚拟机并不使用JVM字节码,而是将Class文件通过DX编译器(现已换成D8)编译为更加紧凑的dex文件,然后由虚拟机执行。

最开始的Android虚拟机是Dalvik,ART虚拟机由Android4.4被引入成为可选项,在Android5.0之后替换掉了Dalvik,并且在Android7.0和8.0分别进行了一系列改动,关于ART虚拟机的说明官方文档有提到,不过并不是那么好理解。

基本概念和名词

  • .dex文件:App所有java源代码编译后生成众多class文件,由DX/D8,编译为一个/多个(multiDex)dex文件,由Android虚拟机编译执行;
  • .odex文件:dex文件经过验证和优化后的产物,art下的odex文件包含经过AOT编译后的代码以及dex的完整内容,但Android8.0之后odex中的dex内容移动到了.vdex文件;
  • .art文件:art下根据配置文件生成odex文件时同时生成.art文件,主要是为了提升运行时加载odex中热点代码的速度,包含了类信息和odex中热点方法的索引,运行App时会首先根据这个文件来加载odex中已经编译过的代码;执行过speed-profile命令的app才会生成.art文件;
  • 解释器(Interpreter):用于程序运行时对代码进行逐行解释,翻译成对应平台的机器码执行
  • JIT编译(Just In Time):由于解释器方式运行太慢引入,对于频繁运行的热点代码(判定标准一般是在某个时间段内执行次数达到某个阈值)进行实时编译(在ART下以方法为粒度)执行,并且缓存JIT编译后的代码在内存中用于下次执行。由于以方法为粒度(ArtMethod)进行编译,JIT编较于解释器可以生成效率更高的代码,运行更快。
  • AOT编译(Ahead-Of-Time):应用安装时全量编译所有代码为本地机器码,运行时直接执行机器码。

ART 如何运作

4.4~7.0

最开始ART只采用AOT编译,在App安装时就编译所有代码存储在本地,打开App直接运行,这样做的优点是应用运行速度变快,缺点也很明显,App安装时间明显变长,而且占用存储空间较大。

7.0

Android N之后对于ART进行改动,重新引入了JIT编译,结合使用AOT/JIT混合编译,主要机制如下:

  1. 安装时不进行任何编译,前几次运行仅通过解释器解释运行,同时对热点代码进行JIT编译,并将这些代码的相关信息记录在一个配置文件里(data/misc/profile/cur/0/...)
  2. 设备处于空闲和充电状态时,编译守护进程读取配置文件对热点代码进行AOT编译并写入到app对应的odex文件中
  3. 再次启动应用后优先使用AOT编译过的代码,否则使用解释器+JIT编译,重复这个过程

对于一些庞大的APP,比如淘宝,有些功能可能你一辈子都不会用到,根据上述策略这部分代码就不会被编译保存,从而减少了存储空间的占用。另外,在系统升级时也避免了全量编译所有现存应用造成的时间空间消耗。

8.0

Android 8.0引入了.vdex文件,它里面包含 APK 的未压缩 DEX 代码,以及一些用于加快验证速度的元数据。怎么理解呢?这里我们需要补充一下对dex文件的编译配置和系统ROM中各类应用的默认编译方式:

编译选项(compiler filters):

  • verify:只对 DEX 文件进行代码验证,校验各部分合法性。
  • quicken:在verify的基础上优化一些 DEX 指令,提升解释器性能。
  • speed:在verify的基础对所有方法进行 AOT 编译。
  • speed-profile:在verify的基础对配置文件中列出的方法(热点方法)进行 AOT 编译。

系统ROM中各类应用默认编译方式:

  • 启动类路径代码(用于启动系统的部分代码):默认使用 speed 编译过滤器进行编译
  • 系统服务器代码(比如电量、多媒体服务代码):默认使用 speed 编译过滤器进行编译
  • 产品专属的核心应用(比如goole服务框架):默认使用 speed 编译过滤器进行编译
  • 所有第三方应用:默认使用 quicken 编译过滤器进行编译

所有第三方应用最开始都是通过quiken模式只进行了dex校验和一些指令优化,.vdex文件存放的就是经过校验后的dex代码,以便在对热点代码进行AOT编译时避免重复验证,加快速度。

总结

扯了这么多淡实际上核心内容就一句话:App安装时不编译代码只校验合法性,运行时通过解释器执行,将运行频繁的代码进行编译放到内存缓存并且记录在本地配置文件,后台线程编译配置文件记录的方法存放到.odex文件,再次运行App时优先读.odex文件中编译后的代码,然后重复这个过程。

转载自链接:https://www.jianshu.com/p/eaf7681c5bfc

单个apk编译优化:

adb shell cmd package compile -m speed-profile/speed/everything/quicken  -f  "packagename"

全量apk编译优化:

adb shell cmd package compile -m speed-profile/speed/everything/quicken  -f  -a

删除编译优化:

adb shell cmd package compile --reset  packagename

模拟充电后台dexopt操作:

adb shell pm compile -a -r bg-dexopt cmd package bg-dexopt-job

adb shell "pm list packages | cut -d ':' -f2 | xargs -n1 -r -t cmd package bg-dexopt-job"

adb shell pm bg-dexopt-job com.hh.hwid 

adb shell "dumpsys jobscheduler" > job2.txt

dumpsys jobscheduler|grep dexopt

dumpsys jobscheduler|grep com.android.server.pm.BackgroundDexOptService -A20