• Android:ART 优化配置(Mstar-6A648)


     1.Android预优化的原理

     先来回顾一下Android的发展史,在2014年的Google I/O大会上,Google隆重的发布了Android 4.4操作系统,其中有一个环节着重介绍了ART(Android runtime),也就是虚拟机,也就是运行APP的环境,也就是运行Java代码的虚拟机。虽然Android 4.4版本默认仍然用DVM(Dalvik VM),但到Android 5.0时ART就变成了默认模式,自此,DVM时代一去不再复返。
     DVM和ART和Android的预优化有什么关系?先了解Java的几种虚拟机基本的工作机制:

      (1)JVM:JVM虚拟机运行的是java字节码。Java文件到JVM的过程是:java -> java bytecode(class) -> java bytecode(jar)。
      (2)DVM:DVM虚拟机解析执行的dex字节码。Java文件到DVM的过程是:java -> java bytecode(class) -> dalvik bytecode(dex)。
      (3)ART:ART虚拟机执行本地机器码。Java文件到ART的过程是:java -> java bytecode(class) -> dalvik bytecode(dex) -> optimized android runtime machine code(oat)。
     在Android 4.4(包括)之前用的是DVM,Android 4.4之后用的是ART,从java文件到虚拟机执行代码,ART比DVM多了oat的过程,ART所使用的AOT(Ahead-Of-Time)编译,在应用首次安装时,字节码预编译成机器码存在本地,而DVM是典型的JIT(Just-In-Time),在此模式下,应用每次运行的时候,字节码都需要即时编译器转换为机器码再执行,ART模式相对于DVM省去了解析字节码的过程,占用内存也相应减少,进而提高APP的运行效率,所以Andorid 5.0发布时,Google号称Android系统可以跑在512 M的内存的机器上,ART发挥了举足轻重的作用。

     

    2. ART 的运作方式

      ART 使用预先 (AOT) 编译,并且从 Android 7.0(代号 Nougat,简称 N)开始结合使用 AOT、即时 (JIT) 编译和配置文件引导型编译。所有这些编译模式的组合均可配置,我们将在本部分中对此进行介绍。例如,Pixel 设备配置了以下编译流程:

      最初安装应用时不进行任何 AOT 编译。应用前几次运行时,系统会对其进行解译,并对经常执行的方法进行 JIT 编译。
      当设备闲置和充电时,编译守护进程会运行,以便根据在应用前几次运行期间生成的配置文件对常用代码进行 AOT 编译。
      下一次重新启动应用时将会使用配置文件引导型代码,并避免在运行时对已经过编译的方法进行 JIT 编译。在应用后续运行期间进行了 JIT 编译的方法将会被添加到配置文件中,然后编译守护进程将会对这些方法进行 AOT 编译。
      ART 包括一个编译器(dex2oat 工具)和一个为启动 Zygote 而加载的运行时 (libart.so)。dex2oat 工具接受一个 APK 文件,并生成一个或多个编译工件文件,然后运行时将会加载这些文件。文件的个数、扩展名和名称会因版本而异,但在 Android O 版本中,将会生成以下文件:

        .vdex:其中包含 APK 的未压缩 DEX 代码,另外还有一些旨在加快验证速度的元数据。
        .odex:其中包含 APK 中已经过 AOT 编译的方法代码。
        .art (optional):其中包含 APK 中列出的某些字符串和类的 ART 内部表示,用于加快应用启动速度。

      详细参考:https://source.android.google.cn/devices/tech/dalvik/configure?hl=zh-cn

    3.Android 优化的过程
      Zygote进程在启动的过程中,创建ART虚拟机。在ART中,打包在APK里面的Dex字节码是通过LLVM翻译成本地机器指令的。LLVM是一个用来快速开发自己的编译器的框架系统,在Dalvik运行时中,APK在安装的时候,安装服务PackageManagerService会通过守护进程installd调用一个工具 dexopt对打包在APK里面包含有Dex字节码的classes.dex进行优化,优化得到的文件保存在/data/dalvik-cache目录 中,并且以.odex为后缀名,表示这是一个优化过的Dex文件。在ART运行时中,APK在安装的时候,同样安装服务 PackageManager Service会通过守护进程installd调用另外一个工具dex2oat对打包在APK里面包含有Dex字节码进翻译。翻译后得到的是一个ELF格式的oat文件,这个oat文件同样是以.odex后缀结束,并且也是保存在/data/dalvik-cache目录中。

      Android在首次启动和首次安装应用时,需要将字节码翻译成机器码,这样Android系统的启动速度将会大大减慢,如果没有预优化,APP的运行速度也会加上翻译所需要的时间。所以,这个翻译的工作需要转移到编译上面来,也就是所,在编译APK文件时,将会预先对APK进行翻译的优化,然后再打包到系统里面去,这样Android系统在首次启动时,就不再需要花费大量的时间去翻译APK的字节码。

    4. Mstar 648 平台配置ART优化

    (1)device/mstar/almond/BoardConfig.mk 中配置:

    WITH_DEXPREOPT := true

      看一下 WITH_DEXPREOPT 控制流程:build/core/dex_preopt_odex_install.mk

    # Setting LOCAL_DEX_PREOPT based on WITH_DEXPREOPT, LOCAL_DEX_PREOPT, etc
    LOCAL_DEX_PREOPT := $(strip $(LOCAL_DEX_PREOPT))
    ifneq (true,$(WITH_DEXPREOPT))
      LOCAL_DEX_PREOPT :=
    else # WITH_DEXPREOPT=true
      ifeq (,$(TARGET_BUILD_APPS)) # TARGET_BUILD_APPS empty
        ifndef LOCAL_DEX_PREOPT # LOCAL_DEX_PREOPT undefined
          ifneq ($(filter $(TARGET_OUT)/%,$(my_module_path)),) # Installed to system.img.
            ifeq (,$(LOCAL_APK_LIBRARIES)) # LOCAL_APK_LIBRARIES empty
              # If we have product-specific config for this module?
              ifeq (disable,$(DEXPREOPT.$(TARGET_PRODUCT).$(LOCAL_MODULE).CONFIG))
                LOCAL_DEX_PREOPT := false
              else
                LOCAL_DEX_PREOPT := $(DEX_PREOPT_DEFAULT)
              endif
            else # LOCAL_APK_LIBRARIES not empty
              LOCAL_DEX_PREOPT := nostripping   
            endif # LOCAL_APK_LIBRARIES not empty
          endif # Installed to system.img.
        endif # LOCAL_DEX_PREOPT undefined
      endif # TARGET_BUILD_APPS empty
    endif # WITH_DEXPREOPT=true

      以上逻辑过滤各种条件,会执行LOCAL_DEX_PREOPT := $(DEX_PREOPT_DEFAULT)语句。

      其中 LOCAL_DEX_PREOPT 的赋值 :nostripping - 优化但不剥离apk中的classes.dex       true - 优化并剥离APK中的classes.dex      false - 不进行优化。

    (2)DEX_PREOPT_DEFAULT的定义在 device/mstar/almond/BoardConfigCommon.mk 中:

    DEX_PREOPT_DEFAULT := true

        LOCAL_DEX_PREOPT为true时将会按照build/core/dex_preopt.mk文件相应的规则优化APK文件。

    ####################################
    # dexpreopt support - typically used on user builds to run dexopt (for Dalvik) or dex2oat (for ART) ahead of time
    #
    ####################################
    
    # list of boot classpath jars for dexpreopt
    DEXPREOPT_BOOT_JARS := $(subst $(space),:,$(PRODUCT_BOOT_JARS))
    DEXPREOPT_BOOT_JARS_MODULES := $(PRODUCT_BOOT_JARS)
    PRODUCT_BOOTCLASSPATH := $(subst $(space),:,$(foreach m,$(DEXPREOPT_BOOT_JARS_MODULES),/system/framework/$(m).jar))
    
    PRODUCT_SYSTEM_SERVER_CLASSPATH := $(subst $(space),:,$(foreach m,$(PRODUCT_SYSTEM_SERVER_JARS),/system/framework/$(m).jar))
    
    DEXPREOPT_BUILD_DIR := $(OUT_DIR)
    DEXPREOPT_PRODUCT_DIR_FULL_PATH := $(PRODUCT_OUT)/dex_bootjars
    DEXPREOPT_PRODUCT_DIR := $(patsubst $(DEXPREOPT_BUILD_DIR)/%,%,$(DEXPREOPT_PRODUCT_DIR_FULL_PATH))
    DEXPREOPT_BOOT_JAR_DIR := system/framework
    DEXPREOPT_BOOT_JAR_DIR_FULL_PATH := $(DEXPREOPT_PRODUCT_DIR_FULL_PATH)/$(DEXPREOPT_BOOT_JAR_DIR)
    
    # The default value for LOCAL_DEX_PREOPT
    DEX_PREOPT_DEFAULT ?= true   #如果之前没有被赋值则=true
    
    # $(1): the .jar or .apk to remove classes.dex
    define dexpreopt-remove-classes.dex
    $(hide) zip --quiet --delete $(1) classes.dex; 
    dex_index=2; 
    while zip --quiet --delete $(1) classes$${dex_index}.dex > /dev/null; do 
      let dex_index=dex_index+1; 
    done
    endef
    
    ...

     (3)如果想跳过指定的APK不进行优化,则在 build/core/dex_preopt_odex_install.mk 修改规则,如跳过 QuickApp.apk 不进行优化:

    # Skip QuickPic DEOEX avoid "Didn't find class "com.alensw.PicFolder.QuickApp"
    ifeq ($(LOCAL_MODULE),QuickPic)
    LOCAL_DEX_PREOPT:=
    endif

     (4) 如果编译jar包不想进行优化,可以修改 build/core/java_library.mk 中 LOCAL_DEX_PREOPT := false ,全部重新编译后就可以mm单独编译jar包进行调试。

     (5) 如果发现 LOCAL_DEX_PREOPT:=true时 apk中的.dex无法剔除出来,需要关注另一个参数 LOCAL_CERTIFICATE,作用是指定用于哪个key签名,未指定的默认用testkey:

    LOCAL_CERTIFICATE := testkey     #普通APK,默认情况下使用。
    LOCAL_CERTIFICATE := platform     #该APK完成一些系统的核心功能,经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。
    LOCAL_CERTIFICATE := shared      #该APK需要和home/contacts进程共享数据。
    LOCAL_CERTIFICATE := media       #该APK是media/download系统中的一环。
    LOCAL_CERTIFICATE := PRESIGNED    #表示apk已经签过名了,系统不需要再次签名,为了保持第三方的签名有效,不会剥离.dex文件。

    5.项目中执行的效果

      优化前:

      

      优化后:

      

      对比可发现,没有预优化的apk文件有个classes.dex文件,而预优化的没有这个文件了,java代码(classes.dex)到了oat子目录中。因此,在user环境下编译,生成的apk文件是不能直接上传到服务器的,因为这个apk已经没有java的代码了。所以在编译apk时,把mk文件中添加LOCAL_DEX_PREOPT := false把预编译关闭。

      如果在user下编译的apk,push到daily的系统中,apk依然能够运行,不是没有classes.dex文件了吗?java代码都没有了,为什么还能运行?在第三节可知道,在android系统启动是会优化系统的app,所以daily中的APK的java代码被缓存到手机本地了,push user下编译的apk运行时依然运行的是daily版中apk缓存下来的code。

  • 相关阅读:
    差一个引号的崩溃
    js中.toString()和String()的一丢丢区别
    PC端和手机端页面的一丢丢区别
    LINQ踩坑记录
    C# list group分组扩展,方法来源网络记录备忘
    NPOI分批读取数据
    Xamarin开发登录示例
    动态创建匿名对象利用表达式树动态构建分组条件
    安卓H5互调笔记
    WPF学习6
  • 原文地址:https://www.cnblogs.com/blogs-of-lxl/p/11608115.html
Copyright © 2020-2023  润新知