Android10 dex2oat实践( 二 )

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);}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相关方法 , 只不过给的参数不一样 。所以大部分逻辑跟第三步是一样的 。代码实现如下:

经验总结扩展阅读