patchoat相关代码在Android M版中的变化(1)
在Android L中,patchoat被framework直接调用的情况还是不少的,但是在Android M中,这个命令被取消了。所以虽然功能上没有什么新意,逻辑上还是有点小变化的。下面我们分析一下。
Android L上SystemServer的patchoat流程
我们先看看SystemServer的patchoat的过程吧。
ZygoteInit.performSystemServerDexOpt
先说SystemServer,启动SystemServer时,如果systemServerClasspath不为空,则调用performSystemServerDexOpt.
495 /**
496 * Finish remaining work for the newly forked system server process.
497 */
498 private static void handleSystemServerProcess(
499 ZygoteConnection.Arguments parsedArgs)
500 throws ZygoteInit.MethodAndArgsCaller {
501
502 closeServerSocket();
503
504 // set umask to 0077 so new files and directories will default to owner-only permissions.
505 Os.umask(S_IRWXG | S_IRWXO);
506
507 if (parsedArgs.niceName != null) {
508 Process.setArgV0(parsedArgs.niceName);
509 }
510
511 final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
512 if (systemServerClasspath != null) {
513 performSystemServerDexOpt(systemServerClasspath);
514 }
...
我们再看performSystemServerDexOpt。
547 /**
548 * Performs dex-opt on the elements of {@code classPath}, if needed. We
549 * choose the instruction set of the current runtime.
550 */
551 private static void performSystemServerDexOpt(String classPath) {
552 final String[] classPathElements = classPath.split(":");
553 final InstallerConnection installer = new InstallerConnection();
554 final String instructionSet = VMRuntime.getRuntime().vmInstructionSet();
555
556 try {
557 for (String classPathElement : classPathElements) {
558 final byte dexopt = DexFile.isDexOptNeededInternal(classPathElement, "*", instructionSet,
559 false /* defer */);
560 if (dexopt == DexFile.DEXOPT_NEEDED) {
561 installer.dexopt(classPathElement, Process.SYSTEM_UID, false, instructionSet);
562 } else if (dexopt == DexFile.PATCHOAT_NEEDED) {
563 installer.patchoat(classPathElement, Process.SYSTEM_UID, false, instructionSet);
564 }
565 }
566 } catch (IOException ioe) {
567 throw new RuntimeException("Error starting system_server", ioe);
568 } finally {
569 installer.disconnect();
570 }
571 }
最终会调用到PM中的Installer的patchoat命令。
Installer.patchoat
57 public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
58 String instructionSet) {
59 if (!isValidInstructionSet(instructionSet)) {
60 Slog.e(TAG, "Invalid instruction set: " + instructionSet);
61 return -1;
62 }
63
64 return mInstaller.patchoat(apkPath, uid, isPublic, pkgName, instructionSet);
65 }
66
67 public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
68 if (!isValidInstructionSet(instructionSet)) {
69 Slog.e(TAG, "Invalid instruction set: " + instructionSet);
70 return -1;
71 }
72
73 return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet);
74 }
这个mInstaller是个什么东东呢?
36 private final InstallerConnection mInstaller;
37
38 public Installer(Context context) {
39 super(context);
40 mInstaller = new InstallerConnection();
41 }
InstallerConnection.patchoat
114 public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
115 return patchoat(apkPath, uid, isPublic, "*", instructionSet);
116 }
117
118 public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
119 String instructionSet) {
120 StringBuilder builder = new StringBuilder("patchoat");
121 builder.append(' ');
122 builder.append(apkPath);
123 builder.append(' ');
124 builder.append(uid);
125 builder.append(isPublic ? " 1" : " 0");
126 builder.append(' ');
127 builder.append(pkgName);
128 builder.append(' ');
129 builder.append(instructionSet);
130 return execute(builder.toString());
131 }
拼装了一个命令,然后调用execute方法将其执行。
84 public int execute(String cmd) {
85 String res = transact(cmd);
86 try {
87 return Integer.parseInt(res);
88 } catch (NumberFormatException ex) {
89 return -1;
90 }
91 }
先说说InstallerConnection的原理,它是通过socket与installd通信的一个代理,下面是官方注释:
29/**
30 * Represents a connection to {@code installd}. Allows multiple connect and
31 * disconnect cycles.
32 *
33 * @hide for internal use only
34 */
有了这个背景,我们再看transact调用的通信功能就比较清晰了:
48 public synchronized String transact(String cmd) {
49 if (!connect()) {
50 Slog.e(TAG, "connection failed");
51 return "-1";
52 }
53
54 if (!writeCommand(cmd)) {
55 /*
56 * If installd died and restarted in the background (unlikely but
57 * possible) we'll fail on the next write (this one). Try to
58 * reconnect and write the command one more time before giving up.
59 */
60 Slog.e(TAG, "write command failed? reconnect!");
61 if (!connect() || !writeCommand(cmd)) {
62 return "-1";
63 }
64 }
65 if (LOCAL_DEBUG) {
66 Slog.i(TAG, "send: '" + cmd + "'");
67 }
68
69 final int replyLength = readReply();
70 if (replyLength > 0) {
71 String s = new String(buf, 0, replyLength);
72 if (LOCAL_DEBUG) {
73 Slog.i(TAG, "recv: '" + s + "'");
74 }
75 return s;
76 } else {
77 if (LOCAL_DEBUG) {
78 Slog.i(TAG, "fail");
79 }
80 return "-1";
81 }
82 }
writeCommand是向输出中写数据:
202 private boolean writeCommand(String cmdString) {
203 final byte[] cmd = cmdString.getBytes();
204 final int len = cmd.length;
205 if ((len < 1) || (len > buf.length)) {
206 return false;
207 }
208
209 buf[0] = (byte) (len & 0xff);
210 buf[1] = (byte) ((len >> 8) & 0xff);
211 try {
212 mOut.write(buf, 0, 2);
213 mOut.write(cmd, 0, len);
214 } catch (IOException ex) {
215 Slog.e(TAG, "write error");
216 disconnect();
217 return false;
218 }
219 return true;
220 }
我们继续追,看这个mOut是如何来的:
133 private boolean connect() {
134 if (mSocket != null) {
135 return true;
136 }
137 Slog.i(TAG, "connecting...");
138 try {
139 mSocket = new LocalSocket();
140
141 LocalSocketAddress address = new LocalSocketAddress("installd",
142 LocalSocketAddress.Namespace.RESERVED);
143
144 mSocket.connect(address);
145
146 mIn = mSocket.getInputStream();
147 mOut = mSocket.getOutputStream();
148 } catch (IOException ex) {
149 disconnect();
150 return false;
151 }
152 return true;
153 }
于是我们跨越java,继续往installd中追。
installd中的命令入口
164struct cmdinfo cmds[] = {
165 { "ping", 0, do_ping },
166 { "install", 4, do_install },
167 { "dexopt", 6, do_dexopt },
168 { "markbootcomplete", 1, do_mark_boot_complete },
169 { "movedex", 3, do_move_dex },
170 { "rmdex", 2, do_rm_dex },
171 { "remove", 2, do_remove },
172 { "rename", 2, do_rename },
173 { "fixuid", 3, do_fixuid },
174 { "freecache", 1, do_free_cache },
175 { "rmcache", 2, do_rm_cache },
176 { "rmcodecache", 2, do_rm_code_cache },
177 { "getsize", 7, do_get_size },
178 { "rmuserdata", 2, do_rm_user_data },
179 { "movefiles", 0, do_movefiles },
180 { "linklib", 3, do_linklib },
181 { "mkuserdata", 4, do_mk_user_data },
182 { "mkuserconfig", 1, do_mk_user_config },
183 { "rmuser", 1, do_rm_user },
184 { "idmap", 3, do_idmap },
185 { "restorecondata", 3, do_restorecon_data },
186 { "patchoat", 5, do_patchoat },
187};
这个命令对应的是do_patchoat函数
153static int do_patchoat(char **arg, char reply[REPLY_MAX]) {
154 /* apk_path, uid, is_public, pkgname, instruction_set, vm_safe_mode, should_relocate */
155 return dexopt(arg[0], atoi(arg[1]), atoi(arg[2]), arg[3], arg[4], 0, 1);
156}
最终执行到的是dexopt函数。
Android M中的变化
patchoat这一分支从cmds中已经取消了,所有的过程统一交给installer.dexopt中处理。
464 /**
465 * Performs dex-opt on the elements of {@code classPath}, if needed. We
466 * choose the instruction set of the current runtime.
467 */
468 private static void performSystemServerDexOpt(String classPath) {
469 final String[] classPathElements = classPath.split(":");
470 final InstallerConnection installer = new InstallerConnection();
471 installer.waitForConnection();
472 final String instructionSet = VMRuntime.getRuntime().vmInstructionSet();
473
474 try {
475 for (String classPathElement : classPathElements) {
476 final int dexoptNeeded = DexFile.getDexOptNeeded(
477 classPathElement, "*", instructionSet, false /* defer */);
478 if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
479 installer.dexopt(classPathElement, Process.SYSTEM_UID, false,
480 instructionSet, dexoptNeeded, false /* boot complete */);
481 }
482 }
483 } catch (IOException ioe) {
484 throw new RuntimeException("Error starting system_server", ioe);
485 } finally {
486 installer.disconnect();
487 }
488 }
命令合成了一个之后,java层就不得不多加一些状态的判断逻辑。有一些在L版上在PackageManagerService中的逻辑被独立成一个PackageDexOptimizer类中。比如performDexOptLI方法,不但移到了PackageDexOptimizer类中,而且还增加了对DexFile.PATCHOAT_NEEDED等状态的判断。
路径位于:frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java中。
167 } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
168 dexoptType = "patchoat";
169 } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
170 dexoptType = "self patchoat";
171 } else {
172 throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
173 }
174
175 Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
176 + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
177 + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
178 + " oatDir = " + oatDir + " bootComplete=" + bootComplete);
179 final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
180 final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
181 !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
182 dexoptNeeded, vmSafeMode, debuggable, oatDir, bootComplete);
我们对比一下L版本上还在PMS类中的比如performDexOptLI方法,可是不管这些事情的。
4881 final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
4882 pkg.packageName, dexCodeInstructionSet, defer);
4883 if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
4884 Log.i(TAG, "Running dexopt on: " + path + " pkg="
4885 + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
4886 + " vmSafeMode=" + vmSafeMode);
4887 final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
4888 final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
4889 pkg.packageName, dexCodeInstructionSet, vmSafeMode);