Android10 dex2oat实践

最近看到一篇博客:Android性能优化之Android 10+ dex2oat实践,对这个优化很感兴趣,打算研究研究能否接入到项目中 。不过该博客只讲述了思路 , 没有给完整源码 。本项目参考该博客的思路,实现了该方案 。
源码地址:https://github.com/carverZhong/DexOpt
一、dex2oat 详解以下是官方对于dex2oat的解释:
ART 使用预先 (AOT) 编译,并且从 Android 7.0(代号 Nougat,简称 N)开始结合使用 AOT、即时 (JIT) 编译和配置文件引导型编译 。所有这些编译模式的组合均可配置,我们将在本部分中对此进行介绍 。例如,Pixel 设备配置了以下编译流程:
  1. 最初安装应用时不进行任何 AOT 编译 。应用前几次运行时,系统会对其进行解译,并对经常执行的方法进行 JIT 编译 。
  2. 当设备闲置和充电时,编译守护程序会运行,以便根据在应用前几次运行期间生成的配置文件对常用代码进行 AOT 编译 。
  3. 下一次重新启动应用时将会使用配置文件引导型代码,并避免在运行时对已经过编译的方法进行 JIT 编译 。在应用后续运行期间经过 JIT 编译的方法将会添加到配置文件中,然后编译守护程序将会对这些方法进行 AOT 编译 。
ART 包括一个编译器(dex2oat 工具)和一个为启动 Zygote 而加载的运行时 (libart.so) 。dex2oat 工具接受一个 APK 文件,并生成一个或多个编译工件文件,然后运行时将会加载这些文件 。文件的个数、扩展名和名称因版本而异 , 但在 Android 8 版本中 , 将会生成以下文件:
.vdex:其中包含 APK 的未压缩 DEX 代码,以及一些旨在加快验证速度的元数据 。.odex:其中包含 APK 中已经过 AOT 编译的方法代码 。.art (optional):其中包含 APK 中列出的某些字符串和类的 ART 内部表示 , 用于加快应用启动速度 。(配置 ART)
也就是说,dex2oat可以触发APK的AOT编译,并生成对应的产物,APP运行时会加载这些文件 。执行过AOT编译的产物能加快启动速度、代码执行效率 。
二、代码实现具体原理还是参考博客:Android性能优化之Android 10+ dex2oat实践 。这里说下实现上的细节 。博客的思路是通过一些手段触发系统来进行dex2oat 。
1.整体思路
  1. PackageManagerShellCommand.runCompile方法可以触发Secondary Apk进行dex2oat,但是Secondary Apk需要先注册 。
  2. 注册的逻辑在IPackageManagerImpl.registerDexModule,其中IPackageManagerImplPackageManagerService的内部类,并继承了IPackageManager.Stub
  3. 最后,再执行PackageManagerShellCommand.runreconcileSecondaryDexFiles反注册,就大功告成了 。
所以整体分三步走:
  • 注册Secondary Apk
  • 执行dex2oat
  • 反注册Secondary Apk
2.注册Secondary ApkIPackageManager是个AIDL接口 , 而应用中的ApplicationPackageManage刚好持有这个AIDL接口,因此可以通过其调用registerDexModule方法 。
为此,可以通过反射调用registerDexModule方法 。以下是核心实现:
// 注册Secondary Apkprivate fun registerDexModule(apkFilePath: String): Boolean {try {val callbackClazz = ReflectUtil.findClass("android.content.pm.PackageManager\$DexModuleRegisterCallback")ReflectUtil.callMethod(getCustomPM(),"registerDexModule",arrayOf(apkFilePath, null),arrayOf(String::class.java, callbackClazz))return true} catch (thr: Throwable) {Log.e(TAG, "registerDexModule: thr.", thr)}return false}/** * 创建一个自定义的 PackageManager,避免影响正常的 PackageManager */private fun getCustomPM(): PackageManager {val customPM = cacheCustomPMif (customPM != null && cachePMBinder?.isBinderAlive == true) {return customPM}val pmBinder = getPMBinder()val pmBinderDynamicProxy = Proxy.newProxyInstance(context.classLoader, ReflectUtil.getInterfaces(pmBinder::class.java)) { _, method, args ->if ("transact" == method.name) {// FLAG_ONEWAY => NONE.args[3] = 0}method.invoke(pmBinder, *args)}val pmStubClass = ReflectUtil.findClass("android.content.pm.IPackageManager\$Stub")val pmStubProxy = ReflectUtil.callStaticMethod(pmStubClass,"asInterface",arrayOf(pmBinderDynamicProxy),arrayOf(IBinder::class.java))val contextImpl = if (context is ContextWrapper) context.baseContext else contextval appPM = createAppPM(contextImpl, pmStubProxy!!)cacheCustomPM = appPMreturn appPM}3.执行dex2oat这里有个难点就是,如何才能调用到PackageManagerShellCommand.runCompile?看下调用逻辑:
// 代码位于PackageManagerService.java 。// IPackageManagerImpl是PackageManagerService的内部类 。@Overridepublic void onShellCommand(FileDescriptor in, FileDescriptor out,FileDescriptor err, String[] args, ShellCallback callback,ResultReceiver resultReceiver) {(new PackageManagerShellCommand(this, mContext, mDomainVerificationManager.getShell())).exec(this, in, out, err, args, callback, resultReceiver);}

推荐阅读