Android Zygote解析
来源:互联网 发布:廖雪峰python视频下载 编辑:程序博客网 时间:2024/06/06 17:17
使用的版本:Android 2.3
Zygote是Android系统应用中一个相当重要的进程,它的主要功能就是执行Android应用程序。在Android系统中运行新的应用,如同卵子受精分裂一样,需要跟Zygote进程结合后才能执行。
Zygote进程运行时,会初始化Dalvik虚拟机,并启动之。Android应用程序由java编写,不能直接已本地进程的形态运行在linux上,只能运行在Dalvik虚拟机中。每个应用程序都运行在各自的虚拟机中,应用程序每次运行都要重新初始化并启动虚拟机,这个过程会耗费相当长的时间,是拖慢应用程序的原因之一。因此,在Android中,应用程序运行前,Zygote进程通过共享已运行的虚拟机的代码与内存信息,缩短应用程序运行所耗费的时间,并且,它会事先将应用程序要使用的Android Framework中的类与资源加载到内存中,并组织形成所用资源的链接信息。新运行的Android应用程序在使用所需资源是不必每次重新形成资源的链接信息,这会节省大量时间,提高程序运行速度。
init进程启动之后,Android的服务与应用程序都由Zygote进程启动运行。Android设备中运行的进程大致有Daemon进程以及在Dalvik虚拟机中运行的Android应用程序两大类,pid为1的进程是init进程,ppid为1的进程是init进程启动的Daemon进程,其中包含Zygote进程。
Zygote启动后,初始并运行Dalvik虚拟机,然后将需要的类与资源加载到内存中。然后调用fork()创建出Zygote的子进程,子进程动态加载并运行Android应用程序,运行的应用程序会使用Zygote已经初始化并启动运行的Dalvik虚拟机代码,因为是fork()创建出的子进程,故使用已加载到内存中的类与资源可以使速度大大加快。
这里要考虑到COW(Copy on Write)技术,就是在创建新进程后,新进程会共享父进程的内存空间,使用COW技术,可以不用复制父进程的内存空间,而是直接共享父进程的内存空间,当需要修改内存空间的信息时,才将相关的内存信息复制到自身的内存空间。因为Zygote创建新进程的时候调用fork()直接运行exec(),新进程的内存空间与父进程的内存空间信息基本不同,复制父进程的内存空间做法变的毫无意义,并且会增加新进程运行的系统开销。
·app_process运行ZygoteInit class
Zygote由java编写而成,不能直接由init进程启动运行,故必须先生成Dalvik虚拟机,再在Dalvik虚拟机上装载运行ZygoteInit类。
这里给出frameworks/base/cmds/app_process/app_main.cpp中的main函数,即app_process进程的main函数。
int main(int argc, const char* const argv[]){ // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; mArgLen = 0; for (int i=0; i<argc; i++) { mArgLen += strlen(argv[i]) + 1; } mArgLen--; AppRuntime runtime; const char *arg; const char *argv0; 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); // Next arg is parent directory if (i < argc) { runtime.mParentDir = argv[i++]; } // Next arg is startup classname or "--zygote" if (i < argc) { arg = argv[i++]; if (0 == strcmp("--zygote", arg)) { bool startSystemServer = (i < argc) ? strcmp(argv[i], "--start-system-server") == 0 : false; setArgv0(argv0, "zygote"); set_process_name("zygote"); runtime.start("com.android.internal.os.ZygoteInit", startSystemServer); } else { set_process_name(argv0); runtime.mClassName = arg; // Remainder of args get passed to startup class main() runtime.mArgC = argc-i; runtime.mArgV = argv+i; LOGV("App process is starting with pid=%d, class=%s.\n", getpid(), runtime.getClassName()); runtime.start(); } } else { LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); return 10; }}<span style="white-space:pre"></span>app_process进程会初始化一个AppRuntime对象,继承自AndroidRuntime类,AppRuntime对象会分析环境变量以及运行的参数,以此生成虚拟机选项。<div></div><div><span style="font-size:14px"><span style="white-space:pre"></span>app_process服务运行时,init.rc文件中的运行命令如下:</span></div><div><span style="font-size:14px"><span style="white-space:pre"></span>/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server</span></div><div><span style="font-size:14px"><span style="white-space:pre"></span>传递给虚拟机的参数保存到AppRuntime类的对象中,然后加载对象,调用对象的main方法。</span></div><div><span style="font-size:14px"><span style="white-space:pre"></span></span><pre code_snippet_id="369733" snippet_file_name="blog_20140530_2_240974" name="code" class="cpp">if (i < argc) { arg = argv[i++]; if (0 == strcmp("--zygote", arg)) { bool startSystemServer = (i < argc) ? strcmp(argv[i], "--start-system-server") == 0 : false; setArgv0(argv0, "zygote"); set_process_name("zygote"); runtime.start("com.android.internal.os.ZygoteInit", startSystemServer); } else { set_process_name(argv0); runtime.mClassName = arg; // Remainder of args get passed to startup class main() runtime.mArgC = argc-i; runtime.mArgV = argv+i; LOGV("App process is starting with pid=%d, class=%s.\n", getpid(), runtime.getClassName()); runtime.start(); } } else { LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); return 10; }这是app_process main函数生成runtime对象的代码片段,Runtime对象的start()函数要获取与虚拟机运行相关的各种系统属性和环境变量,通过property_get()函数改变全局属性。Runtime对象的start()函数通过调用JNI_CreateJavaVM()函数来创建并运行虚拟机。
下面给出AndroidRuntime.cpp的start()函数,在frameworks/base/core/jni目录中
void AndroidRuntime::start(const char* className, const bool startSystemServer){ LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n", className != NULL ? className : "(unknown)"); char* slashClassName = NULL; char* cp; JNIEnv* env; blockSigpipe(); /* * 'startSystemServer == true' means runtime is obslete and not run from * init.rc anymore, so we print out the boot start event here. */ if (startSystemServer) { /* track our progress through the boot sequence */ const int LOG_BOOT_PROGRESS_START = 3000; LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC))); } const char* rootDir = getenv("ANDROID_ROOT"); if (rootDir == NULL) { rootDir = "/system"; if (!hasDir("/system")) { LOG_FATAL("No root directory specified, and /android does not exist."); goto bail; } setenv("ANDROID_ROOT", rootDir, 1); } //const char* kernelHack = getenv("LD_ASSUME_KERNEL"); //LOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack); /* start the virtual machine */ if (startVm(&mJavaVM, &env) != 0) goto bail; /* * Register android functions. */ if (startReg(env) < 0) { LOGE("Unable to register all android natives\n"); goto bail; } /* * We want to call main() with a String array with arguments in it. * At present we only have one argument, the class name. Create an * array to hold it. */ jclass stringClass; jobjectArray strArray; jstring classNameStr; jstring startSystemServerStr; stringClass = env->FindClass("java/lang/String"); assert(stringClass != NULL); strArray = env->NewObjectArray(2, stringClass, NULL); assert(strArray != NULL); classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); env->SetObjectArrayElement(strArray, 0, classNameStr); startSystemServerStr = env->NewStringUTF(startSystemServer ? "true" : "false"); env->SetObjectArrayElement(strArray, 1, startSystemServerStr); /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ jclass startClass; jmethodID startMeth; slashClassName = strdup(className); for (cp = slashClassName; *cp != '\0'; cp++) if (*cp == '.') *cp = '/'; startClass = env->FindClass(slashClassName); if (startClass == NULL) { LOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { LOGE("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 } } LOGD("Shutting down VM\n"); if (mJavaVM->DetachCurrentThread() != JNI_OK) LOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) LOGW("Warning: VM did not shut down cleanly\n");bail: free(slashClassName);}
创建完VM之后,app_process会加载Zygote相关的类,其中上述的start()函数中,会加载ZygoteInit类
for (cp = slashClassName; *cp != '\0'; cp++) if (*cp == '.') *cp = '/'; startClass = env->FindClass(slashClassName); if (startClass == NULL) { LOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { LOGE("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 } }这是AndroidRuntime类start()函数加载ZygoteInit类的代码。
·ZygoteInit的功能
1.绑定套接字,接收新Android应用程序运行请求
2.加载Android Application Framework使用的类与资源
3.启动运行System Server
4.处理新Android应用程序运行请求
ZygoteInit类的源代码在frameworks\base\core\java\com\android\internal\os目录下的ZygoteInit.java文件中
下面是main方法:
public static void main(String argv[]) { try { VMRuntime.getRuntime().setMinimumHeapSize(5 * 1024 * 1024); // Start profiling the zygote initialization. SamplingProfilerIntegration.start(); <span style="background-color: rgb(255, 255, 255);"> <span style="color:#ffff33;"> </span><span style="color:#000099;">registerZygoteSocket();</span></span> EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); <span style="color:#3366ff;"> <span style="background-color: rgb(255, 255, 255);">preloadClasses(); //cacheRegisterMaps(); preloadResources();</span></span> EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); // Finish profiling the zygote initialization. SamplingProfilerIntegration.writeZygoteSnapshot(); // Do an initial gc to clean up after startup gc(); // If requested, start system server directly from Zygote if (argv.length != 2) { throw new RuntimeException(argv[0] + USAGE_STRING); } if (argv[1].equals("true")) { <span style="color:#000099;"> <span style="background-color: rgb(255, 255, 255);">startSystemServer();</span></span> } else if (!argv[1].equals("false")) { throw new RuntimeException(argv[0] + USAGE_STRING); } Log.i(TAG, "Accepting command socket connections"); if (ZYGOTE_FORK_MODE) { runForkMode(); } else { <span style="background-color: rgb(255, 255, 255);"><span style="color:#33ccff;"> runSelectLoopMode();</span></span> } closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }这四个方法分别:
1.绑定套接字UDS(Unix Domain Socket)
2.预加载类、平台资源(图像、XML类、信息、字符串等)
3.参数--start-system-server会调用startSystemServer()方法启动系统服务器,系统服务器用来运行一些主要的本地服务
4.监视UDS,收到新的Android应用程序生成请求,则进入处理循环
1.绑定/dev/socket/zygote套接字
Zygote生成的UDS套接字,从ActivityManager接收新Android应用程序的生成请求。init.rc文件中有生成该套接字的相关内容
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd
ZygoteInit的main方法会先调用registerZygoteSocket()方法,代码如下
private static void registerZygoteSocket() { if (sServerSocket == null) { int fileDesc; try { String env = System.getenv(ANDROID_SOCKET_ENV); fileDesc = Integer.parseInt(env); } catch (RuntimeException ex) { throw new RuntimeException( ANDROID_SOCKET_ENV + " unset or invalid", ex); } try { sServerSocket = new LocalServerSocket( createFileDescriptor(fileDesc)); } catch (IOException ex) { throw new RuntimeException( "Error binding to local socket '" + fileDesc + "'", ex); } } }
registerZygoteSocket()方法会创建一个LocalServerSocket类的对象,并将其与/dev/socket/zygote绑定在一起,接收生成新Android进程的信息,并在最后的循环语句中进行处理。
2.加载应用程序Framework中的类与平台资源
preloadClasses()方法的主要代码如下:
private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream( PRELOADED_CLASSES); if (is == null) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "."); } else { Log.i(TAG, "Preloading classes..."); long startTime = SystemClock.uptimeMillis(); // Drop root perms while running static initializers. setEffectiveGroup(UNPRIVILEGED_GID); setEffectiveUser(UNPRIVILEGED_UID); // Alter the target heap utilization. With explicit GCs this // is not likely to have any effect. float defaultUtilization = runtime.getTargetHeapUtilization(); runtime.setTargetHeapUtilization(0.8f); // Start with a clean slate. runtime.gcSoftReferences(); runtime.runFinalizationSync(); Debug.startAllocCounting(); try { BufferedReader br = new BufferedReader(new InputStreamReader(is), 256); int count = 0; String line; while ((line = br.readLine()) != null) { // Skip comments and blank lines. line = line.trim(); if (line.startsWith("#") || line.equals("")) { continue; } try { if (Config.LOGV) { Log.v(TAG, "Preloading " + line + "..."); } Class.forName(line); if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) { if (Config.LOGV) { Log.v(TAG, " GC at " + Debug.getGlobalAllocSize()); } runtime.gcSoftReferences(); runtime.runFinalizationSync(); Debug.resetGlobalAllocSize(); } count++; } catch (ClassNotFoundException e) { Log.w(TAG, "Class not found for preloading: " + line); } catch (Throwable t) { Log.e(TAG, "Error preloading " + line + ".", t); if (t instanceof Error) { throw (Error) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } throw new RuntimeException(t); } } Log.i(TAG, "...preloaded " + count + " classes in " + (SystemClock.uptimeMillis()-startTime) + "ms."); } catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { // Restore default. runtime.setTargetHeapUtilization(defaultUtilization); Debug.stopAllocCounting(); // Bring back root. We'll need it later. setEffectiveUser(ROOT_UID); setEffectiveGroup(ROOT_GID); } } }
代码主要通过获取一个输入流,以便获取preloaded-classes文件中记录的类,创建BufferedReader对象,并读取preloaded-classes文件的内容,调用Class.forName()方法,将读到的类动态的加载到内存中。
3.运行SystemServer
Zygote启动Dalvik虚拟机后,会再生成一个Dalvik虚拟机实例,以便运行名称为SystemServer的Java服务,SystemServer用于运行Audio Flinger与Surface Flinger本地服务。在运行完所需的本地服务之后,SystemServer开始运行Android Framework的服务,如ActivityManager、PackageManager等。
下面是startSystemServer()方法:
private static boolean startSystemServer() throws MethodAndArgsCaller, RuntimeException { /* Hardcoded command line to start the system server */ String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; ZygoteConnection.Arguments parsedArgs = null; int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); /* * Enable debugging of the system process if *either* the command line flags * indicate it should be debuggable or the ro.debuggable system property * is set to "1" */ int debugFlags = parsedArgs.debugFlags; if ("1".equals(SystemProperties.get("ro.debuggable"))) debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER; /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ if (pid == 0) { handleSystemServerProcess(parsedArgs); } return true; }
代码定义了一个字符串数组,保存SystemServer的启动参数,这些参数被硬编码进字符串数组,字符串数组中的最后一个参数用于指定SystemServer类。接着调用forkSystemServer()方法来创建新进程,并运行SystemServer,同时检查生成的SystemServer进程工作是否正常。SystemServer类的main()方法会加载名称为android_servers的本地库,然后继续调用init1()函数,init1()函数调用system_init()函数,启动Audio Flinger、Surface Flinger、MediaPlayerService、CameraService等本地服务。
extern "C" status_t system_init(){ LOGI("Entered system_init()"); sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); LOGI("ServiceManager: %p\n", sm.get()); sp<GrimReaper> grim = new GrimReaper(); sm->asBinder()->linkToDeath(grim, grim.get(), 0); char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsurfaceflinger", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { // Start the SurfaceFlinger SurfaceFlinger::instantiate(); } // Start the sensor service SensorService::instantiate(); // On the simulator, audioflinger et al don't get started the // same way as on the device, and we need to start them here if (!proc->supportsProcesses()) { // Start the AudioFlinger AudioFlinger::instantiate(); // Start the media playback service MediaPlayerService::instantiate(); // Start the camera service CameraService::instantiate(); // Start the audio policy service AudioPolicyService::instantiate(); } // And now start the Android runtime. We have to do this bit // of nastiness because the Android runtime initialization requires // some of the core system services to already be started. // All other servers should just start the Android runtime at // the beginning of their processes's main(), before calling // the init function. LOGI("System server: starting Android runtime.\n"); AndroidRuntime* runtime = AndroidRuntime::getRuntime(); LOGI("System server: starting Android services.\n"); runtime->callStatic("com/android/server/SystemServer", "init2"); // If running in our own process, just go into the thread // pool. Otherwise, call the initialization finished // func to let this process continue its initilization. if (proc->supportsProcesses()) { LOGI("System server: entering thread pool.\n"); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); LOGI("System server: exiting thread pool.\n"); } return NO_ERROR;}
本地服务注册完毕后调用SystemServer类的静态方法init2(),创建android.server.ServerThread线程,并启动它,从而运行Android Framework的主要服务。
public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); Thread thr = new ServerThread(); thr.setName("android.server.ServerThread"); thr.start(); }
4.运行新的Android应用程序
看一下ZygoteInit类的runSelectLoopMode()方法,frameworkds/base/core/java/com/android/internal/os
private static void runSelectLoopMode() throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList(); ArrayList<ZygoteConnection> peers = new ArrayList(); FileDescriptor[] fdArray = new FileDescriptor[4]; fds.add(sServerSocket.getFileDescriptor()); peers.add(null); int loopCount = GC_LOOP_COUNT; while (true) { int index; /* * Call gc() before we block in select(). * It's work that has to be done anyway, and it's better * to avoid making every child do it. It will also * madvise() any free memory as a side-effect. * * Don't call it every time, because walking the entire * heap is a lot of overhead to free a few hundred bytes. */ if (loopCount <= 0) { gc(); loopCount = GC_LOOP_COUNT; } else { loopCount--; } try { fdArray = fds.toArray(fdArray); index = selectReadable(fdArray); } catch (IOException ex) { throw new RuntimeException("Error in select()", ex); } if (index < 0) { throw new RuntimeException("Error in select()"); } else if (index == 0) { ZygoteConnection newPeer = acceptCommandPeer(); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { boolean done; done = peers.get(index).runOnce(); if (done) { peers.remove(index); fds.remove(index); } } } }该方法采用典型的异步处理方式。代码首先将套接字的描述符添加到描述符数组中,程序使用该描述符处理来自外部的连接请求。selectReadable()是一个jni本地方法的本地函数,用来监视参数传递过来的文件描述符数组,若描述符目录中存在相关事件,则返回其在数组中的索引。为了处理dev/socket/zygote套接字的连接请求,程序先创建出ZygoteConnection类的对象,检查请求连接一方的访问权限,处理ZygoteConnection对象的输入输出事件。被添加的套接字面束缚的输入输出事件在下一个循环中由selectReadable()方法检测。runOnce()方法用于处理新连接的输入输出套接字,并生成新的Android应用程序。
下面是runOnce()方法:
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller { String args[]; Arguments parsedArgs = null; FileDescriptor[] descriptors; try { args = readArgumentList(); descriptors = mSocket.getAncillaryFileDescriptors(); } catch (IOException ex) { Log.w(TAG, "IOException on command socket " + ex.getMessage()); closeSocket(); return true; } if (args == null) { // EOF reached. closeSocket(); return true; } /** the stderr of the most recent request, if avail */ PrintStream newStderr = null; if (descriptors != null && descriptors.length >= 3) { newStderr = new PrintStream( new FileOutputStream(descriptors[2])); } int pid; try { parsedArgs = new Arguments(args); applyUidSecurityPolicy(parsedArgs, peer); applyDebuggerSecurityPolicy(parsedArgs); applyRlimitSecurityPolicy(parsedArgs, peer); applyCapabilitiesSecurityPolicy(parsedArgs, peer); int[][] rlimits = null; if (parsedArgs.rlimits != null) { rlimits = parsedArgs.rlimits.toArray(intArray2d); } pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits); } catch (IllegalArgumentException ex) { logAndPrintError (newStderr, "Invalid zygote arguments", ex); pid = -1; } catch (ZygoteSecurityException ex) { logAndPrintError(newStderr, "Zygote security policy prevents request: ", ex); pid = -1; } if (pid == 0) { // in child handleChildProc(parsedArgs, descriptors, newStderr); // should never happen return true; } else { /* pid != 0 */ // in parent...pid of < 0 means failure return handleParentProc(pid, descriptors, parsedArgs); } }
这个方法读取请求信息,包含新创建进程的参数选项,分析请求信息中的字符串数组,为运行进程设置好各个选项,创建新进程,Zygote.forkAndSpecialize()方法接收上面分析好的参数,调用Zygote类的本地方法forkAndSpecialize(),然后调用本地方法fork(),创建新进程,并根据新创建的进程传递的选项,设置uid,gid,rlimit等。handleChildProc()函数用来加载新进程所需的类,并调用类的main()方法,Zygote返回新进程创建是否成功,若成功,则返回进程的pid,最后,请求完成后,断开连接,关闭套接字。
最后,从套接字描述符数组中删除套接字描述符,防止重复处理。Zygote重新返回到循环中,等待并处理新请求。
0 0
- Android Zygote解析
- Android情景分析之深入解析zygote
- Android情景分析之深入解析zygote
- Android Zygote启动流程源码解析
- Android Zygote启动流程源码解析
- Android Zygote
- Android zygote
- Android Zygote
- Android源码解析之(八)-->Zygote进程启动流程
- 三、Android情景分析之深入解析zygote
- Android源码解析之(八)-->Zygote进程启动流程
- Android系统启动流程(二)解析Zygote进程启动过程
- Android Framework学习(二)之Zygote进程启动解析
- Android源码基础解析之Zygote进程启动流程
- android 的Zygote 分析
- android 的Zygote 分析
- Android深入浅出之Zygote
- Android深入浅出之Zygote
- Android WebView 与HttpClient 共用本地cookie问题
- 加快冯就卡而后士大夫人体后熬过
- Java Socket实战之六 使用NIO包实现Socket通信
- Office 2013 双激活状态
- 树
- Android Zygote解析
- 使用了Ormlite这个jar包,混淆代码就崩溃问题总结
- win7修改hosts时提示没有权限
- VMware下Linux的可以上网啦
- 信号
- HDU 4605 Magic Ball Game (在线主席树|| 离线 线段树)
- OpenCV_连通区域分析(Connected Component Analysis-Labeling)
- ZOJ-1745
- 数据结构之顺序栈