Skip to content

zongwu's blog

dexopt的源码跟踪

关于dexopt的预备知识在这里
一般都是从AndroidStudio run出来的apk安装到手机,这里从 adb shell pm install -r xxx.apk 开始看起:
找到android/frameworks/base/cmds/pm/src/com/android/commands/pm/pm.java pm命令的源码
main函数调用了new Pm().run(args);
找到 runInstall()方法
哦刚才的常用参数 "-r" 表示覆盖安装模式 INSTALL_REPLACE_EXISTING
runInstall()方法最终会调用

mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName,
 verificationURI, null, encryptionParams);

而 mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 就是PackageMangerService
PackageMangerService 的main方法:

public static final IPackageManager main(Context context, Installer installer,
 boolean factoryTest, boolean onlyCore) {
 PackageManagerService m = new PackageManagerService(context, installer,
 factoryTest, onlyCore);
 ServiceManager.addService("package", m);
 return m;
 }

去看installPackageWithVerification()

@Override
 public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
 int flags, String installerPackageName, Uri verificationURI,
 ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
 VerificationParams verificationParams = new VerificationParams(verificationURI, null, null,
 VerificationParams.NO_UID, manifestDigest);
 installPackageWithVerificationAndEncryption(packageURI, observer, flags,
 installerPackageName, verificationParams, encryptionParams);
 }

接着看 installPackageWithVerificationAndEncryption()方法

public void installPackageWithVerificationAndEncryption(Uri packageURI,
 IPackageInstallObserver observer, int flags, String installerPackageName,
 VerificationParams verificationParams, ContainerEncryptionParams encryptionParams)

在最后的时候:

final Message msg = mHandler.obtainMessage(INIT_COPY);
 msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
 verificationParams, encryptionParams, user);
 mHandler.sendMessage(msg);

发messge 给handler,找到这个handler的定义

mHandlerThread.start();
 mHandler = new PackageHandler(mHandlerThread.getLooper());
final HandlerThread mHandlerThread = new HandlerThread("PackageManager",
 Process.THREAD_PRIORITY_BACKGROUND);
 final PackageHandler mHandler;

看来 是个工作线程。
跟进handler的handlemsg() 消息类型会从 INIT_COPY 绕成 MCS_BOUND。总算调用了 params.startCopy()
先看handleStartCopy(). InstallParams 对该方法的实现。 略过各种检查和校验 找到了ret = args.copyApk(mContainerService, true);copy apk文件 到 /data/app 目录下 而且是以一种临时文件的格式。

final boolean startCopy() {
 boolean res;
 try {
 if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
 if (++mRetries > MAX_RETRIES) {
 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
 mHandler.sendEmptyMessage(MCS_GIVE_UP);
 handleServiceError();
 return false;
 } else {
 handleStartCopy();
 res = true;
 }
 } catch (RemoteException e) {
 if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
 mHandler.sendEmptyMessage(MCS_RECONNECT);
 res = false;
 }
 handleReturnCode();
 return res;
 }

然后是handleReturnCode() 主要是:

 private void processPendingInstall(final InstallArgs args, final int currentStatus) 

该方法调用

private void installNewPackageLI(PackageParser.Package pkg,
 int parseFlags, int scanMode, UserHandle user,
 String installerPackageName, PackageInstalledInfo res)

终于到了 installnewPackage 了

private void installNewPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, UserHandle user,
String installerPackageName, PackageInstalledInfo res)


private PackageParser.Package scanPackageLI(File scanFile,
 int parseFlags, int scanMode, long currentTime, UserHandle user)

然后调用了

// Note that we invoke the following method only if we are about to unpack an application
 PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode
 | SCAN_UPDATE_SIGNATURE, currentTime, user);

其中有

//invoke installer to do the actual installation
 int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid);



if ((scanMode&SCAN_NO_DEX) == 0) {
 if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
 == DEX_OPT_FAILED) {
 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
 return null;
 }
 }

先看scanPackageLI

private int createDataDirsLI(String packageName, int uid) {
 int[] users = sUserManager.getUserIds();
 int res = mInstaller.install(packageName, uid, uid);
 if (res < 0) {
 return res;
 }
 for (int user : users) {
 if (user != 0) {
 res = mInstaller.createUserData(packageName,
 UserHandle.getUid(user, uid), user);
 if (res < 0) {
 return res;
 }
 }
 }
 return res;
 }

mInstaller.install() 出现了
再看performDexOptLI()

private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) {
 boolean performed = false;
 if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
 String path = pkg.mScanPath;
 int ret = 0;
 try {
 if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
 if (!forceDex && defer) {
 mDeferredDexOpt.add(pkg);
 return DEX_OPT_DEFERRED;
 } else {
 Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
 final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
 ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg));
 pkg.mDidDexOpt = true;
 performed = true;
 }
 }
 } catch (FileNotFoundException e) {
 Slog.w(TAG, "Apk not found for dexopt: " + path);
 ret = -1;
 } catch (IOException e) {
 Slog.w(TAG, "IOException reading apk: " + path, e);
 ret = -1;
 } catch (dalvik.system.StaleDexCacheError e) {
 Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
 ret = -1;
 } catch (Exception e) {
 Slog.w(TAG, "Exception when doing dexopt : ", e);
 ret = -1;
 }
 if (ret < 0) {
 //error from installer
 return DEX_OPT_FAILED;
 }
 }
 return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
 }

mInstaller.dexopt() 终于也找出来了
PackageManagerService通过socket访问installd的服务进程,而installd的服务是在init进程里被启动的。继续看 frameworks/base/cmds/installd/installd.c

int main(const int argc, const char *argv[]) {
 char buf[BUFFER_MAX];
 struct sockaddr addr;
 socklen_t alen;
 int lsocket, s, count;
 if (initialize_globals() < 0) {
 ALOGE("Could not initialize globals; exiting.\n");
 exit(1);
 }
 if (initialize_directories() < 0) {
 ALOGE("Could not create directories; exiting.\n");
 exit(1);
 }
 lsocket = android_get_control_socket(SOCKET_PATH);
 if (lsocket < 0) {
 ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
 exit(1);
 }
 if (listen(lsocket, 5)) {
 ALOGE("Listen on socket failed: %s\n", strerror(errno));
 exit(1);
 }
 fcntl(lsocket, F_SETFD, FD_CLOEXEC);
 for (;;) {
 alen = sizeof(addr);
 s = accept(lsocket, &addr, &alen);
 if (s < 0) {
 ALOGE("Accept failed: %s\n", strerror(errno));
 continue;
 }
 fcntl(s, F_SETFD, FD_CLOEXEC);
 ALOGI("new connection\n");
 for (;;) {
 unsigned short count;
 if (readx(s, &count, sizeof(count))) {
 ALOGE("failed to read size\n");
 break;
 }
 if ((count < 1) || (count >= BUFFER_MAX)) {
 ALOGE("invalid size %d\n", count);
 break;
 }
 if (readx(s, buf, count)) {
 ALOGE("failed to read command\n");
 break;
 }
 buf[count] = 0;
 if (execute(s, buf)) break;
 }
 ALOGI("closing connection\n");
 close(s);
 }
 return 0;
}

接收的命令:

struct cmdinfo {
 const char *name;
 unsigned numargs;
 int (*func)(char **arg, char reply[REPLY_MAX]);
};
struct cmdinfo cmds[] = {
 { "ping", 0, do_ping },
 { "install", 3, do_install },
 { "dexopt", 3, do_dexopt },
 { "movedex", 2, do_move_dex },
 { "rmdex", 1, do_rm_dex },
 { "remove", 2, do_remove },
 { "rename", 2, do_rename },
 { "freecache", 1, do_free_cache },
 { "rmcache", 1, do_rm_cache },
 { "protect", 2, do_protect },
 { "getsize", 4, do_get_size },
 { "rmuserdata", 2, do_rm_user_data },
 { "movefiles", 0, do_movefiles },
 { "linklib", 2, do_linklib },
 { "unlinklib", 1, do_unlinklib },
 { "mkuserdata", 3, do_mk_user_data },
 { "rmuser", 1, do_rm_user },
 { "cloneuserdata", 3, do_clone_user_data },
};

比如

static int do_install(char **arg, char reply[REPLY_MAX])
{
 return install(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, gid */
}
static int do_dexopt(char **arg, char reply[REPLY_MAX])
{
 /* apk_path, uid, is_public */
 return dexopt(arg[0], atoi(arg[1]), atoi(arg[2]));
}

frameworks/base/cmds/installd/commands.c

int install(const char *pkgname, uid_t uid, gid_t gid)
{
 char pkgdir[PKG_PATH_MAX];
 char libdir[PKG_PATH_MAX];
 if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
 ALOGE("invalid uid/gid: %d %d\n", uid, gid);
 return -1;
 }
 if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
 ALOGE("cannot create package path\n");
 return -1;
 }
 if (create_pkg_path(libdir, pkgname, PKG_LIB_POSTFIX, 0)) {
 ALOGE("cannot create package lib path\n");
 return -1;
 }
 if (mkdir(pkgdir, 0751) < 0) {
 ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
 return -errno;
 }
 if (chmod(pkgdir, 0751) < 0) {
 ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
 unlink(pkgdir);
 return -errno;
 }
 if (chown(pkgdir, uid, gid) < 0) {
 ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
 unlink(pkgdir);
 return -errno;
 }
 if (mkdir(libdir, 0755) < 0) {
 ALOGE("cannot create dir '%s': %s\n", libdir, strerror(errno));
 unlink(pkgdir);
 return -errno;
 }
 if (chmod(libdir, 0755) < 0) {
 ALOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
 unlink(libdir);
 unlink(pkgdir);
 return -errno;
 }
 if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
 ALOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
 unlink(libdir);
 unlink(pkgdir);
 return -errno;
 }2
 return 0;
}

adb pm -r install xxx.apk 的流程跟踪结束。

再来看看MultiDex.install()的流程:

android.support.multidex.MultiDex.V14#install()
android.support.multidex.MultiDex.V14#makeDexElements();
通过反射调用了DexPathList 的makeDexElements()
该方法就是调用dex = loadDexFile(file, optimizedDirectory);
看看 loadDexFile

/**
* Constructs a {@code DexFile} instance, as appropriate depending
* on whether {@code optimizedDirectory} is {@code null}.
*/
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}

第一次load dex文件的时候 是 optimizedDirectory 空的 。 进入 return new DexFile(file); 的分支。

/*
* Open a DEX file. The value returned is a magic VM cookie. On
* failure, an IOException is thrown.
*/
native private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException;

进入native层代码openDexFile()

static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
JValue* pResult)

该方法比较长 主要是调用了dvmJarFileOpen()方法,在 android/dalvik/vm/JarFile.cpp

/*
 * Open a Jar file. It's okay if it's just a Zip archive without all of
 * the Jar trimmings, but we do insist on finding "classes.dex" inside
 * or an appropriately-named ".odex" file alongside.
 *
 * If "isBootstrap" is not set, the optimizer/verifier regards this DEX as
 * being part of a different class loader.
 */
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
 JarFile** ppJarFile, bool isBootstrap)
{
 /*
 * TODO: This function has been duplicated and modified to become
 * dvmRawDexFileOpen() in RawDexFile.c. This should be refactored.
 */
 ZipArchive archive;
 DvmDex* pDvmDex = NULL;
 char* cachedName = NULL;
 bool archiveOpen = false;
 bool locked = false;
 int fd = -1;
 int result = -1;
 /* Even if we're not going to look at the archive, we need to
 * open it so we can stuff it into ppJarFile.
 */
 if (dexZipOpenArchive(fileName, &archive) != 0)
 goto bail;
 archiveOpen = true;
 /* If we fork/exec into dexopt, don't let it inherit the archive's fd.
 */
 dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
 /* First, look for a ".odex" alongside the jar file. It will
 * have the same name/path except for the extension.
 */
 fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
 if (fd >= 0) {
 ALOGV("Using alternate file (odex) for %s ...", fileName);
 if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
 ALOGE("%s odex has stale dependencies", fileName);
 free(cachedName);
 cachedName = NULL;
 close(fd);
 fd = -1;
 goto tryArchive;
 } else {
 ALOGV("%s odex has good dependencies", fileName);
 //TODO: make sure that the .odex actually corresponds
 // to the classes.dex inside the archive (if present).
 // For typical use there will be no classes.dex.
 }
 } else {
 ZipEntry entry;
tryArchive:
 /*
 * Pre-created .odex absent or stale. Look inside the jar for a
 * "classes.dex".
 */
 entry = dexZipFindEntry(&archive, kDexInJarName);
 if (entry != NULL) {
 bool newFile = false;
 /*
 * We've found the one we want. See if there's an up-to-date copy
 * in the cache.
 *
 * On return, "fd" will be seeked just past the "opt" header.
 *
 * If a stale .odex file is present and classes.dex exists in
 * the archive, this will *not* return an fd pointing to the
 * .odex file; the fd will point into dalvik-cache like any
 * other jar.
 */
 if (odexOutputName == NULL) {
 cachedName = dexOptGenerateCacheFileName(fileName,
 kDexInJarName);
 if (cachedName == NULL)
 goto bail;
 } else {
 cachedName = strdup(odexOutputName);
 }
 ALOGV("dvmJarFileOpen: Checking cache for %s (%s)",
 fileName, cachedName);
 fd = dvmOpenCachedDexFile(fileName, cachedName,
 dexGetZipEntryModTime(&archive, entry),
 dexGetZipEntryCrc32(&archive, entry),
 isBootstrap, &newFile, /*createIfMissing=*/true);
 if (fd < 0) {
 ALOGI("Unable to open or create cache for %s (%s)",
 fileName, cachedName);
 goto bail;
 }
 locked = true;
 /*
 * If fd points to a new file (because there was no cached version,
 * or the cached version was stale), generate the optimized DEX.
 * The file descriptor returned is still locked, and is positioned
 * just past the optimization header.
 */
 if (newFile) {
 u8 startWhen, extractWhen, endWhen;
 bool result;
 off_t dexOffset;
 dexOffset = lseek(fd, 0, SEEK_CUR);
 result = (dexOffset > 0);
 if (result) {
 startWhen = dvmGetRelativeTimeUsec();
 result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
 extractWhen = dvmGetRelativeTimeUsec();
 }
 if (result) {
 result = dvmOptimizeDexFile(fd, dexOffset,
 dexGetZipEntryUncompLen(&archive, entry),
 fileName,
 dexGetZipEntryModTime(&archive, entry),
 dexGetZipEntryCrc32(&archive, entry),
 isBootstrap);
 }
 if (!result) {
 ALOGE("Unable to extract+optimize DEX from '%s'",
 fileName);
 goto bail;
 }
 endWhen = dvmGetRelativeTimeUsec();
 ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms",
 fileName,
 (int) (extractWhen - startWhen) / 1000,
 (int) (endWhen - extractWhen) / 1000);
 }
 } else {
 ALOGI("Zip is good, but no %s inside, and no valid .odex "
 "file in the same directory", kDexInJarName);
 goto bail;
 }
 }
 /*
 * Map the cached version. This immediately rewinds the fd, so it
 * doesn't have to be seeked anywhere in particular.
 */
 if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
 ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
 goto bail;
 }
 if (locked) {
 /* unlock the fd */
 if (!dvmUnlockCachedDexFile(fd)) {
 /* uh oh -- this process needs to exit or we'll wedge the system */
 ALOGE("Unable to unlock DEX file");
 goto bail;
 }
 locked = false;
 }
 ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
 *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
 (*ppJarFile)->archive = archive;
 (*ppJarFile)->cacheFileName = cachedName;
 (*ppJarFile)->pDvmDex = pDvmDex;
 cachedName = NULL; // don't free it below
 result = 0;
bail:
 /* clean up, closing the open file */
 if (archiveOpen && result != 0)
 dexZipCloseArchive(&archive);
 free(cachedName);
 if (fd >= 0) {
 if (locked)
 (void) dvmUnlockCachedDexFile(fd);
 close(fd);
 }
 return result;
}

其中 , 执行了dvmOpenCachedDexFile()后 newFile 为true 。 会执行

result = dvmOptimizeDexFile(fd, dexOffset,
 dexGetZipEntryUncompLen(&archive, entry),
 fileName,
 dexGetZipEntryModTime(&archive, entry),
 dexGetZipEntryCrc32(&archive, entry),
 isBootstrap);

这里调用了 /dalvik/vm/RawDexFile.cpp 中的 dvmOptimizeDexFile()方法

/*
 * Given a descriptor for a file with DEX data in it, produce an
 * optimized version.
 *
 * The file pointed to by "fd" is expected to be a locked shared resource
 * (or private); we make no efforts to enforce multi-process correctness
 * here.
 *
 * "fileName" is only used for debug output. "modWhen" and "crc" are stored
 * in the dependency set.
 *
 * The "isBootstrap" flag determines how the optimizer and verifier handle
 * package-scope access checks. When optimizing, we only load the bootstrap
 * class DEX files and the target DEX, so the flag determines whether the
 * target DEX classes are given a (synthetic) non-NULL classLoader pointer.
 * This only really matters if the target DEX contains classes that claim to
 * be in the same package as bootstrap classes.
 *
 * The optimizer will need to load every class in the target DEX file.
 * This is generally undesirable, so we start a subprocess to do the
 * work and wait for it to complete.
 *
 * Returns "true" on success. All data will have been written to "fd".
 */
bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
 const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
 const char* lastPart = strrchr(fileName, '/');
 if (lastPart != NULL)
 lastPart++;
 else
 lastPart = fileName;
 ALOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---", lastPart, isBootstrap);
 pid_t pid;
 /*
 * This could happen if something in our bootclasspath, which we thought
 * was all optimized, got rejected.
 */
 if (gDvm.optimizing) {
 ALOGW("Rejecting recursive optimization attempt on '%s'", fileName);
 return false;
 }
 pid = fork();
 if (pid == 0) {
 static const int kUseValgrind = 0;
 static const char* kDexOptBin = "/bin/dexopt";
 static const char* kValgrinder = "/usr/bin/valgrind";
 static const int kFixedArgCount = 10;
 static const int kValgrindArgCount = 5;
 static const int kMaxIntLen = 12; // '-'+10dig+'\0' -OR- 0x+8dig
 int bcpSize = dvmGetBootPathSize();
 int argc = kFixedArgCount + bcpSize
 + (kValgrindArgCount * kUseValgrind);
 const char* argv[argc+1]; // last entry is NULL
 char values [argc][kMaxIntLen];
 char* execFile;
 const char* androidRoot;
 int flags;
 /* change process groups, so we don't clash with ProcessManager */
 setpgid(0, 0);
 /* full path to optimizer */
 androidRoot = getenv("ANDROID_ROOT");
 if (androidRoot == NULL) {
 ALOGW("ANDROID_ROOT not set, defaulting to /system");
 androidRoot = "/system";
 }
 execFile = (char*)alloca(strlen(androidRoot) + strlen(kDexOptBin) + 1);
 strcpy(execFile, androidRoot);
 strcat(execFile, kDexOptBin);
 /*
 * Create arg vector.
 */
 int curArg = 0;
 if (kUseValgrind) {
 /* probably shouldn't ship the hard-coded path */
 argv[curArg++] = (char*)kValgrinder;
 argv[curArg++] = "--tool=memcheck";
 argv[curArg++] = "--leak-check=yes"; // check for leaks too
 argv[curArg++] = "--leak-resolution=med"; // increase from 2 to 4
 argv[curArg++] = "--num-callers=16"; // default is 12
 assert(curArg == kValgrindArgCount);
 }
 argv[curArg++] = execFile;
 argv[curArg++] = "--dex";
 sprintf(values[2], "%d", DALVIK_VM_BUILD);
 argv[curArg++] = values[2];
 sprintf(values[3], "%d", fd);
 argv[curArg++] = values[3];
 sprintf(values[4], "%d", (int) dexOffset);
 argv[curArg++] = values[4];
 sprintf(values[5], "%d", (int) dexLength);
 argv[curArg++] = values[5];
 argv[curArg++] = (char*)fileName;
 sprintf(values[7], "%d", (int) modWhen);
 argv[curArg++] = values[7];
 sprintf(values[8], "%d", (int) crc);
 argv[curArg++] = values[8];
 flags = 0;
 if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) {
 flags |= DEXOPT_OPT_ENABLED;
 if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)
 flags |= DEXOPT_OPT_ALL;
 }
 if (gDvm.classVerifyMode != VERIFY_MODE_NONE) {
 flags |= DEXOPT_VERIFY_ENABLED;
 if (gDvm.classVerifyMode == VERIFY_MODE_ALL)
 flags |= DEXOPT_VERIFY_ALL;
 }
 if (isBootstrap)
 flags |= DEXOPT_IS_BOOTSTRAP;
 if (gDvm.generateRegisterMaps)
 flags |= DEXOPT_GEN_REGISTER_MAPS;
 sprintf(values[9], "%d", flags);
 argv[curArg++] = values[9];
 assert(((!kUseValgrind && curArg == kFixedArgCount) ||
 ((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount))));
 ClassPathEntry* cpe;
 for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
 argv[curArg++] = cpe->fileName;
 }
 assert(curArg == argc);
 argv[curArg] = NULL;
 if (kUseValgrind)
 execv(kValgrinder, const_cast<char**>(argv));
 else
 execv(execFile, const_cast<char**>(argv));
 ALOGE("execv '%s'%s failed: %s", execFile,
 kUseValgrind ? " [valgrind]" : "", strerror(errno));
 exit(1);
 } else {
 ALOGV("DexOpt: waiting for verify+opt, pid=%d", (int) pid);
 int status;
 pid_t gotPid;
 /*
 * Wait for the optimization process to finish. We go into VMWAIT
 * mode here so GC suspension won't have to wait for us.
 */
 ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
 while (true) {
 gotPid = waitpid(pid, &status, 0);
 if (gotPid == -1 && errno == EINTR) {
 ALOGD("waitpid interrupted, retrying");
 } else {
 break;
 }
 }
 dvmChangeStatus(NULL, oldStatus);
 if (gotPid != pid) {
 ALOGE("waitpid failed: wanted %d, got %d: %s",
 (int) pid, (int) gotPid, strerror(errno));
 return false;
 }
 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
 ALOGD("DexOpt: --- END '%s' (success) ---", lastPart);
 return true;
 } else {
 ALOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed",
 lastPart, status);
 return false;
 }
 }
}

终于找到 ALOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---", lastPart, isBootstrap); 这句日志的出处了!

该方法的注释写的也比较详细了,其中pid=fork(); 表明dexopt确实开启了新的进程来完成,而且当前进程是使用while循环等待这个子进程返回结果的。

那么之前关于dexopt的所有疑惑都解开了。