Android开机速度



我们知道在L及以后的版本中,当系统升级后,或者恢复出厂设置后,或者第一次开机,其启动时间往往很长,短的有3-4分钟,而长的多达10分钟,那么是原因导致的,我们将分析其原因.

ART 的前世今生

我们知道,在Android的最初的几个版本,Android的app 运行在Dalvik中,完全是解释性执行.在Android2.2中,Google把JVM中常用的技术JIT带进了Dalvik,并且增加了多线程的处理能力.随着岁月的流失,科技的进步,屌丝用户期望的提高,手机厂商拼分数,拼情怀,拼硬件,跟踪赵氏族人的style.终于Android 手机的HW,从singlecore 到了8cores,10 cores. RAM 也从256M,512M, 1G, 2G, 3G 到6G.内部存储也从256M到了128G,256G.Google也顺应潮流,终于在L 中,正式推出ART.

下面是ART 的架构图.


首先ART兼容Dalvik.也就是说ART 能运行”dex”(Dalvik执行文件).因此对Androidapp的开发者来说,他们没有什么区别.两者最大的区别是:ART把JIT(Just-in-Time)变成了AOT(Ahead-of-Time).JIT需要在每次运行app时都需要执行一遍,而AOT 只需要执行一次,而后续再运行此app是不需要再执行,其明显提高了性能.当然ART 这样做,也是有代价的,那就是以空间换时间.ART能对应用的所有code做优化,其把bitcode 编译为ELF文件.而ELF文件也往往比odex文件大很多.而JIT 只能对local/method做优化.ART的另一个缺点是其第一次执行优化时需要更长的时间.这也是导致第一次开机时间过长的原因.


此外在ART中,对GC 也有了很大的提高.我们知道在Dalvik中,GC首先需要”stopall the world”.然后再执行GC.其后果就是影响app的性能.其表现为Janks或者启动时间长,卡顿等.相比较Dalvik的GC,ART 做了如下的提高:

  • 在ART 中有几种不同的GC 类型,分别是MS (Mark-Sweep),CMS(Concurrent Mark-Sweep),SS(Semi-space GC), GSS(Generational Semi-space ), MC(Mark Compact ),其default 是CMS.
  • 在ART 中,有三种GC plan,分别是Sticky GC: 只free 从上次GC 以来新分配的object,这种GC plan 是最常用的,也是最经济的; Partial GC:只free 除zyogte外分配的空间;Full GC,释放APP所有分配的Java空间.其执行时间为sticky GC < partial GC < full GC.ART 会根据heap的memory使用情况,选择GC plan.
  • 在ART的GC 中,只有一次pause 在remark 阶段.而Dalvik中有两次pause, 一次是Root Mark阶段, 另一次是remark 阶段.如下图所示.


  • ART 对前台应用和后台应用采用不同的GC 算法来来达到性能和memory 的平衡.对于前台应用采用CMS算法, CMS算法不会合并空间, 其好处是反应快,给用户更好的performance体验.但是其可能会导致heap的碎片较多.而对应后台引用采用SS, GSS 和MC 算法, 这些算法可以合并压缩memory空间, 有效的减少heap的碎片并节省memory.

预先优化

对于ART我们现在有了如下认知,

  • ART 用AOT(ahead-of-time)进行优化.即在install app的时候,把dex 文件编译为native code 并生成OAT file(依然是odex,但其格式是ELF).
  • 和Dalvik相比,其Performance有了较大的提升并且更节省功耗.
  • 在ART 中,没有code cache(Dalvik 有JIT cache).OAT 文件直接map到memory里.
  • ART 在zygote 启动的时候, 会把初始化的class, object (boot.art) map 到memory 中去(大概10M 左右).在内存紧张的时候, 系统可以把一些proloaded classes swap out. 这样不但加快了zygote的启动速度,又提升了系统性能.
  • ART 用dex2oat 工具来进行Dex 文件的本地编译.其编译时间比dexopt要长.
  • OAT 文件比dexopt 生成的odex 文件要大.

归纳起来就是ART 比Dilvik 性能更好,其把dex文件优化为为本地代码,但是其体积大,编译时间长,运行时的性能好.

由于dex优化为OAT文件的时间比较长,所以第一次开机时间(包括恢复工厂设置/OTA升级后)可能就会比较长如果我们没有预先优化dex文件.所以我们可以用预先优化来规避这个问题, 但是其副作用就是其System image 比较大.在很多屌丝机上, 其内部存储比较小, 这就需要一个平衡.要么忍受第一次的痛苦 (开机时间长), 要么加大投入增大内部存储.

下面我们来看看相关的预先优化选项对ART优化后的OAT文件的大小和系统性能的影响.

· WITH_DEXPREOPT

我们可以在BoardConfig.mk里定义

WITH_DEXPREOPT := true

这样整个system image 就会被预先优化. 由于在启动时不再需要进行app的dex文件进行优化(dex2oat操作)从而提升其启动速度.

· DONT_DEXPREOPT_PREBUILTS

如果我们不想把prebuilts目录中的第三方应用进行预先优化(这些应用在他们的Android.mk文件中有include$(BUILD_PREBUILT) ).而是希望这些app通过playstore 或者app提供商进行升级.我们需要在BoardConfig.mk里进行如下的定义.

WITH_DEXPREOPT :=true
DONT_DEXPREOPT_PREBUILTS := true

· WITH_DEXPREOPT_BOOT_IMG_ONLY

这个选项只是把boot image 进行预先优化.简单来说,其主要生成boot.art 和boot.oat.这能显著的减少systemimage 大小.但是,所有的app 都需要在第一次启动的时候进行优化需要花更长的boottime 时间.

我们需要在BoardConfig.mk里进行如下的定义
WITH_DEXPREOPT :=true
WITH_DEXPREOPT_BOOT_IMG_ONLY := true

· LOCAL_DEX_PREOPT

我们也可以控制单个APP是否需要预先优化.如果我们的APP是通过Google play 来进行升级的,或者为了平衡空间问题,我们可以在app的Android.mk中设置如下属性.

LOCAL_DEX_PREOPT :=false // 不进行预先优化
LOCAL_DEX_PREOPT :=true // 进行预先优化

· WITH_DEXPREOPT_PIC

我们知道ART 在system 有一份OAT file ,在运行时也会copy 一份到/data/ dalvik-cache下.如果我们内部存储不够,可以enable这个选项.但是这个选项可能会影响运行时的性能.因为ART 会disable和position相关的优化.

在device.mk 中添加,

WITH_DEXPREOPT :=true
WITH_DEXPREOPT_PIC :=true

· WITH_ART_SMALL_MODE

如果手机没有足够的空间,我们可以enable 这个宏.ART只会预先优化boot class.第一次启动的时间会大大的提升,但是也会大大影响运行时性能.因为其它的appcode 是解释性执行的.

在device.mk中添加,

WITH_ART_SMALL_MODE :=true

在Android 后面的版本中也许会修改成 device.mk:

PRODUCT_PROPERTY_OVERRIDES +=\
     dalvik.vm.dex2oat-filter=interpret-only \
     dalvik.vm.image-dex2oat-filter=speed

其它

· Preloaded Classes List

所谓preloaded classes list就是zygote在启动是需要初始化的哪些类.这将会使所有的APPshare zygote启动时初始化的类.从而APP 不需单独去初始化这些类,进而提高了效率.proloaded classes 在frameworks/base/preloaded-classes中定义. 如果我们有自己SDK并且这些类是很多应用都会用到的,我们可以把这些常用的类添加到这个list中.注意如果添加过多无用的类会浪费memory,而没有把APP都会用到的基础类添加进来,也会让每一个app都有一份基础类copy,同样浪费空间.

··Image Classes List

Image classes 是dex2oat初始化时的预先优化的类保存在boot.art文件中. 这个文件在系统启动时map到memory 中去,这部分memory在memory紧张的情况下可以被swapout. Zygote启动时,可以直接从memory中load这部分image.在 L 中,这部分classes list 和preloaded classes list 相同. 在M 中,可以定制需要添加哪些classess.方法如下,

在device.mk中添加,

PRODUCT_DEX_PREOPT_BOOT_FLAGS += --image-classes=<filename>

··Compiled Classes List

在L的后续版本,如M 中,可以用compiledclasses list 来预先优化boot classpath 中的某些classes.这对哪些内部存储有限的手机非常有用.dex2oat在做优化时,首先会去check所优化的类是否在$OUT/system/etc/compiled-classeslist 中,如果没有在这个列表中的classes则不会进行优化.只能解释性执行,这样虽然可以节省空间,但是势必影响运行时的性能.

device.mk

PRODUCT_COPY_FILES += <filename>:system/etc/compiled-classes

所以如果我们想我们的第一次开机时间比较快,那么请用预先优化吧,在BoardConfig.mk里添加下面这行就可.

WITH_DEXPREOPT := true