由于四大組件的啟動(dòng)都涉及到進(jìn)程的啟動(dòng),因此我們這章先講一下進(jìn)程啟動(dòng)流程,然后再講四大組件的啟動(dòng)流程。
基礎(chǔ)知識(shí)
Android應(yīng)用程序框架層創(chuàng)建的應(yīng)用程序進(jìn)程具有兩個(gè)特點(diǎn),一是進(jìn)程的入口函數(shù)是ActivityThread.main,二是進(jìn)程天然支持Binder進(jìn)程間通信機(jī)制;這兩個(gè)特點(diǎn)都是在進(jìn)程的初始化過程中實(shí)現(xiàn)的。(引用自老羅安卓之旅-Android應(yīng)用程序進(jìn)程啟動(dòng)過程的源代碼分析)
進(jìn)程按照重要性可以分為下面五類:
- 前臺(tái)進(jìn)程(Foreground process)
- 可見進(jìn)程(Visible process)
- 服務(wù)進(jìn)程(Service process)
- 后臺(tái)進(jìn)程(Background process)
- 空進(jìn)程(Empty process)
進(jìn)程啟動(dòng)流程
AMS(ActivityMagagerService)啟動(dòng)進(jìn)程是從其成員函數(shù)startProcessLocked開始調(diào)用Process.start方法開始的。我們先看一下進(jìn)程啟動(dòng)的時(shí)序圖:

1. Process.start方法:
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[] zygoteArgs) {
try {
// 請(qǐng)求Zygote進(jìn)程創(chuàng)建一個(gè)應(yīng)用進(jìn)程
return startViaZygote(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
throw new RuntimeException(
"Starting VM process through Zygote failed", ex);
}
}
注意:傳入的第一個(gè)參數(shù)是“android.app.ActivityThread”,這是進(jìn)程初始化要加載的類,這個(gè)類加載到進(jìn)程之后,就會(huì)把這個(gè)類的靜態(tài)成員方法main作為進(jìn)程的入口。然后調(diào)用startViaZygote方法。
2. startViaZygote方法:
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 abi,
String instructionSet,
String appDataDir,
String[] extraArgs)
throws ZygoteStartFailedEx {
synchronized (Process.class) {
ArrayList<String> argsForZygote = new ArrayList<String>();
// 保存要?jiǎng)?chuàng)建應(yīng)用程序進(jìn)程的啟動(dòng)參數(shù)到argsForZygote中
...
// 保存id到argsForZygote中
...
// 保存其他信息到argsForZygote中
...
// 請(qǐng)求Zygote進(jìn)程創(chuàng)建這個(gè)應(yīng)用進(jìn)程
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
}
這個(gè)方法主要是保存信息到argsForZygote中,然后調(diào)用openZygoteSocketIfNeeded,然后根據(jù)返回的值調(diào)用zygoteSendArgsAndGetResult方法,首先先看openZygoteSocketIfNeeded方法。
3. openZygoteSocketIfNeeded方法:
private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
// 通過調(diào)用ZygoteState.connect方法創(chuàng)建LocalSocket對(duì)象,以便將相應(yīng)參數(shù)傳入Zygote進(jìn)程
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 {
// 通過調(diào)用ZygoteState.connect方法創(chuàng)建LocalSocket對(duì)象,以便將相應(yīng)參數(shù)傳入Zygote進(jìn)程
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);
}
通過ZygoteState.connect放創(chuàng)建primaryZygoteState對(duì)象,如果第一次創(chuàng)建不成功,創(chuàng)建第二次。connect方法代碼如下:
4. ZygoteState.connect方法:
public static ZygoteState connect(String socketAddress) throws IOException {
DataInputStream zygoteInputStream = null;
BufferedWriter zygoteWriter = null;
// 這個(gè)Socket由ZygoteInit.java文件中的ZygoteInit類在runSelectLoopMode函數(shù)偵聽的。
final LocalSocket zygoteSocket = new LocalSocket();
try {
// 開始建立連接,在連接過程中,LocalSocket對(duì)象zygoteSocket會(huì)在/dev/socket目錄下找到
// 一個(gè)對(duì)應(yīng)的zygote文件,然后將它與自己綁定起來,這就相當(dāng)于與Zygote進(jìn)程中的名稱為“zygote”
// 的Socket建立了連接
zygoteSocket.connect(new LocalSocketAddress(socketAddress,
LocalSocketAddress.Namespace.RESERVED));
// 連接成功以后,首先獲取LocalSocket對(duì)象zygoteSocket的一個(gè)輸入流,并且保存在
// zygoteInputStream中,以便獲得Zygote進(jìn)程發(fā)送過來的通信數(shù)據(jù)
zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
// 又得到LocalSocket對(duì)象zygoteSocket的一個(gè)輸入流,并且保存在zygoteWriter中,以便
// 可以向Zygote進(jìn)程發(fā)送通信數(shù)據(jù)
zygoteWriter = new BufferedWriter(new OutputStreamWriter(
zygoteSocket.getOutputStream()), 256);
} catch (IOException ex) {
...
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
// 創(chuàng)建的LocalSocket對(duì)象zygoteSocket會(huì)保存在ZygoteState中
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
}
首先創(chuàng)建一個(gè)LocalSocket對(duì)象,這個(gè)LocalSocket對(duì)象是在ZygoteInit中的runSelectLoop函數(shù)進(jìn)行監(jiān)聽的。然后通過connect方法并且傳入連接地址連接該Socket,連接以后會(huì)獲取輸入流DataInputStream,以便獲得Zygote進(jìn)程發(fā)送過來的通信數(shù)據(jù),然后又獲取BufferedWriter輸入流,以便向Zygote進(jìn)程發(fā)送通信數(shù)據(jù)。最后會(huì)返回一個(gè)ZygoteState對(duì)象。下面我們看一下LocalSocket.connect方法。
5. LocalSocket.connect方法:
public void connect(LocalSocketAddress endpoint) throws IOException {
synchronized (this) {
if (isConnected) {
throw new IOException("already connected");
}
implCreateIfNeeded();
impl.connect(endpoint, 0);
isConnected = true;
isBound = true;
}
}
如果已經(jīng)連接,拋出異常,因?yàn)檫B接完成后,會(huì)關(guān)閉連接,使用時(shí)在打開連接。最后調(diào)用native方法連接socket,并且改變連接標(biāo)簽。
6. 回到第二步,調(diào)用完openZygoteSocketIfNeeded返回參數(shù)ZygoteState傳入到zygoteSendArgsAndGetResult方法中:
private static 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");
}
}
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();
// Zygote進(jìn)程接收到這些數(shù)據(jù)之后,就會(huì)創(chuàng)建一個(gè)新的應(yīng)用程序進(jìn)程,并且將這個(gè)新創(chuàng)建的應(yīng)用程序進(jìn)程
// 的PID返回給Activity管理服務(wù)AMS
// Should there be a timeout on this?
ProcessStartResult result = new 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);
}
}
這方法通過Socket流的方式將啟動(dòng)進(jìn)程的信息發(fā)送出去,從步驟4可知,這個(gè)Socket的監(jiān)聽是ZygoteInit類中的runSelectLoop方法,我們接著看這個(gè)方法。
7. ZygoteInit.runSelectLoop方法:
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
pollFds[i].events = (short) POLLIN;
}
try {
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
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 {
boolean done = peers.get(i).runOnce();
if (done) {
peers.remove(i);
fds.remove(i);
}
}
}
}
}
數(shù)據(jù)通過Socket發(fā)送以后,Zygote進(jìn)程接收到后會(huì)調(diào)用peers.get(i).runOnce()方法。這個(gè)peers.get(i)是獲取ZygoteConnection對(duì)象,表示一個(gè)Socket連接,然后調(diào)用它的runOnce方法。
8. ZygoteConnection.runOnce方法:
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
try {
// 獲得創(chuàng)建應(yīng)用程序進(jìn)程需要的啟動(dòng)參數(shù),并且保存在一個(gè)Arguments對(duì)象parsedArgs中
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
Log.w(TAG, "IOException on command socket " + ex.getMessage());
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 = -1;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
try {
parsedArgs = new Arguments(args);
if (parsedArgs.abiListQuery) {
return handleAbiListQuery();
}
...
// 調(diào)用forkAndSpecialize方法來創(chuàng)建這個(gè)應(yīng)用程序進(jìn)程,最終通過函數(shù)fork在當(dāng)前進(jìn)程中創(chuàng)建一個(gè)子進(jìn)程,
// 因此,當(dāng)它的返回值等于0時(shí),就表示是在新創(chuàng)建的子進(jìn)程中執(zhí)行的,這時(shí)候ZygoteConnection類就會(huì)調(diào)用
// 成員函數(shù)handleChildProc來啟動(dòng)這個(gè)子進(jìn)程
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
parsedArgs.appDataDir);
} catch (ErrnoException ex) {
...
} catch (IllegalArgumentException ex) {
...
} catch (ZygoteSecurityException ex) {
...
}
try {
if (pid == 0) {
...
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
return true;
} else {
...
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
} finally {
...
}
}
首先通過Zygote.forkAndSpecialize方法來創(chuàng)建一個(gè)新的進(jìn)程,并且返回其pid。因?yàn)槲覀冊(cè)诜中男陆ㄟM(jìn)程,因此我們只分析pid為0的情況,pid為0時(shí)會(huì)調(diào)用handleChildProc方法,
9. handleChildProc方法:
private void handleChildProc(Arguments parsedArgs,
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
...
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs);
} else {
// 初始化運(yùn)行庫以及啟動(dòng)一個(gè)Binder線程池
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
parsedArgs.remainingArgs, null /* classLoader */);
}
}
由于我們之前加入?yún)?shù)是沒有parsedArgs.invokeWith這個(gè)參數(shù),因此這里是null,因此會(huì)走else里面的代碼,執(zhí)行RuntimeInit.zygoteInit方法。
10. RuntimeInit.zygoteInit方法:
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
redirectLogStreams();
// 首先調(diào)用下面函數(shù)來設(shè)置新創(chuàng)建的應(yīng)用程序進(jìn)程的時(shí)區(qū)和鍵盤布局等通用信息
commonInit();
// 然后調(diào)用下面Native函數(shù)在新創(chuàng)建的應(yīng)用程序進(jìn)程中啟動(dòng)一個(gè)Binder線程池
nativeZygoteInit();
applicationInit(targetSdkVersion, argv, classLoader);
}
首先調(diào)用nativeZygoteInit函數(shù),這是一個(gè)native函數(shù),函數(shù)的目的是在新創(chuàng)建的應(yīng)用程序進(jìn)程中啟動(dòng)一個(gè)Binder線程池然后進(jìn)行進(jìn)程間通信。然后調(diào)用applicationInit函數(shù)
11. applicationInit函數(shù):
private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
...
// Remaining arguments are passed to the start class's static main
// 我們知道AMS指定了新創(chuàng)建的應(yīng)用程序進(jìn)程的入口函數(shù)為ActivityThread類的靜態(tài)成員函數(shù)main。實(shí)際是
// 通過下面方法進(jìn)入到ActivityThread類的靜態(tài)成員函數(shù)main中的
invokeStaticMain(args.startClass, args.startArgs, classLoader);
}
我們?cè)谇懊嬷v過args.startClass傳入進(jìn)來的是"android.app.ActivityThread",表示要執(zhí)行"android.app.ActivityThread"的main函數(shù)。
12. invokeStaticMain函數(shù):
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
Class<?> cl;
try {
cl = Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
...
}
Method m;
try {
// 獲取它的靜態(tài)成員函數(shù)main,并且保存在Method對(duì)象m中
m = cl.getMethod("main", new Class[] { String[].class });
} catch (NoSuchMethodException ex) {
...
} catch (SecurityException ex) {
...
}
...
/*
* 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.
* 將這個(gè)Method對(duì)象封裝在一個(gè)MethodAndArgsCaller對(duì)象中,并且將這個(gè)MethodAndArgsCaller對(duì)象作為
* 一個(gè)異常對(duì)象拋出來給當(dāng)前應(yīng)用程序處理
*/
throw new ZygoteInit.MethodAndArgsCaller(m, argv);
/**
* 引用自Android系統(tǒng)源代碼情景分析中的Android進(jìn)程啟動(dòng)分析一文
* 新創(chuàng)建的應(yīng)用程序進(jìn)程復(fù)制了Zygote進(jìn)程的地址空間,因此,當(dāng)前新創(chuàng)建的應(yīng)用程序進(jìn)程的調(diào)用棧與Zygote
* 進(jìn)程的調(diào)用堆棧是一致的。Zygote進(jìn)程最開始執(zhí)行的是應(yīng)用程序app_process的入口函數(shù)main,接著再調(diào)用
* ZygoteInit類的靜態(tài)成員函數(shù)main,最后進(jìn)入到ZygoteInit類的靜態(tài)成員函數(shù)runSelectLoopMode來循環(huán)
* 等待Activity管理服務(wù)AMS發(fā)送過來的創(chuàng)建新的應(yīng)用進(jìn)程的請(qǐng)求。當(dāng)Zygote進(jìn)程收到AMS發(fā)送過來的創(chuàng)建新的
* 應(yīng)用程序進(jìn)程的請(qǐng)求之后,它就會(huì)創(chuàng)建一個(gè)新的應(yīng)用程序進(jìn)程,并且讓這個(gè)新創(chuàng)建的應(yīng)用程序進(jìn)程沿著
* ZygoteInit類的靜態(tài)函數(shù)runSelectLoopModel一直執(zhí)行到RuntimeInit類的靜態(tài)成員函數(shù)
* invokeStaticMain。因此,當(dāng)RuntimeInit類的靜態(tài)成員函數(shù)invokeStaticMain拋出一個(gè)類型為
* MethodAndArgsCaller的常時(shí),系統(tǒng)就會(huì)沿著這個(gè)調(diào)用過程往后找到一個(gè)適合的代碼塊來捕獲它。
* 由于ZygoteInit函數(shù)main捕獲了類型為MethodAndArgsCaller的異常,因此,接下來它就會(huì)被調(diào)用,以便
* 可以處理這里拋出的一個(gè)MethodAndArgsCaller異常。因此,拋出這個(gè)異常后,會(huì)執(zhí)行ZygoteInit中main
* 函數(shù)中的catch來捕獲異常。
*
*/
}
這個(gè)就是通過類加載器加載ActivityThread,然后調(diào)用起main方法。然后拋出異常,通過ZygoteInit中main函數(shù)中的catch來捕獲異常。
13. ZygoteInit.main函數(shù):
public static void main(String argv[]) {
...
} catch (MethodAndArgsCaller caller) {
// 捕獲MethodAndArgsCaller異常以后會(huì)調(diào)用MethodAndArgsCaller的run函數(shù)
// ActivityThread.main
caller.run();
} catch (Throwable ex) {
...
}
}
通過步驟12可知拋出的異常是MethodAndArgsCaller異常,因此會(huì)執(zhí)行caller.run方法。
14. MethodAndArgsCaller.run:
/**
* 注釋來自Android系統(tǒng)源代碼情景分析
* 這里開始調(diào)用ActivityThread.main方法,為什么要繞這么遠(yuǎn)呢,前面提到,AMS請(qǐng)求Zygote進(jìn)程創(chuàng)建的應(yīng)用
* 程序進(jìn)程的入口函數(shù)為ActivityThread的main函數(shù),但是由于新創(chuàng)建的應(yīng)用程序進(jìn)程一開始就需要再內(nèi)部初始
* 化運(yùn)行時(shí)庫,以及啟動(dòng)Binder線程池,因此,ActivityThread的main函數(shù)被調(diào)用時(shí),新創(chuàng)建的應(yīng)用程序進(jìn)程
* 實(shí)際上已經(jīng)執(zhí)行了相當(dāng)多的代碼,為了使得西創(chuàng)建的應(yīng)用程序的進(jìn)程覺得它的入口函數(shù)就是ActivityThread類
* 的main函數(shù),系統(tǒng)就不能直接調(diào)用,而是拋出異?;氐絑ygoteInit的main函數(shù)中,然后間接調(diào)用它,這樣就
* 可以巧妙的利用Java語言的異常處理來清理它前面調(diào)用的堆棧了
*/
public void run() {
try {
// 調(diào)用ActivityThread.main
mMethod.invoke(null, new Object[]{mArgs});
} 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);
}
}
通過mMethod.invoke方法調(diào)用ActivityThread的main方法。
15. ActivityThread.mian方法:
/**
* 啟動(dòng)新的進(jìn)程時(shí)調(diào)用Process的start方法會(huì)最終調(diào)用改函數(shù)
* 啟動(dòng)新的進(jìn)程主要做了兩件事:
* 1.在進(jìn)程中創(chuàng)建了一個(gè)ActivityThread對(duì)象,并調(diào)用了它的成員函數(shù)attach向AMS發(fā)送一個(gè)啟動(dòng)完成的通知
* 2.調(diào)用Looper類的靜態(tài)成員函數(shù)prepareMainLooper創(chuàng)建一個(gè)消息循環(huán),并且在向AMS發(fā)送啟動(dòng)完成通知后,
* 使得當(dāng)前進(jìn)程進(jìn)入到這個(gè)消息循環(huán)中
*
* @param args
*/
public static void main(String[] args) {
...
// 創(chuàng)建looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
// 傳入false表示非系統(tǒng)進(jìn)程啟動(dòng)
thread.attach(false);
if (sMainThreadHandler == null) {
// 獲取主線程的Handler
sMainThreadHandler = thread.getHandler();
}
...
// 開始無限循環(huán)
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
這里主要是創(chuàng)建該線程的looper,然后創(chuàng)建ActivityThread對(duì)象,然后進(jìn)入消息循環(huán)。然后我們就可以啟動(dòng)Activity或者Service了。
注
原文地址:http://www.codemx.cn/2017/09/13/AndroidOS005-Process/
注:本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處,多謝。