Android10 dex2oat实践( 二 )

IPackageManager.Stub继承了Binder,而这个方法是Binder中的,调用逻辑如下:
// Binder.javaprotected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,int flags) throws RemoteException {if (code == INTERFACE_TRANSACTION) {reply.writeString(getInterfaceDescriptor());return true;} else if (code == DUMP_TRANSACTION) {// 省略部分代码...return true;} else if (code == SHELL_COMMAND_TRANSACTION) {ParcelFileDescriptor in = data.readFileDescriptor();ParcelFileDescriptor out = data.readFileDescriptor();ParcelFileDescriptor err = data.readFileDescriptor();String[] args = data.readStringArray();ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);try {if (out != null) {// 重点?。。〉饔昧?shellCommand 方法shellCommand(in != null ? in.getFileDescriptor() : null,out.getFileDescriptor(),err != null ? err.getFileDescriptor() : out.getFileDescriptor(),args, shellCallback, resultReceiver);}} finally {// 省略部分代码...}return true;}return false;}public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,@Nullable FileDescriptor err,@NonNull String[] args, @Nullable ShellCallback callback,@NonNull ResultReceiver resultReceiver) throws RemoteException {// 这里调用的?。。?onShellCommand(in, out, err, args, callback, resultReceiver);}所以这里逻辑清晰了,再次整理下逻辑:

  • Binder.onTransact收到 SHELL_COMMAND_TRANSACTION 命令会执行 shellCommand方法
  • shellCommand方法又调用了onShellCommand方法
  • IPackageManager.Stub继承了Binder
  • IPackageManagerImpl继承了IPackageManager.Stub并重写了onShellCommand方法
  • IPackageManagerImpl的onShellCommand执行了PackageManagerShellCommand相关逻辑
所以我们的核心是找到IPackageManager.aidl,并向其发送 SHELL_COMMAND_TRANSACTION 命令 。得益于Android Binder机制,我们可以在应用进程拿到IPackageManger的Binder , 并通过它来发送命令 。
代码实现如下:
// 执行dex2oatprivate fun performDexOpt() {val args = arrayOf("compile", "-f", "--secondary-dex", "-m",if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) "verify" else "speed-profile",context.packageName)executeShellCommand(args)}// IPackageManager.aidl 发送 SHELL_COMMAND_TRANSACTION 命令private fun executeShellCommand(args: Array<String>) {val lastIdentity = Binder.clearCallingIdentity()var data: Parcel? = nullvar reply: Parcel? = nulltry {data = https://www.huyubaike.com/biancheng/Parcel.obtain()reply = Parcel.obtain()data.writeFileDescriptor(FileDescriptor.`in`)data.writeFileDescriptor(FileDescriptor.out)data.writeFileDescriptor(FileDescriptor.err)data.writeStringArray(args)data.writeStrongBinder(null)resultReceiver.writeToParcel(data, 0)getPMBinder().transact(SHELL_COMMAND_TRANSACTION, data, reply, 0)reply.readException()} catch (t: Throwable) {Log.e(TAG,"executeShellCommand error.", t)} finally {data?.recycle()reply?.recycle()}Binder.restoreCallingIdentity(lastIdentity)}4.反注册Secondary Apk反注册也是执行PackageManagerShellCommand相关方法,只不过给的参数不一样 。所以大部分逻辑跟第三步是一样的 。代码实现如下:
private fun reconcileSecondaryDexFiles() {val args = arrayOf("reconcile-secondary-dex-files", context.packageName)executeShellCommand(args)}最后,本项目的代码组织情况如下:
  • DexOpt:外部调用接口,执行DexOpt.dexOpt即可开启dex2oat 。
  • ApkOptimizerN:负责Android7-Android9的dex2oat逻辑 。
  • ApkOptimizerQ:负责Android10的dex2oat逻辑 。也是本文的讲解重点 。
三、优缺点把这项技术应用到了一个插件化项目中,对插件APK进行dex2oat优化,总结下其优缺点 。
1.优点
  • 插件的加载速度大大增加(实测可以达到90%以上),对插件化框架的冷启动有很大的意义 。
  • 代码运行的速度有微小的提升 。测试了跳转Activity、Service这些场景,能够提升20-80ms左右,跟机型有很大的关系 。
2.缺点
  • dex2oat产物也会占用一定的存储空间 。所以如果插件更新记得及时删除老的oat文件 。
  • dex2oat 执行时间较长,首次还是建议直接加载插件 , 在后台执行dex2oat优化 。
  • 部分手机执行后没有成功生成oat文件,还是存在机型兼容问题 。
【Android10 dex2oat实践】

推荐阅读