当Android系统启动完成之后,我们安装的应用程序又是如何启动的呢?启动一个应用程序首先要保证该应用程序的进程已经被启动,本篇文章我们就基于Android8.1系统来分析应用程序进程启动过程。需要注意,是“应用程序进程启动过程”,不是“应用程序启动过程”。 要想启动一个应用程序,首先要保证这个应用程序所需要的应用程序进程已经启动。 AMS 在启动应用程序时会检查这个应用程序需要的应用程序进程是否存在,不存在就会请求Zygote 进程启动需要的应用程序进程。我们知道在 Zygote的Java 框架层中会创建 Server 端的 Socket ,这个 Socket 用来等待 AMS 请求 Zygote 来创建新的应用程序进程。Zygote 进程通过 fock 自身创建应用程序进程,这样应用程序进程就会获得 Zygote 进程在启动时创建的虚拟机实例。当然,在应用程序进程创建过程中除了获取虚拟机实例外,还创建了 Binder 线程池和消息循环,这样运行在应用进程中的应用程序就可以方便地使用 Binder 进行进程间通信以及处理消息了。(这里对Zygote进程不了解的可以先看”Android系统启动流程(2) —— 解析Zygote进程启动过程“这篇文章)。 应用程序进程创建过程的步骤分为两个部分,一部分是 AMS 发送启动应用程序进程请求,一部分是 Zygote 接收请求并创建应用程序进程。 AMS如果想要启动应用程序首先就需要向Zygote进程发送创建应用程序进程的请求,AMS会通过调用startProcessLocked方法向Zygote进程发送请求,代码如下所示: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java frameworks/base/core/java/android/os/Process.java 在注释1处我们可以看到在Process的start方法中只调用了ZygoteProcess的start方法, 其中ZygoteProcess类用于保持与Zygote进程的通信状态。ZygoteProcess的start方法代码如下所示: frameworks/base/core/java/android/os/ZygoteProcess.java 在注释1处又调用了startViaZygote方法,代码如下所示: frameworks/base/core/java/android/os/ZygoteProcess.java 在注释1处创建了字符串列表argsForZygote,然后将应用程序进程的启动参数保存在列表中。最后再注释2处调用zygoteSendArgsAndGetResult方法,然而zygoteSendArgsAndGetResult方法需要传入的第一个参数是调用openZygoteSocketIfNeeded方法的返回值,第二个参数就是保存了应用程序的启动参数的argsForZygote列表,接下来我们先分析zygoteSendArgsAndGetResult方法,代码如下所示: frameworks/base/core/java/android/os/ZygoteProcess.java frameworks/base/core/java/android/os/ZygoteProcess.java frameworks/base/core/java/com/android/internal/os/Zygotelnit.java frameworks/base/core/java/com/android/internal/os/ZygoteServe.java frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java 在注释1处调用ZygoteInit的zygoteInit方法,代码如下: frameworks/base/core/jni/AndroidRuntime.cpp 通过JNI的gMethods 数组,可以看出 nativeZygotelnit 方法对应的是 JNI 文件 AndroidRuntime.cpp的com_android_internal_os _zygotelnit_ nativeZ ygotelnit 函数,代码如下所示: frameworks/base/core/jni/AndroidRuntime.cpp 这里 gCurRuntime是AndroidRuntime 类型的指针,具体指向的是 AndroidRuntime的子类AppRuntime ,它在app_main.cpp 中定义,我们接着来查看 AppRuntime的onZygotelnit 方法,代码如下所示: frameworks/base/cmds/app_process/app_main .cpp 注释1处的代码用来启动一个Binder线程池,接下来分析ProcessState的startThreadPool方法,代码如下: 可以看到Binder线程为一个PoolThread 。在注释1处调 PoolThread的run函数来启动一个新的线程。下面来查 PoolThread 类做了什么,代码如下: frameworks/native/libs/binder/ProcessState.cpp 在注释1处调用findStaticMain方法,第一个参数agrs.startClass,它指的就是本文开头提到的参数”android.app.ActivityThread”。接下来分析findStaticMain方法,代码如下所示: frameworks/base/core/java/com/android/internal/os/Runtimelnit.java 可以看到在注释1通过反射获得了android.app.ActivityThread 类,接下来在注释2处获得了 ActivityThread的main 法,在注释3处将找到的 main 方法传入 MethodAndArgsCaller 类中,MethodAndArgsCaller 实现了Runnable接口,代码如下: frameworks/base/core/java/com/android/internal/os/Runtimelnit.java 在注释1处通过invoke调用ActivityThread的main方法,那么MethodAndArgsCaller 的run方法又是在哪里调用的呢?findStaticMain方法最终会将MethodAndArgsCaller方法返回给ZygoteInit的main方法中的callercan参数,代码如下所示: frameworks/base/core/java/com/android/internal/os/Zygotelnit.java 在注释1处,调用caller.run方法就会调用到MethodAndArgsCaller的run方法,然后通过invoke就进入到ActivityThread的main方法,这样应用程序进程就启动了。应用程序进程启动过程
1 应用程序进程简介
2 应用程序进程启动过程介绍
2.1 AMS发送启动应用程序进程的请求
private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) { ... try { try { final int userId = UserHandle.getUserId(app.uid); AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } // 获取要创建的应用程序进程的用户ID int uid = app.uid; // ... 1 int[] gids = null; int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; if (!app.isolated) { int[] permGids = null; ... /* * Add shared application and profile GIDs so applications can share some * resources like shared libraries and access user-wide resources */ // 对gid进程创建与赋值 ... 2 if (ArrayUtils.isEmpty(permGids)) { gids = new int[3]; } else { gids = new int[permGids.length + 3]; System.arraycopy(permGids, 0, gids, 3, permGids.length); } gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid)); gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid)); gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid)); } ... if (entryPoint == null) entryPoint = "android.app.ActivityThread"; // ... 3 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " + app.processName); checkTime(startTime, "startProcess: asking zygote to start proc"); ProcessStartResult startResult; if (hostingType.equals("webview_service")) { startResult = startWebView(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, entryPointArgs); } else { // 启动应用程序进程 startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, entryPointArgs); // ... 4 } ... } catch (RuntimeException e) { ... } }
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 abi, String instructionSet, String appDataDir, String invokeWith, String[] zygoteArgs) { return zygoteProcess.start(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, zygoteArgs); // ... 1 }
public final Process.ProcessStartResult start(final String processClass, final String niceName, int uid, int gid, int[] gids, int debugFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, String instructionSet, String appDataDir, String invokeWith, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, zygoteArgs); // ... 1 } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); throw new RuntimeException( "Starting VM process through Zygote failed", ex); } }
private Process.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 abi, String instructionSet, String appDataDir, String invokeWith, String[] extraArgs) throws ZygoteStartFailedEx { /** * ... 1 创建字符串列表argsForZygote保存应用进程的启动参数 */ ArrayList<String> argsForZygote = new ArrayList<String>(); // --runtime-args, --setuid=, --setgid=, // and --setgroups= must go first argsForZygote.add("--runtime-args"); 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_JDWP) != 0) { argsForZygote.add("--enable-jdwp"); } if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) { argsForZygote.add("--enable-checkjni"); } if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) { argsForZygote.add("--generate-debug-info"); } if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) { argsForZygote.add("--always-jit"); } if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) { argsForZygote.add("--native-debuggable"); } if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) { argsForZygote.add("--java-debuggable"); } if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { argsForZygote.add("--enable-assert"); } if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) { argsForZygote.add("--mount-external-default"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) { argsForZygote.add("--mount-external-read"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) { argsForZygote.add("--mount-external-write"); } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); // --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); } if (instructionSet != null) { argsForZygote.add("--instruction-set=" + instructionSet); } if (appDataDir != null) { argsForZygote.add("--app-data-dir=" + appDataDir); } if (invokeWith != null) { argsForZygote.add("--invoke-with"); argsForZygote.add(invokeWith); } argsForZygote.add(processClass); if (extraArgs != null) { for (String arg : extraArgs) { argsForZygote.add(arg); } } synchronized(mLock) { return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); // ... 2 } }
@GuardedBy("mLock") private static Process.ProcessStartResult zygoteSendArgsAndGetResult( ZygoteState zygoteState, ArrayList<String> args) throws ZygoteStartFailedEx { try { // Throw early if any of the arguments are malformed. This means we can // avoid writing a partial response to the zygote. int sz = args.size(); for (int i = 0; i < sz; i++) { if (args.get(i).indexOf('n') >= 0) { throw new ZygoteStartFailedEx("embedded newlines not allowed"); } } /** * See com.android.internal.os.SystemZygoteInit.readArgumentList() * Presently the wire format to the zygote process is: * a) a count of arguments (argc, in essence) * b) a number of newline-separated argument strings equal to count * * After the zygote process reads these it will write the pid of * the child or -1 on failure, followed by boolean to * indicate whether a wrapper process was used. */ final BufferedWriter writer = zygoteState.writer; final DataInputStream inputStream = zygoteState.inputStream; writer.write(Integer.toString(args.size())); writer.newLine(); for (int i = 0; i < sz; i++) { String arg = args.get(i); writer.write(arg); writer.newLine(); } writer.flush(); // Should there be a timeout on this? Process.ProcessStartResult result = new Process.ProcessStartResult(); // Always read the entire result from the input stream to avoid leaving // bytes in the stream for future process starts to accidentally stumble // upon. result.pid = inputStream.readInt(); result.usingWrapper = inputStream.readBoolean(); if (result.pid < 0) { throw new ZygoteStartFailedEx("fork() failed"); } return result; } catch (IOException ex) { zygoteState.close(); throw new ZygoteStartFailedEx(ex); } }
@GuardedBy("mLock") private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); if (primaryZygoteState == null || primaryZygoteState.isClosed()) { try { // 与Zygote进程建立Socket连接 primaryZygoteState = ZygoteState.connect(mSocket); // ... 1 } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); } } // 连接Zygote主模式返回的ZygoteState是否与启动应用程序进程所需要的ABI匹配 if (primaryZygoteState.matches(abi)) { // ... 2 return primaryZygoteState; } // The primary zygote didn't match. Try the secondary. // 如果不匹配,则尝试连接Zygote辅模式 if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { try { secondaryZygoteState = ZygoteState.connect(mSecondarySocket); // ... 3 } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); } } // 连接Zygote辅模式返回的ZygoteState是否与启动应用程序进程所需要的ABI匹配 if (secondaryZygoteState.matches(abi)) { // ... 4 return secondaryZygoteState; } throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); }
2 Zygote 接收请求并创建应用程序进程
public static void main(String argv[]) { ZygoteServer zygoteServer = new ZygoteServer() ... String socketName = "zygote"; ... try { ... // 创建一个Server端的Socket,socketName的值为"zygote" zygoteServer.registerServerSocket(socketName); // ... 1 // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. if (!enableLazyPreload) { bootTimingsTraceLog.traceBegin("ZygotePreload"); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); // 预加载类和资源 preload(bootTimingsTraceLog); // ... 2 EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); bootTimingsTraceLog.traceEnd(); // ZygotePreload } else { Zygote.resetNicePriority(); } ... if (startSystemServer) { // 启动SystemServer进程 Runnable r = forkSystemServer(abiList, socketName, zygoteServer); // {@code r == null} in the parent (zygote) process, and {@code r != null} in the // child (system_server) process. if (r != null) { r.run(); return; } } Log.i(TAG, "Accepting command socket connections"); // The select loop returns early in the child process after a fork and // loops forever in the zygote. // 等待AMS请求 caller = zygoteServer.runSelectLoop(abiList); // 4 } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; } finally { zygoteServer.closeServerSocket(); } // We're in the child process and have exited the select loop. Proceed to execute the // command. if (caller != null) { caller.run(); } }
Runnable runSelectLoop(String abiList) { ... // 无线循环等待AMS的请求 while (true) { ... for (int i = pollFds.length - 1; i >= 0; --i) { if ((pollFds[i].revents & POLLIN) == 0) { continue; } if (i == 0) { ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { try { ZygoteConnection connection = peers.get(i); final Runnable command = connection.processOneCommand(this); // ... 1 if (mIsForkChild) { // We're in the child. We should always have a command to run at this // stage if processOneCommand hasn't called "exec". if (command == null) { throw new IllegalStateException("command == null"); } return command; } else { // We're in the server - we should never have any commands to run. if (command != null) { throw new IllegalStateException("command != null"); } // We don't know whether the remote side of the socket was closed or // not until we attempt to read from it from processOneCommand. This shows up as // a regular POLLIN event in our regular processing loop. if (connection.isClosedByPeer()) { connection.closeSocket(); peers.remove(i); fds.remove(i); } } } catch (Exception e) { ... } } } } }
Runnable processOneCommand(ZygoteServer zygoteServer) { String args[]; Arguments parsedArgs = null; FileDescriptor[] descriptors; try { // 获取应用进程的启动参数 args = readArgumentList(); // ... 1 descriptors = mSocket.getAncillaryFileDescriptors(); } catch (IOException ex) { throw new IllegalStateException("IOException on command socket", ex); } ... parsedArgs = new Arguments(args); // ... 2 ... /** * ... 3 创建应用程序进程 */ pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet, parsedArgs.appDataDir); try { // 当前代码逻辑运行在子进程中 if (pid == 0) { // in child zygoteServer.setForkChild(); zygoteServer.closeServerSocket(); IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; // 处理应用程序进程 return handleChildProc(parsedArgs, descriptors, childPipeFd); // ... 4 } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. IoUtils.closeQuietly(childPipeFd); childPipeFd = null; handleParentProc(pid, descriptors, serverPipeFd); return null; } } finally { IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(serverPipeFd); } }
private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd) { ... if (parsedArgs.invokeWith != null) { WrapperInit.execApplication(parsedArgs.invokeWith, parsedArgs.niceName, parsedArgs.targetSdkVersion, VMRuntime.getCurrentInstructionSet(), pipeFd, parsedArgs.remainingArgs); // Should not get here. throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, null /* classLoader */); // ... 1 } }
public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit"); RuntimeInit.redirectLogStreams(); RuntimeInit.commonInit(); ZygoteInit.nativeZygoteInit(); // ... 1 return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); // ... 2 }
int register_com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env) { const JNINativeMethod methods[] = { { "nativeZygoteInit", "()V", (void*) com_android_internal_os_ZygoteInit_nativeZygoteInit }, }; return jniRegisterNativeMethods(env, "com/android/internal/os/ZygoteInit", methods, NELEM(methods)); }
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz) { gCurRuntime->onZygoteInit(); }
virtual void onZygoteInit() { sp<ProcessState> proc = ProcessState::self(); ALOGV("App process: starting thread pool.n"); proc->startThreadPool(); // ... 1 }
void ProcessState::startThreadPool() { AutoMutex _l(mLock); if (!mThreadPoolStarted) { // ... 1 mThreadPoolStarted = true; spawnPooledThread(true); // ... 2 } }
void ProcessState::spawnPooledThread(bool isMain) { if (mThreadPoolStarted) { String8 name = makeBinderThreadName(); ALOGV("Spawning new pooled thread, name=%sn", name.string()); sp<Thread> t = new PoolThread(isMain); t->run(name.string()); //...1 } }
class PoolThread : public Thread { public: explicit PoolThread(bool isMain) : mIsMain(isMain) { } protected: virtual bool threadLoop() { IPCThreadState::self()->joinThreadPool(mIsMain); // ... 1 return false; } const bool mIsMain; };
protected static Runnable applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) { // If the application calls System.exit(), terminate the process // immediately without running any shutdown hooks. It is not possible to // shutdown an Android application gracefully. Among other things, the // Android runtime shutdown hooks close the Binder driver, which can cause // leftover running threads to crash before the process actually exits. nativeSetExitWithoutCleanup(true); // We want to be fairly aggressive about heap utilization, to avoid // holding on to a lot of memory that isn't needed. VMRuntime.getRuntime().setTargetHeapUtilization(0.75f); VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); final Arguments args = new Arguments(argv); // The end of of the RuntimeInit event (see #zygoteInit). Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // Remaining arguments are passed to the start class's static main return findStaticMain(args.startClass, args.startArgs, classLoader); // ... 1 }
private static Runnable findStaticMain(String className, String[] argv, ClassLoader classLoader) { Class<?> cl; try { // 获得android.app.ActivityThread类 cl = Class.forName(className, true, classLoader); // ... 1 } catch (ClassNotFoundException ex) { throw new RuntimeException( "Missing class when invoking static main " + className, ex); } Method m; try { // 获得ActivityThread的main方法 m = cl.getMethod("main", new Class[] { String[].class }); } catch (NoSuchMethodException ex) { throw new RuntimeException( "Missing static main on " + className, ex); } catch (SecurityException ex) { throw new RuntimeException( "Problem getting static main on " + className, ex); } int modifiers = m.getModifiers(); if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) { throw new RuntimeException( "Main method is not public and static on " + className); } /* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */ return new MethodAndArgsCaller(m, argv); // ... 3 }
static class MethodAndArgsCaller implements Runnable { /** method to call */ private final Method mMethod; /** argument array */ private final String[] mArgs; public MethodAndArgsCaller(Method method, String[] args) { mMethod = method; mArgs = args; } public void run() { try { // 通过invoke调用ActivityThread的main方法 mMethod.invoke(null, new Object[] { mArgs }); // ... 1 } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(ex); } } } }
public static void main(String argv[]) { final Runnable caller; try { ... caller = zygoteServer.runSelectLoop(abiList); } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; } finally { zygoteServer.closeServerSocket(); } // We're in the child process and have exited the select loop. Proceed to execute the // command. if (caller != null) { caller.run(); // ... 1 } }
3. 消息循环创建过程
public static void main(String[] args) { ... // 创建主线程的Looper Looper.prepareMainLooper(); // ... 1 ActivityThread thread = new ActivityThread(); // ... 2 thread.attach(false); if (sMainThreadHandler == null) { // ... 3 // 创建主线程的H类 sMainThreadHandler = thread.getHandler(); // ... 4 } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // Looper开始工作 Looper.loop(); // ... 5 throw new RuntimeException("Main thread loop unexpectedly exited"); }
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算