深入理解Dalvik虚拟机- Android应用进程启动过程分析

Posted threepigs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Dalvik虚拟机- Android应用进程启动过程分析相关的知识,希望对你有一定的参考价值。

android的应用进程启动是apk在manifest里申明的Activity,Service,BroadcastReceiver等组件被调起时而触发的。我们以Activity为例,当点击桌面上的应用图标时,桌面会调用startActivity,启动manifest里申明的相应Launcher的Activity,而Activity的startActivity会通过Binder调用来到ActivityManagerService(AMS)里。AMS是system_server的一个服务,负责管理Android系统的所有Activity的栈,逻辑比较复杂,在这里就不详细分析,以后专门写AMS的专题。AMS里startActivity的时候,如果发现这个应用进程没有启动,那么就会通过Zygote创建出这个进程。 

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java:

private final void startProcessLocked(ProcessRecord app,
            String hostingType, String hostingNameStr) 
   ...
   ...
   Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, null);
   ...
   ...

显然是通过调用Process.start来启动进程,”android.app.ActivityThread” 这个参数是整个进程启动的入口类,后续的分析可以看到,进程被fork出来之后,就会调用android.app.ActivityThread的main函数。

frameworks/base/core/java/android/os/Process.java:

    public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String[] zygoteArgs) 
        try 
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs);
         catch (ZygoteStartFailedEx ex) 
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        
    

    private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx 
        synchronized(Process.class) 
            ArrayList<String> argsForZygote = new ArrayList<String>();

            // --runtime-init, --setuid=, --setgid=,
            // and --setgroups= must go first
            argsForZygote.add("--runtime-init");
            argsForZygote.add("--setuid=" + uid);
            argsForZygote.add("--setgid=" + gid);
            if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) 
                argsForZygote.add("--enable-jni-logging");
            
            if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) 
                argsForZygote.add("--enable-safemode");
            
            if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) 
                argsForZygote.add("--enable-debugger");
            
            if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) 
                argsForZygote.add("--enable-checkjni");
            
            if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) 
                argsForZygote.add("--enable-assert");
            
            if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) 
                argsForZygote.add("--mount-external-multiuser");
             else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) 
                argsForZygote.add("--mount-external-multiuser-all");
            
            argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

            //TODO optionally enable debuger
            //argsForZygote.add("--enable-debugger");

            // --setgroups is a comma-separated list
            if (gids != null && gids.length > 0) 
                StringBuilder sb = new StringBuilder();
                sb.append("--setgroups=");

                int sz = gids.length;
                for (int i = 0; i < sz; i++) 
                    if (i != 0) 
                        sb.append(',');
                    
                    sb.append(gids[i]);
                

                argsForZygote.add(sb.toString());
            

            if (niceName != null) 
                argsForZygote.add("--nice-name=" + niceName);
            

            if (seInfo != null) 
                argsForZygote.add("--seinfo=" + seInfo);
            

            argsForZygote.add(processClass);

            if (extraArgs != null) 
                for (String arg : extraArgs) 
                    argsForZygote.add(arg);
                
            
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        
    

    private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx 
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) 
            try 
                primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET);
             catch (IOException ioe) 
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            
        

        if (primaryZygoteState.matches(abi)) 
            return primaryZygoteState;
        

        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) 
            try 
            secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET);
             catch (IOException ioe) 
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            
        

        if (secondaryZygoteState.matches(abi)) 
            return secondaryZygoteState;
        

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    

调用startViaZygote函数,从代码可以看到,Zygote创建进程是socket跨进程的调用。通过LocalSocket通信,来完成进程的创建,所以这里的Process.start只是一个Client端
的调用,实际是由Server端的接收到消息后处理的。
Server端是app_process这个进程里,这是个常驻的系统服务。

frameworks/base/cmds/app_process/app_main.cpp

class AppRuntime : public AndroidRuntime


AppRuntime继承自AndroidRuntime,AndroidRuntime类是libandroid_runtime.so里导出的,frameworks/base/core/jni/AndroidRuntime.cpp。
app_process这个进程在init.rc里会创建,

#init.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

app_process以zygote作为进程名,这个adb shell进入手机ps一下就可以看到zygote。

这个进程的main函数里有如下初始化代码片段:

    AppRuntime runtime;
    const char* argv0 = argv[0];

    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    // Everything up to '--' or first non '-' arg goes to the vm

    int i = runtime.addVmArguments(argc, argv);

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    const char* parentDir = NULL;
    const char* niceName = NULL;
    const char* className = NULL;
    while (i < argc) 
        const char* arg = argv[i++];
        if (!parentDir) 
            parentDir = arg;
         else if (strcmp(arg, "--zygote") == 0) 
            zygote = true;
            niceName = "zygote";
         else if (strcmp(arg, "--start-system-server") == 0) 
            startSystemServer = true;
         else if (strcmp(arg, "--application") == 0) 
            application = true;
         else if (strncmp(arg, "--nice-name=", 12) == 0) 
            niceName = arg + 12;
         else 
            className = arg;
            break;
        
    

    if (niceName && *niceName) 
        setArgv0(argv0, niceName);
        set_process_name(niceName);
    

    runtime.mParentDir = parentDir;

    if (zygote) 
        runtime.start("com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
     else if (className) 
        // Remainder of args get passed to startup class main()
        runtime.mClassName = className;
        runtime.mArgC = argc - i;
        runtime.mArgV = argv + i;
        runtime.start("com.android.internal.os.RuntimeInit",
                application ? "application" : "tool");
    

 第一个if是正常的Zygote进程启动,执行AndroidRuntime::start,会调用ZygoteInit的类main函数。app_process的初始化就在ZygoteInit里,监听本地的socket,接收ZygoteClient的请求,当有请求来的时候,调用ZygoteConnection::runOnce,从而调用Zygote.forkAndSpecialize来创建新的进程,并且调用RuntimeInit.zygoteInit做进程的初始化,初始化过程就会调用ActivityThread.main

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
...
...
/*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) 
        ALOGE("JavaVM unable to locate class '%s'\\n", slashClassName);
        /* keep going */
     else 
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) 
            ALOGE("JavaVM unable to find main() in '%s'\\n", className);
            /* keep going */
         else 
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        
    
...
第二个当指定了className的时候,则用com.android.internal.os.RuntimeInit作为入口,指定className的场景是用am命令创建的进程的时候:

exec app_process $base/bin com.android.commands.am.Am "$@" 

 而我们正常的Zygote进程则走的是ZygoteInit。

AndroidRuntime.start函数会调用startVm,frameworks/base/core/jni/AndroidRuntime.cpp

    
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)

    ...
    initArgs.version = JNI_VERSION_1_4;
    initArgs.options = mOptions.editArray();
    initArgs.nOptions = mOptions.size();
    initArgs.ignoreUnrecognized = JNI_FALSE;
    /*
     * Initialize the VM.
     *
     * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
     * If this call succeeds, the VM is ready, and we can start issuing
     * JNI calls.
     */
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) 
        ALOGE("JNI_CreateJavaVM failed\\n");
        goto bail;
    

startVm会初始化JavaVm和JNIEnv,最终是通过JNI_CreateJavaVM实现的,JNI_CreateJavaVM是个接口,不同的虚拟机有不同的实现。

Dalvik虚拟机相关的代码主要在下面几个目录下: 
libcore/libdvm
libcore/dalvik
dalvik/vm/

Dalvik的JNI_CreateJavaVM在dalvik/vm/Jni.cpp 里实现:

jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) 
    const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
    if (dvmIsBadJniVersion(args->version)) 
        ALOGE("Bad JNI version passed to CreateJavaVM: %d", args->version);
        return JNI_EVERSION;
    

    // TODO: don't allow creation of multiple VMs -- one per customer for now

    /* zero globals; not strictly necessary the first time a VM is started */
    memset(&gDvm, 0, sizeof(gDvm));

    /*
     * Set up structures for JNIEnv and VM.
     */
    JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));
    pVM->funcTable = &gInvokeInterface;
    pVM->envList = NULL;
    dvmInitMutex(&pVM->envListLock);

    UniquePtr<const char*[]> argv(new const char*[args->nOptions]);
    memset(argv.get(), 0, sizeof(char*) * (args->nOptions));

    /*
     * Convert JNI args to argv.
     *
     * We have to pull out vfprintf/exit/abort, because they use the
     * "extraInfo" field to pass function pointer "hooks" in.  We also
     * look for the -Xcheck:jni stuff here.
     */
    int argc = 0;
    for (int i = 0; i < args->nOptions; i++) 
        const char* optStr = args->options[i].optionString;
        if (optStr == NULL) 
            dvmFprintf(stderr, "ERROR: CreateJavaVM failed: argument %d was NULL\\n", i);
            return JNI_ERR;
         else if (strcmp(optStr, "vfprintf") == 0) 
            gDvm.vfprintfHook = (int (*)(FILE *, const char*, va_list))args->options[i].extraInfo;
         else if (strcmp(optStr, "exit") == 0) 
            gDvm.exitHook = (void (*)(int)) args->options[i].extraInfo;
         else if (strcmp(optStr, "abort") == 0) 
            gDvm.abortHook = (void (*)(void))args->options[i].extraInfo;
         else if (strcmp(optStr, "sensitiveThread") == 0) 
            gDvm.isSensitiveThreadHook = (bool (*)(void))args->options[i].extraInfo;
         else if (strcmp(optStr, "-Xcheck:jni") == 0) 
            gDvmJni.useCheckJni = true;
         else if (strncmp(optStr, "-Xjniopts:", 10) == 0) 
            char* jniOpts = strdup(optStr + 10);
            size_t jniOptCount = 1;
            for (char* p = jniOpts; *p != 0; ++p) 
                if (*p == ',') 
                    ++jniOptCount;
                    *p = 0;
                
            
            char* jniOpt = jniOpts;
            for (size_t i = 0; i < jniOptCount; ++i) 
                if (strcmp(jniOpt, "warnonly") == 0) 
                    gDvmJni.warnOnly = true;
                 else if (strcmp(jniOpt, "forcecopy") == 0) 
                    gDvmJni.forceCopy = true;
                 else if (strcmp(jniOpt, "logThirdPartyJni") == 0) 
                    gDvmJni.logThirdPartyJni = true;
                 else 
                    dvmFprintf(stderr, "ERROR: CreateJavaVM failed: unknown -Xjniopts option '%s'\\n",
                            jniOpt);
                    free(pVM);
                    free(jniOpts);
                    return JNI_ERR;
                
                jniOpt += strlen(jniOpt) + 1;
            
            free(jniOpts);
         else 
            /* regular option */
            argv[argc++] = optStr;
        
    

    if (gDvmJni.useCheckJni) 
        dvmUseCheckedJniVm(pVM);
    

    if (gDvmJni.jniVm != NULL) 
        dvmFprintf(stderr, "ERROR: Dalvik only supports one VM per process\\n");
        free(pVM);
        return JNI_ERR;
    
    gDvmJni.jniVm = (JavaVM*) pVM;

    /*
     * Create a JNIEnv for the main thread.  We need to have something set up
     * here because some of the class initialization we do when starting
     * up the VM will call into native code.
     */
    JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);

    /* Initialize VM. */
    gDvm.initializing = true;
    std::string status =
            dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
    gDvm.initializing = false;

    if (!status.empty()) 
        free(pEnv);
        free(pVM);
        ALOGW("CreateJavaVM failed: %s", status.c_str());
        return JNI_ERR;
    

    /*
     * Success!  Return stuff to caller.
     */
    dvmChangeStatus(NULL, THREAD_NATIVE);
    *p_env = (JNIEnv*) pEnv;
    *p_vm = (JavaVM*) pVM;
    ALOGV("CreateJavaVM succeeded");
    return JNI_OK;

malloc完了之后,会调用dvmStartup完成虚拟机的启动,dvmStartup定义在dalvik/vm/Init.cpp里:

/*
 * VM initialization.  Pass in any options provided on the command line.
 * Do not pass in the class name or the options for the class.
 *
 * Returns 0 on success.
 */
std::string dvmStartup(int argc, const char* const argv[],
        bool ignoreUnrecognized, JNIEnv* pEnv)

    ScopedShutdown scopedShutdown;

    assert(gDvm.initializing);

    ALOGV("VM init args (%d):", argc);
    for (int i = 0; i < argc; i++) 
        ALOGV("  %d: '%s'", i, argv[i]);
    
    setCommandLineDefaults();

    /*
     * Process the option flags (if any).
     */
    int cc = processOptions(argc, argv, ignoreUnrecognized);
    if (cc != 0) 
        if (cc < 0) 
            dvmFprintf(stderr, "\\n");
            usage("dalvikvm");
        
        return "syntax error";
    

#if WITH_EXTRA_GC_CHECKS > 1
    /* only "portable" interp has the extra goodies */
    if (gDvm.executionMode != kExecutionModeInterpPortable) 
        ALOGI("Switching to 'portable' interpreter for GC checks");
        gDvm.executionMode = kExecutionModeInterpPortable;
    
#endif

    /* Configure group scheduling capabilities */
    if (!access("/dev/cpuctl/tasks", F_OK)) 
        ALOGV("Using kernel group scheduling");
        gDvm.kernelGroupScheduling = 1;
     else 
        ALOGV("Using kernel scheduler policies");
    

    /* configure signal handling */
    if (!gDvm.reduceSignals)
        blockSignals();

    /* verify system page size */
    if (sysconf(_SC_PAGESIZE) != SYSTEM_PAGE_SIZE) 
        return StringPrintf("expected page size %d, got %d",
                SYSTEM_PAGE_SIZE, (int) sysconf(_SC_PAGESIZE));
    

    /* mterp setup */
    ALOGV("Using executionMode %d", gDvm.executionMode);
    dvmCheckAsmConstants();

    /*
     * Initialize components.
     */
    dvmQuasiAtomicsStartup();
    if (!dvmAllocTrackerStartup()) 
        return "dvmAllocTrackerStartup failed";
    
    if (!dvmGcStartup()) 
        return "dvmGcStartup failed";
    
    if (!dvmThreadStartup()) 
        return "dvmThreadStartup failed";
    
    if (!dvmInlineNativeStartup()) 
        return "dvmInlineNativeStartup";
    
    if (!dvmRegisterMapStartup()) 
        return "dvmRegisterMapStartup failed";
    
    if (!dvmInstanceofStartup()) 
        return "dvmInstanceofStartup failed";
    
    if (!dvmClassStartup()) 
        return "dvmClassStartup failed";
    

    /*
     * At this point, the system is guaranteed to be sufficiently
     * initialized that we can look up classes and class members. This
     * call populates the gDvm instance with all the class and member
     * references that the VM wants to use directly.
     */
    if (!dvmFindRequiredClassesAndMembers()) 
        return "dvmFindRequiredClassesAndMembers failed";
    

    if (!dvmStringInternStartup()) 
        return "dvmStringInternStartup failed";
    
    if (!dvmNativeStartup()) 
        return "dvmNativeStartup failed";
    
    if (!dvmInternalNativeStartup()) 
        return "dvmInternalNativeStartup failed";
    
    if (!dvmJniStartup()) 
        return "dvmJniStartup failed";
    
    if (!dvmProfilingStartup()) 
        return "dvmProfilingStartup failed";
    

    /*
     * Create a table of methods for which we will substitute an "inline"
     * version for performance.
     */
    if (!dvmCreateInlineSubsTable()) 
        return "dvmCreateInlineSubsTable failed";
    

    /*
     * Miscellaneous class library validation.
     */
    if (!dvmValidateBoxClasses()) 
        return "dvmValidateBoxClasses failed";
    

    /*
     * Do the last bits of Thread struct initialization we need to allow
     * JNI calls to work.
     */
    if (!dvmPrepMainForJni(pEnv)) 
        return "dvmPrepMainForJni failed";
    

    /*
     * Explicitly initialize java.lang.Class.  This doesn't happen
     * automatically because it's allocated specially (it's an instance
     * of itself).  Must happen before registration of system natives,
     * which make some calls that throw assertions if the classes they
     * operate on aren't initialized.
     */
    if (!dvmInitClass(gDvm.classJavaLangClass)) 
        return "couldn't initialized java.lang.Class";
    

    /*
     * Register the system native methods, which are registered through JNI.
     */
    if (!registerSystemNatives(pEnv)) 
        return "couldn't register system natives";
    

    /*
     * Do some "late" initialization for the memory allocator.  This may
     * allocate storage and initialize classes.
     */
    if (!dvmCreateStockExceptions()) 
        return "dvmCreateStockExceptions failed";
    

    /*
     * At this point, the VM is in a pretty good state.  Finish prep on
     * the main thread (specifically, create a java.lang.Thread object to go
     * along with our Thread struct).  Note we will probably be executing
     * some interpreted class initializer code in here.
     */
    if (!dvmPrepMainThread()) 
        return "dvmPrepMainThread failed";
    

    /*
     * Make sure we haven't accumulated any tracked references.  The main
     * thread should be starting with a clean slate.
     */
    if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
    
        ALOGW("Warning: tracked references remain post-initialization");
        dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
    

    /* general debugging setup */
    if (!dvmDebuggerStartup()) 
        return "dvmDebuggerStartup failed";
    

    if (!dvmGcStartupClasses()) 
        return "dvmGcStartupClasses failed";
    

    /*
     * Init for either zygote mode or non-zygote mode.  The key difference
     * is that we don't start any additional threads in Zygote mode.
     */
    if (gDvm.zygote) 
        if (!initZygote()) 
            return "initZygote failed";
        
        dvmPostInitZygote();
     else 
        if (!dvmInitAfterZygote()) 
            return "dvmInitAfterZygote failed";
        
    


#ifndef NDEBUG
    if (!dvmTestHash())
        ALOGE("dvmTestHash FAILED");
    if (false /*noisy!*/ && !dvmTestIndirectRefTable())
        ALOGE("dvmTestIndirectRefTable FAILED");
#endif

    if (dvmCheckException(dvmThreadSelf())) 
        dvmLogExceptionStackTrace();
        return "Exception pending at end of VM initialization";
    

    scopedShutdown.disarm();
    return "";

dvmStartup调用了很多初始化接口,比如dvmClassStartup:

/*
 * Initialize the bootstrap class loader.
 *
 * Call this after the bootclasspath string has been finalized.
 */
bool dvmClassStartup()

    /* make this a requirement -- don't currently support dirs in path */
    if (strcmp(gDvm.bootClassPathStr, ".") == 0) 
        ALOGE("ERROR: must specify non-'.' bootclasspath");
        return false;
    

    gDvm.loadedClasses =
        dvmHashTableCreate(256, (HashFreeFunc) dvmFreeClassInnards);

    gDvm.pBootLoaderAlloc = dvmLinearAllocCreate(NULL);
    if (gDvm.pBootLoaderAlloc == NULL)
        return false;

    if (false) 
        linearAllocTests();
        exit(0);
    

    /*
     * Class serial number.  We start with a high value to make it distinct
     * in binary dumps (e.g. hprof).
     */
    gDvm.classSerialNumber = INITIAL_CLASS_SERIAL_NUMBER;

    /*
     * Set up the table we'll use for tracking initiating loaders for
     * early classes.
     * If it's NULL, we just fall back to the InitiatingLoaderList in the
     * ClassObject, so it's not fatal to fail this allocation.
     */
    gDvm.initiatingLoaderList = (InitiatingLoaderList*)
        calloc(ZYGOTE_CLASS_CUTOFF, sizeof(InitiatingLoaderList));

    /*
     * Create the initial classes. These are the first objects constructed
     * within the nascent VM.
     */
    if (!createInitialClasses()) 
        return false;
    

    /*
     * Process the bootstrap class path.  This means opening the specified
     * DEX or Jar files and possibly running them through the optimizer.
     */
    assert(gDvm.bootClassPath == NULL);
    processClassPath(gDvm.bootClassPathStr, true);

    if (gDvm.bootClassPath == NULL)
        return false;

    return true;

这个类初始化gDvm的bootClassPath,这样就能执行最早看到的ActivityThread的main函数。

具体的调用栈为:ActivityThread.main -> ActivityThread.attach ->  ActivityThread.bindApplication -> Activity.handleBindApplication.

到目前的分析,应用的ClassLoader还是BootClassPath,只包含了Java和Android的类,Apk自身的类是找不到的,会报ClassNotFound,接下来就是介绍ClassLoader的加载过程。

handleBindApplication会初始化ApplicationInfo对象,通getPackageInfo初始化LoadedApk,而LoadedApk则会创建这个apk对应的ClassLoader,这个ClassLoader是集成自BaseDexClassLoader,加载了apk的dex。 

ApplicationInfo instrApp = new ApplicationInfo();
instrApp.packageName = ii.packageName;
instrApp.sourceDir = ii.sourceDir;
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.dataDir = ii.dataDir;
instrApp.nativeLibraryDir = ii.nativeLibraryDir;
LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
    appContext.getClassLoader(), false, true);
ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try 
    java.lang.ClassLoader cl = instrContext.getClassLoader();
    mInstrumentation = (Instrumentation)
    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
 catch (Exception e) 
    throw new RuntimeException(
        "Unable to instantiate instrumentation + data.instrumentationName + ": " + e.toString(), e);

ContextImpl和Instrumentation的ClassLoader都已经初始化为apk对应的BaseDexClassLoader,在这之后的类加载,都会从这个ClassLoader对象里找。ClassLoader是个树状结构,查找规则是先从父节点查找,如果类已经加载,则直接返回加载好的Class<?>。
类的加载时机有两个,一个是new操作符创建对象的,一个是直接调用ClassLoader的loadClass的时候,new操作符的代码在dalvik解释器里,我们下一个专题会讲,最后会调用dvmResolveClass(dalvik/vm/oo/Resolve.cpp)来加载类。loadClass的实现如下:
libcore/libdvm/src/main/java/java/lang/ClassLoader.java

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException 
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) 
            try 
                clazz = parent.loadClass(className, false);
             catch (ClassNotFoundException e) 
                // Don't want to see this.
            

            if (clazz == null) 
                clazz = findClass(className);
            
        

        return clazz;

它首先看自己的parent是否已经加载过class了,加载了就返回,没有就调用BaseDexClassLoader的findClass。

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException 
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) 
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \\"" + name + "\\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) 
                cnfe.addSuppressed(t);
            
            throw cnfe;
        
        return c;

BaseDexClassLoader的findClass,实际是调用了DexPathList的findClass。

上面的整个过程就是Android的apk从启动,一直到ClassLoader被初始化完,之后就是走Android的Activity正常生命周期了。

下篇介绍一下Dalvik虚拟机的解析器的工作原理。


作者简介:

田力,网易彩票Android端创始人,小米视频创始人,现任roobo技术经理、视频云技术总监

欢迎关注微信公众号 磨剑石,定期推送技术心得以及源码分析等文章,谢谢


以上是关于深入理解Dalvik虚拟机- Android应用进程启动过程分析的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Android之Java虚拟机Dalvik

深入理解ART虚拟机—虚拟机的启动

深入理解Dalvik虚拟机- 解释器的运行机制

理解Android虚拟机体系结构

深入理解Dalvik虚拟机- 解释器的执行机制

深入理解Dalvik虚拟机- 解释器的运行机制