dex2oat编译模式、触发场景、命令强制执行

       dex2oat简单理解就是把delvik虚拟机的可执行文件dex转化成AndroidRuntime虚拟机的可执行文件oat。

        Android  T版本由PKMS下发命令、native层进程installd负责具体执行dex2oat操作。installd回去调用dex2oat64完成编译工作,可以将dex2oat64理解成一个程序。源码路径:/apex/com.android.art/bin/dex2oat6

一、dex2oat的几种常见的编译模式

ab-ota    ab升级时进行OTA prepare编译
speed-profile    bg-dexopt    手机进入idle状态(充电灭屏半小时以上)编译
verify    boot    开机过程中编译
speed    core-app    开机过程或者充电时候的核心应用
quicken    first-boot    首次开机过程中编译(刷机、OTA升级)
inactive    verify    idle时对10天内未使用过的应用转入此状态
install    speed-profile    安装时编译
shared    speed    通过package usage得到,被其他应用加载的dex
unknown    run-from-apk    应用首次启动后编译,一般需要一定时间


first-boot: 首次开机或恢复出厂设置后的第一次开机,对应优化方式:speed-profile
boot:大部分时候是OTA升级后开机,可能在开机动画阶段,也可能是进入桌面后后台执行,对应优化方式:verify
core-app: 手软新增场景,针对TOP100应用做bg-dexopt优化时,采用此方式,对应的优化方式:speed
install:应用安装场景,对应优化方式:speed-profile
bg-dexopt: 系统记录热代码(包括apk和插件中的dex)后,在息屏idle状态下执行优化,对应优化方式:speed-profile

ab-ota:若系统采用A/B分区,在准备升级过程中,提前对应用做dexopt优化,对应优化方式:speed-profile

inactive:空间不足时,取消部分应用已经做的dexopt优化,重新做低级别的优化,对应优化方式:verify

shared:若当前优化应用执行方式为speed-profile且此应用有被其他应用加载的情况,则升级优化方式为speed

verify:只运行 DEX 代码验证。
quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解释器性能。
speed-profile:运行 DEX 代码验证,并对配置文件中列出的方法进行 AOT 编译。
speed:运行 DEX 代码验证,并对所有方法进行 AOT 编译。

speed和speed-profile区别

speed和speed-profile是Android 编译模式中的两种选项。speed是一种编译模式,旨在最大化运行时性能,而speed-profile则是一种部分编译模式,根据profile记录的热点函数来编译,也是为了最大化运行时性能。

speed-profile是基于profile的quicken,即profile-guilde部分是speed,其余是quicken。但是安装的时候没啥profile,约等于quicken。profile是靠后续收集的,debug版本1000次,user版本10000次,会记录到每个进程的prof文件里。

每个app的profile文件都位于/data/misc/profiles/cur/userId/包名/primary.prof,profile文件用来记录运行比较频繁的代码,用来进行 profile-guide 编译,使得 dex2oat编译代码更精准。

1、run-from-apk

     dex文件包含于apk文件中,apk是一个压缩包,run-from-apk的意思就是运行APP的时候再从apk中把dex解压出来,运行的时候边运行边把dex解释成AndroidRuntime可以执行的文件。这种模式显然是最慢的。一般不会有这种模式出现,下面三种特殊情况会出现这种模式:

①应用做dex2oat有花花似,为了保证系统稳定性,中断了优化。

②Android R上应用如果7天或者更长时间未使用,升级过程中会被认为是不常用应用,为了节省资源不优化这类应用。升级后这类应用将是run-from-apk状态。

③处在debug模式的应用不会进行dex2oat优化,会保持run-from-apk。

T上的项目将不会出现run-from-apk这种模式了。

2、extract

       这种模式会将apk中的dex提前解压释放出来,这类性能比run-from-apk好一些,毕竟省去了解压apk的过程。S上进行ota升级后,会对升级过程中未选中的应用进行extract(7天或者更长时间未使用)。执行此模式的时候不涉及到编译,因此占用的系统资源相比其他编译模式会少很多。但是也正是由于本身没有任何除了解压之外的预处理,因此运行的时候所有的类加载都要verify,且只能解释执行,速度也比较慢。

3、verity,quicken

      这两种模式相比前面的两种更进一步,verify模式不仅会将dex文件提前释放出来,还会提前做pre verify,生成vdex,从而省去运行的时候verify的步骤;quciken模式编译的时候会把dex文件编译成quciken code,quciken code是没有使用AndroidRuntime虚拟机的时候,系统dexopt的产物。但是这两种都不会编译成ota code。 quicken模式已在S上移除,R上quicken等效于S上的verify。

4、speed

      系统的核心应用或者ShareuserID的应用将会是speed模式。这类模式的时候系统会对限定大小的方法以及class进行oat编译,生成对应的机器码。基本上是能编译的都编译了,可以说饥不择食了,因此十分耗时和耗费CPU资源。此类模式在应用启动时是相对来说很快的模式了。简单来理解就是编译了dex的大部分文件了。

5、everying

      顾名思义就是把dex文件全部一丝不留编译成机械码,比speed还狠。特点是运行最快,编译文件占用存储空间最大。也因为占用系统资源很大,编译文件很大,这种编译模式不存在于正常的应用中,只存在于debug模式的应用中。

6、speed-profile

      这是现在最常见的一种编译模式,它会根据用户的使用习惯,不断的进行再编译(当然再编译是要满足条件的)。它会将用户经常打开的页面中的类和经常使用的方法记录到profile文件中,用profile文件去指导编译,同时对于常用的class和string类型还会保存到对应的art文件中,用以启动加速。当应用进程起来的时候,会同时启动jit线程,而jit会对当前进程中的hot method进行run time编译,同时也会记录保存需要编译的方法和类名,这个就是profile文件。此类模式编译的结果是oat code。 此类模式由于有了profile的引入,因此编译触发比较特殊。是根据profile记录的差异与大小来触发编译。比如当前使用的应用记录了1000个方法,如果继续使用后记录变成了1100个方法,这个时候仍然不会触发编译(默认是20%增加)。如果profile记录的方法数量小于100或者class数量小于50,系统也会认为没有编译的必要。也正是因为引入了profile的编译,同样的系统版本,同样的应用版本,不同人使用的习惯不同,可能应用的odex文件编译大小也不同。理论上随着应用使用越久,profile信息将越完善,也越稳定,应用性能将越来越好。

static constexpr uint32_t kMinNewMethodsPercentChangeForCompilation = 20;

static constexpr uint32_t kMinNewClassesPercentChangeForCompilation = 20;

static constexpr const uint32_t kMinNewMethodsForCompilation = 100;

static constexpr const uint32_t kMinNewClassesForCompilation = 50

dex2oat编译模式、触发场景、命令强制执行_第1张图片

      如果有root权限或者可以拿到应用的profile文件,有两个地方会保存profile,运行时写入的地方是:/data/misc/profiles/cur/0/包名/primary.prof, 编译时使用的是/data/misc/profiles/ref/包名/primary.prof。 采用speed-profile模式编译的应用,在data/dalvik-cache/arm64中一定会产生art文件,可以用这一点去判断编译模式,当然最好用的是用dumpsys命令去查看,使用dumpsys package +应用包名,可以看到应用的所有信息,包括UID、安装时间、权限信息、签名信息、dex2oat的状态。

7、vdex

      为何要搞出个vdex文件?目的是为了避免不必要的验证Dex 文件合法性的过程,首次安装进行dex2oat时,会校验Dex 文件各个section的合法性,生成vdex文件,下次启动应用的时候就可以不用去校验。odex文件来自于vdex,odex+vdex=完整的apk dex信息。我对vdex的理解就是pre verify dex,就是做过verify验证的dex文件。 Android S 之后,谷歌更新了vdex格式,致使vdex复用成为可能。在S 之前,应用安装的时候会生成vdex,启动编译,或者充电编译时,会再生成一次,浪费系统资源。S上谷歌改进了这点,当二次编译的时候,如果是同一个apk,vdex将能够复用。 应用dex2oat成为vdex模式的原因,有且只有odex失效才会是vdex状态(注:odex是从vdex中提取内容组成)。有以下几种场景: 应用已经有odex,此时进行了mainline更新,重启后,由于mainline更新了,odex的依赖也需要更新,此时odex会失效,应用会变成vdex状态。 系统进行ota更新,应用不再常用应用内(七天内使用过),此类应用不会再升级过程中二次优化,升级完成后将是vdex状态。 需要注意。有一种vdex状态是异常的。比如桌面launcher,systemui这类在固件内已经是prebuild编译的应用,这类应用刷机后如果是vdex状态,那么意味着固件编译的时候odex有问题。(odex值dex2oat的结果)

8、总结

      从上面介绍的这几种常见的编译模式中可以看出,谷歌设计这几种编译模式的目的,是让我们基于性能的角度可以自主控制dex2oat的编译模式,其策略是以空间换取时间, 想要应用启动得越快,就需要提前编译更多的方法和类,当然编译产生的文件占用的存储空间也会越来越大。

二、dex2oat常见的触发场景

1、内置应用

内置应用有两种方式可以修改prebuilt出来的模式。

第一种,配置自己的mk文件配置对应的模式,mk文件中加入如下修改LOCAL_DEX_PREOPT_FLAGS += --compiler-filter=speed

第二种方法,在PRODUCT_DEXPREOPT_SPEED_APPS配置文件中添加对应的apk model名字修改完成后,刷机后可以通过adb shell dumpsys package + 包名 查看其中的dex status 与reason确认是否配置成功,其中reason是prebuilt。

2、应用安装

       安装触发场景根据APK来源不同分为三种:

 2.1、 非谷歌商店,非应用商店安装

此类安装后通过dump查看 reason = install status = speed-profile

 2.2、 谷歌商店安装

谷歌商店安装部分应用(谷歌商店内部有名单)安装后 reason=install-dm。此类应用是由于谷歌商店下发了profile文件,在安装过程中使用了profile。因此installd-dm一般是安装完成后就有比较好的性能。相当于已经进行过了二次编译,但也是因为这样,install-dm的安装时间比较长。

reason = install-dm status = speed-profile

3、灭屏充电

      灭屏充电这种触发场景是通过job scheduler完成的,简单来说jobscheduler就是一种系统委托,当手机灭屏充电的时候,达到某一个job的触发条件,job就会自动执行起来。常见的委托条件,充电条件,idle条件,网络等。API介绍:https://developer.android.com/reference/android/app/job/JobInfo.Builder

        这里着重说下idle条件,这里是原生系统定义的,目前R之前的idle状态是灭屏71分钟,R之后的版本为31分钟。也就是当灭屏时间满足半个小时的时候,所有委托idle状态的job就该起来干活了。

如何避免灭屏充电编译:

1、进行一晚上灭屏充电,进行灭屏充电编译。

2、通过命令禁止,上层重启后起作用。整机重启后失效。

adb shell setprop pm.dexopt.disable_bg_dexopt true adb shell am restart

3、停止job,每次开机设置一下,也是整机重启就失效。

adb shell cmd jobscheduler cancel android 800

adb shell cmd jobscheduler cancel android 801

       当灭屏充电编译发起的时候,必然有BackgroundDexOptService 的日志打印,其中可以看到当前编译的状态(R上日志可能要少一些)。

充电编译开始:

      BackgroundDexOptService:Performing idle optimizations

充电类型:           

      BackgroundDexOptService: schargeType is x 其中 x 如果是1 ,则是vooc充电 ,3或者4是pdqc充电,5是无线充电 充电电流设置: charging feature is on 是记录的上层设置的充电电流

充电编译中断:

BackgroundDexOptService: abort by thermal type

dex2oat编译模式、触发场景、命令强制执行_第2张图片

dex2oat编译模式、触发场景、命令强制执行_第3张图片

4、升级

    升级过程会根据升级之前应用的使用记录进行选择编译,当前系统定义的是7天内未使用过的应用为不常用应用,这类应用将不在升级过程中进行升级编译。而对于升级编译的应用,为了平衡性能与升级时间,R上默认将采用quicken模式 S上默认是verify模式。

   reason = boot status = verify

5、升级开机完成后

       升级过程中会筛选出近期用过的应用进行优化,而针对那些未被选中的,原生则是放到了开机完成后再进行优化,这也是考虑到全部都在开机过程中做,时间太长。 在R 上这一过程的应用是采取了speed-verify模式编译,与安装场景的级别相当,因此会占用较多的系统资源,该过程会在开机完成一分钟后进行。

      在S上这一过程的应用是采取了extract模式,不涉及到编译。且谷歌已调整为开机十分钟后进行,相比之前的speed-verify,该模式不涉及到编译,只是单纯的解压,当前S 保留了该功能。 reason = boot status = extract

 R上默认关闭,S上可以通过以下命令临时关闭。

adb shell cmd jobscheduler cancel android 801

     在Android T上谷歌将其推迟到了idle状态执行,且灭屏充电编译时该优化必须执行完成,才能进行接下来的编译。谷歌计划在Android U上彻底删除该触发类型的编译。

三、如何在systrace中查看dex2oat编译类型

以高通骁龙原生相机为例,先在trace中找到对应的进程。

Trace中一般会有OpenDexFilesFromOat,点击后面的location可以查看到应用的编译模式和触发原因。

dex2oat编译模式、触发场景、命令强制执行_第4张图片

四执行dex2oat时打印的log

BackgroundDexOptService

installd

dex20at64

dex2oat编译模式、触发场景、命令强制执行_第5张图片

dex2oat编译模式、触发场景、命令强制执行_第6张图片

四、使用命令强制执行dex2oat

 一、强制编译命令

如下:adb shell cmd package compile

1、基于profile文件强制编译某个应用:

adb shell cmd package compile -m speed-profile -f +package_name

2、强制全面speed编译某个应用:

adb shell cmd package compile -m speed -f +package_name

3、基于profile强制编译所有带profile文件的应用:

adb shell cmd package compile -m speed-profile -f -a

4、强制全面speed编译所有应用:

adb shell cmd package compile -m speed -f -a

二、清除配置文件数据并移除经过编译的代码

请运行以下命令:

1、针对某个应用:

adb shell cmd package compile --reset +package_name

2、针对所有应用:

adb shell cmd package compile --reset -a

你可能感兴趣的:(AndrPerformance,android)