關於作者
郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。
文章目錄
- 一 程式的建立與啟動流程
- 二 程式的優先順序
- 三 程式的排程流程
Android系統的啟動流程如下圖(點選檢視大圖)所示:
Loader層
- 當手機處於關機狀態時,長按電源鍵開機,引導晶片開始從固化在Boot ROM裡的預設程式碼開始執行,然後載入載入程式Boot Loader到RAM。
- Boot Loader被載入到RAM之後開始執行,該程式主要完成檢查RAM,初始化硬體引數等功能。
Kernel層
- 載入程式之後進入Android核心層,先啟動swapper程式(idle程式),該程式用來初始化程式管理、記憶體管理、載入Display、Camera Driver、Binder Driver等相關工作。
- swapper程式程式之後再啟動kthreadd程式,該程式會建立核心工作執行緒kworkder、軟中斷執行緒ksoftirqd、thernal等核心守護程式,kthreadd程式是所有核心程式的鼻祖。
Native層
- 接著會啟動init程式,init程式是所有使用者程式的鼻祖,它會接著孵化出ueventd、logd、healthd、installd、adbd、lmkd等使用者守護程式,啟動ServiceManager來管理系統 服務,啟動Bootnaim開機動畫。
- init程式通過解析init.rc檔案fork生成Zygote程式,該程式是Android系統第一個Java程式,它是所有Java程式父程式,該程式主要完成了載入ZygoteInit類,註冊Zygote Socket 服務套接字;載入虛擬機器;預載入Class;預載入Resources。
Framework層
-
init程式接著fork生成Media Server程式,該程式負責啟動和管理整個C++ Framwork(包含AudioFlinger、Camera Service等服務)。
-
Zygote程式接著會fork生成System Server程式,該程式負責啟動和管理整個Java Framwork(包含ActivityManagerService、WindowManagerService等服務)。
App層
Zygote程式孵化出的第一個應用程式是Launcher程式(桌面),它還會孵化出Browser程式(瀏覽器)、Phone程式(電話)等。我們每個建立的應用都是一個單獨的程式。
通過上述流程的分析,想必讀者已經對Android的整個程式模型有了大致的理解。作為一個應用開發者我們往往更為關注Framework層和App層裡程式的建立與管理相關原理,我們來 一一分析。
一 程式的建立與啟動流程
在正式介紹程式之前,我們來思考一個問題,何為程式,程式的本質是什麼??
我們知道,程式碼是靜態的,有程式碼和資源組成的系統要想執行起來就需要一種動態的存在,程式就是程式的動態執行過程。何為程式? 程式就是處理執行狀態的程式碼以及相關資源的集合,包括程式碼端段、檔案、訊號、CPU狀態、記憶體地址空間等。
程式使用task_struct結構體來描述,如下所示:
- 程式碼段:編譯後形成的一些指令
- 資料段:程式執行時需要的資料
- 只讀資料段:常量
- 已初始化資料段:全域性變數,靜態變數
- 未初始化資料段(bss):未初始化的全域性變數和靜態變數
- 堆疊段:程式執行時動態分配的一些記憶體
- PCB:程式資訊,狀態標識等
關於程式的更多詳細資訊,讀者可以去翻閱Linux相關書籍,這裡只是給讀者帶來一種整體上的理解,我們的重心還是放在程式再Android平臺上的應用。
在文章開篇的時候,我們提到了系統中執行的各種程式,那麼這些程式如何被建立呢??
我們先來看看我們最熟悉的應用程式是如何被建立的,前面我們已經說來每一個應用都執行在一個單獨的程式裡,當ActivityManagerService去啟動四大元件時, 如果發現這個元件所在的程式沒有啟動,就會去建立一個新的程式,啟動程式的時機我們在分析四大元件的啟動流程的時候也有講過,這裡再總結一下:
- Activity ActivityStackSupervisor.startSpecificActivityLocked()
- Service ActiveServices.bringUpServiceLocked()
- ContentProvider ActivityManagerService.getContentProviderImpl() = Broadcast BroadcastQueue.processNextBroadcast()
這個新程式就是zygote程式通過複製自身來建立的,新程式在啟動的過程中還會建立一個Binder執行緒池(用來做程式通訊)和一個訊息迴圈(用來做執行緒通訊) 整個流程如下圖所示:
- 當我們點選應用圖示啟動應用時或者在應用內啟動一個帶有process標籤的Activity時,都會觸發建立新程式的請求,這種請求會先通過Binder 傳送給system_server程式,也即是傳送給ActivityManagerService進行處理。
- system_server程式會呼叫Process.start()方法,會先收集uid、gid等引數,然後通過Socket方式傳送給Zygote程式,請求建立新程式。
- Zygote程式接收到建立新程式的請求後,呼叫ZygoteInit.main()方法進行runSelectLoop()迴圈體內,當有客戶端連線時執行ZygoteConnection.runOnce() 方法,最後fork生成新的應用程式。
- 新建立的程式會呼叫handleChildProc()方法,最後呼叫我們非常熟悉的ActivityThread.main()方法。
注:整個流程會涉及Binder和Socket兩種程式通訊方式,這個我們後續會有專門的文章單獨分析,這個就不再展開。
整個流程大致就是這樣,我們接著來看看具體的程式碼實現,先來看一張程式啟動序列圖:
從第一步到第三步主要是收集整理uid、gid、groups、target-sdk、nice-name等一系列的引數,為後續啟動新程式做準備。然後呼叫openZygoteSocketIfNeeded()方法 開啟Socket通訊,向zygote程式發出建立新程式的請求。
注:第二步中的Process.start()方法是個阻塞操作,它會一直等待程式建立完畢,並返回pid才會完成該方法。
我們來重點關注幾個關鍵的函式。
1.1 Process.openZygoteSocketIfNeeded(String abi)
關於Process類與Zygote程式的通訊是如何進行的呢??
Process的靜態內部類ZygoteState有個成員變數LocalSocket物件,它會與ZygoteInit類的成員變數LocalServerSocket物件建立連線,如下所示:
客戶端
public static class ZygoteState {
final LocalSocket socket;
}
複製程式碼
服務端
public class ZygoteInit {
//該Socket與/dev/socket/zygote檔案繫結在一起
private static LocalServerSocket sServerSocket;
}
複製程式碼
我們來具體看看程式碼裡的實現。
public static class ZygoteState {
public static ZygoteState connect(String socketAddress) throws IOException {
DataInputStream zygoteInputStream = null;
BufferedWriter zygoteWriter = null;
//建立LocalSocket物件
final LocalSocket zygoteSocket = new LocalSocket();
try {
//將LocalSocket與LocalServerSocket建立連線,建立連線的過程就是
//LocalSocket物件在/dev/socket目錄下查詢一個名稱為"zygote"的檔案
//然後將自己與其繫結起來,這樣就建立了連線。
zygoteSocket.connect(new LocalSocketAddress(socketAddress,
LocalSocketAddress.Namespace.RESERVED));
//建立LocalSocket的輸入流,以便可以接收Zygote程式傳送過來的資料
zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
//建立LocalSocket的輸出流,以便可以向Zygote程式傳送資料。
zygoteWriter = new BufferedWriter(new OutputStreamWriter(
zygoteSocket.getOutputStream()), 256);
} catch (IOException ex) {
try {
zygoteSocket.close();
} catch (IOException ignore) {
}
throw ex;
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
}
}
複製程式碼
建立Socket連線的流程很明朗了,如下所示:
- 建立LocalSocket物件。
- 將LocalSocket與LocalServerSocket建立連線,建立連線的過程就是LocalSocket物件在/dev/socket目錄下查詢一個名稱為"zygote"的檔案,然後將自己與其繫結起來,這樣就建立了連線。
- 建立LocalSocket的輸入流,以便可以接收Zygote程式傳送過來的資料。
- 建立LocalSocket的輸出流,以便可以向Zygote程式傳送資料。
1.2 ZygoteInit.main(String argv[])
ZygoteInit是Zygote程式的啟動類,該類會預載入一些類,然後便開啟一個迴圈,等待通過Socket發過來的建立新程式的命令,fork出新的 子程式。
ZygoteInit的入口函式就是main()方法,如下所示:
public class ZygoteInit {
public static void main(String argv[]) {
// Mark zygote start. This ensures that thread creation will throw
// an error.
ZygoteHooks.startZygoteNoThreadCreation();
try {
//...
registerZygoteSocket(socketName);
//...
//開啟迴圈
runSelectLoop(abiList);
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (Throwable ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}
// 開啟一個選擇迴圈,接收通過Socket發過來的命令,建立新執行緒
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
//sServerSocket指的是Socket通訊的服務端,在fds中的索引為0
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 {
//處理輪詢狀態,當pollFds有時間到來時則往下執行,否則阻塞在這裡。
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
for (int i = pollFds.length - 1; i >= 0; --i) {
//採用IO多路複用機制,當接收到客戶端發出的連線請求時或者資料處理請求到來時則
//往下執行,否則進入continue跳出本次迴圈。
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
//索引為0,即為sServerSocket,表示接收到客戶端發來的連線請求。
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
}
//索引不為0,表示通過Socket接收來自對端的資料,並執行相應的操作。
else {
boolean done = peers.get(i).runOnce();
//處理完成後移除相應的檔案描述符。
if (done) {
peers.remove(i);
fds.remove(i);
}
}
}
}
}
}
複製程式碼
可以發現ZygoteInit在其入口函式main()方法裡呼叫runSelectLoop()開啟了迴圈,接收Socket發來的請求。請求分為兩種:
- 連線請求
- 資料請求
沒有連線請求時Zygote程式會進入休眠狀態,當有連線請求到來時,Zygote程式會被喚醒,呼叫acceptCommadPeer()方法建立Socket通道ZygoteConnection
private static ZygoteConnection acceptCommandPeer(String abiList) {
try {
return new ZygoteConnection(sServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
複製程式碼
然後呼叫runOnce()方法讀取連線請求裡的資料,然後建立新程式。
此外,連線的過程中服務端接受的到客戶端的connect()操作會執行accpet()操作,建立連線手,客戶端通過write()寫資料,服務端通過read()讀資料。
1.3 ZygoteConnection.runOnce()
class ZygoteConnection {
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;
}
//... 引數處理
try {
//... 引數處理
//呼叫Zygote.forkAndSpecialize(來fork出新程式
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) {
logAndPrintError(newStderr, "Exception creating pipe", ex);
} catch (IllegalArgumentException ex) {
logAndPrintError(newStderr, "Invalid zygote arguments", ex);
} catch (ZygoteSecurityException ex) {
logAndPrintError(newStderr,
"Zygote security policy prevents request: ", ex);
}
try {
//pid == 0時表示當前是在新建立的子程式重磅執行
if (pid == 0) {
// in child
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
// should never get here, the child is expected to either
// throw ZygoteInit.MethodAndArgsCaller or exec().
return true;
}
// pid < 0表示建立新程式失敗,pid > 0 表示當前是在父程式中執行
else {
// in parent...pid of < 0 means failure
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
} finally {
IoUtils.closeQuietly(childPipeFd);
IoUtils.closeQuietly(serverPipeFd);
}
}
}
複製程式碼
該方法主要用來讀取程式啟動引數,然後呼叫Zygote.forkAndSpecialize()方法fork出新程式,該方法是建立新程式的核心方法,它主要會陸續呼叫三個 方法來完成工作:
- preFork():先停止Zygote程式的四個Daemon子執行緒的執行以及初始化GC堆。這四個Daemon子執行緒分別為:Java堆記憶體管理現場、堆線下引用佇列執行緒、析構執行緒與監控執行緒。
- nativeForkAndSpecialize():呼叫Linux系統函式fork()建立新程式,建立Java堆處理的執行緒池,重置GC效能資料,設定程式的訊號處理函式,啟動JDWP執行緒。
- postForkCommon():啟動之前停止的Zygote程式的四個Daemon子執行緒。
上面的方法都完成會後,新程式會建立完成,並返回pid,接著就呼叫handleChildProc()來啟動新程式。handleChildProc()方法會接著呼叫RuntimeInit.zygoteInit()來 完成新程式的啟動。
1.4 RuntimeInit.zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
這個就是個關鍵的方法了,它主要用來建立一些執行時環境,我們來看一看。
public class RuntimeInit {
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();
//建立應用程式的時區和鍵盤等通用資訊
commonInit();
//在應用程式中建立一個Binder執行緒池
nativeZygoteInit();
//建立應用資訊
applicationInit(targetSdkVersion, argv, classLoader);
}
}
複製程式碼
該方法主要完成三件事:
- 呼叫commonInit()方法建立應用程式的時區和鍵盤等通用資訊。
- 呼叫nativeZygoteInit()方法在應用程式中建立一個Binder執行緒池。
- 呼叫applicationInit(targetSdkVersion, argv, classLoader)方法建立應用資訊。
Binder執行緒池我們後續的文章會分析,我們重點來看看applicationInit(targetSdkVersion, argv, classLoader)方法的實現,它主要用來完成應用的建立。
該方法裡的argv引數指的就是ActivityThread,該方法會呼叫invokeStaticMain()通過反射的方式呼叫ActivityThread類的main()方法。如下所示:
public class RuntimeInit {
private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
//...
// Remaining arguments are passed to the start class's static main
invokeStaticMain(args.startClass, args.startArgs, classLoader);
}
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
Class<?> cl;
//通過反射呼叫ActivityThread類的main()方法
try {
cl = Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
"Missing class when invoking static main " + className,
ex);
}
Method m;
try {
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);
}
//...
}
}
複製程式碼
走到ActivityThread類的main()方法,我們就很熟悉了,我們知道在main()方法裡,會建立主執行緒Looper,並開啟訊息迴圈,如下所示:
public final class ActivityThread {
public static void main(String[] args) {
//...
Environment.initForCurrentUser();
//...
Process.setArgV0("<pre-initialized>");
//建立主執行緒looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
//attach到系統程式
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//主執行緒進入迴圈狀態
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
複製程式碼
前面我們從Process.start()開始講起,分析了應用程式的建立及啟動流程,既然有啟動就會有結束,接下來我們從 Process.killProcess()開始講起,繼續分析程式的結束流程。
二 程式的優先順序
程式按照優先順序大小不同又可以分為實時程式與普通程式。
prio值越小表示程式優先順序越高,
- 靜態優先順序:優先順序不會隨時間改變,核心也不會修改,只能通過系統呼叫改變nice值,優先順序對映公式為:static_prio = MAX_RT_PRIO + nice + 20,其中MAX_RT_PRIO = 100,那麼取值區間為[100, 139];對應普通程式;
- 實時優先順序:取值區間為[0, MAX_RT_PRIO -1],其中MAX_RT_PRIO = 100,那麼取值區間為[0, 99];對應實時程式;
- 懂愛優先順序:排程程式通過增加或者減少程式優先順序,來達到獎勵IO消耗型或按照懲罰CPU消耗型的程式的效果。區間範圍[0, MX_PRIO-1],其中MX_PRIO = 140,那麼取值區間為[0,139];
三 程式排程流程
程式的排程在Process類裡完成。
3.1 優先順序排程
優先順序排程方法
setThreadPriority(int tid, int priority)
複製程式碼
程式的優先順序以及對應的nice值如下所示:
- THREAD_PRIORITY_LOWEST 19 最低優先順序
- THREAD_PRIORITY_BACKGROUND 10 後臺
- THREAD_PRIORITY_LESS_FAVORABLE 1 比預設略低
- THREAD_PRIORITY_DEFAULT 0 預設
- THREAD_PRIORITY_MORE_FAVORABLE -1 比預設略高
- THREAD_PRIORITY_FOREGROUND -2 前臺
- THREAD_PRIORITY_DISPLAY -4 顯示相關
- THREAD_PRIORITY_URGENT_DISPLAY -8 顯示(更為重要),input事件
- THREAD_PRIORITY_AUDIO -16 音訊相關
- THREAD_PRIORITY_URGENT_AUDIO -19 音訊(更為重要)
3.2 組優先順序排程
程式組優先順序排程方法
setProcessGroup(int pid, int group)
setThreadGroup(int tid, int group)
複製程式碼
組優先順序及對應取值
- THREAD_GROUP_DEFAULT -1 僅用於setProcessGroup,將優先順序<=10的程式提升到-2
- THREAD_GROUP_BG_NONINTERACTIVE 0 CPU分時的時長縮短
- THREAD_GROUP_FOREGROUND 1 CPU分時的時長正常
- THREAD_GROUP_SYSTEM 2 系統執行緒組
- THREAD_GROUP_AUDIO_APP 3 應用程式音訊
- THREAD_GROUP_AUDIO_SYS 4 系統程式音訊
3.3 排程策略
排程策略設定方法
setThreadScheduler(int tid, int policy, int priority)
複製程式碼
- SCHED_OTHER 預設 標準round-robin分時共享策略
- SCHED_BATCH 批處理排程 針對具有batch風格(批處理)程式的排程策略
- SCHED_IDLE 空閒排程 針對優先順序非常低的適合在後臺執行的程式
- SCHED_FIFO 先進先出 實時排程策略,android暫未實現
- SCHED_RR 迴圈排程 實時排程策略,android暫未實現
3.4 程式adj排程
另外除了這些基本的排程策略,Android系統還定義了兩個和程式相關的狀態值,一個就是定義在ProcessList.java裡的adj值,另一個 是定義在ActivityManager.java裡的procState值。
定義在ProcessList.java檔案,oom_adj劃分為16級,從-17到16之間取值。
- UNKNOWN_ADJ 16 一般指將要會快取程式,無法獲取確定值
- CACHED_APP_MAX_ADJ 15 不可見程式的adj最大值 1
- CACHED_APP_MIN_ADJ 9 不可見程式的adj最小值 2
- SERVICE_B_AD 8 B List中的Service(較老的、使用可能性更小)
- PREVIOUS_APP_ADJ 7 上一個App的程式(往往通過按返回鍵)
- HOME_APP_ADJ 6 Home程式
- SERVICE_ADJ 5 服務程式(Service process)
- HEAVY_WEIGHT_APP_ADJ 4 後臺的重量級程式,system/rootdir/init.rc檔案中設定
- BACKUP_APP_ADJ 3 備份程式 3
- PERCEPTIBLE_APP_ADJ 2 可感知程式,比如後臺音樂播放 4
- VISIBLE_APP_ADJ 1 可見程式(Visible process) 5
- FOREGROUND_APP_ADJ 0 前臺程式(Foreground process) 6
- PERSISTENT_SERVICE_ADJ -11 關聯著系統或persistent程式
- PERSISTENT_PROC_ADJ -12 系統persistent程式,比如telephony
- SYSTEM_ADJ -16 系統程式
- NATIVE_ADJ -17 native程式(不被系統管理)
更新程式adj值的方法定義在ActivityManagerService中,分別為:
- updateOomAdjLocked:更新adj,當目標程式為空,或者被殺則返回false;否則返回true;
- computeOomAdjLocked:計算adj,返回計算後RawAdj值;
- applyOomAdjLocked:應用adj,當需要殺掉目標程式則返回false;否則返回true。
那麼程式的adj值什麼時候會被更新呢??
Activity
- ActivityManagerService.realStartActivityLocked: 啟動Activity
- ActivityStack.resumeTopActivityInnerLocked: 恢復棧頂Activity
- ActivityStack.finishCurrentActivityLocked: 結束當前Activity
- ActivityStack.destroyActivityLocked: 摧毀當前Activity
Service
- ActiveServices.realStartServiceLocked: 啟動服務
- ActiveServices.bindServiceLocked: 繫結服務(只更新當前app)
- ActiveServices.unbindServiceLocked: 解綁服務 (只更新當前app)
- ActiveServices.bringDownServiceLocked: 結束服務 (只更新當前app)
- ActiveServices.sendServiceArgsLocked: 在bringup或則cleanup服務過程呼叫 (只更新當前app)
BroadcastReceiver
- BroadcastQueue.processNextBroadcast: 處理下一個廣播
- BroadcastQueue.processCurBroadcastLocked: 處理當前廣播
- BroadcastQueue.deliverToRegisteredReceiverLocked: 分發已註冊的廣播 (只更新當前app)
ContentProvider
- ActivityManagerService.removeContentProvider: 移除provider
- ActivityManagerService.publishContentProviders: 釋出provider (只更新當前app)
- ActivityManagerService.getContentProviderImpl: 獲取provider (只更新當前app)
另外,Lowmemorykiller也會根據當前的記憶體情況逐級進行程式釋放,一共有六個級別(上面加粗的部分):
- CACHED_APP_MAX_ADJ
- CACHED_APP_MIN_ADJ
- BACKUP_APP_ADJ
- PERCEPTIBLE_APP_ADJ
- VISIBLE_APP_ADJ
- FOREGROUND_APP_ADJ
定義在ActivityManager.java檔案,process_state劃分18類,從-1到16之間取值
- PROCESS_STATE_CACHED_EMPTY 16 程式處於cached狀態,且為空程式
- PROCESS_STATE_CACHED_ACTIVITY_CLIENT 15 程式處於cached狀態,且為另一個cached程式(內含Activity)的client程式
- PROCESS_STATE_CACHED_ACTIVITY 14 程式處於cached狀態,且內含Activity
- PROCESS_STATE_LAST_ACTIVITY 13 後臺程式,且擁有上一次顯示的Activity
- PROCESS_STATE_HOME 12 後臺程式,且擁有home Activity
- PROCESS_STATE_RECEIVER 11 後臺程式,且正在執行receiver
- PROCESS_STATE_SERVICE 10 後臺程式,且正在執行service
- PROCESS_STATE_HEAVY_WEIGHT 9 後臺程式,但無法執行restore,因此儘量避免kill該程式
- PROCESS_STATE_BACKUP 8 後臺程式,正在執行backup/restore操作
- PROCESS_STATE_IMPORTANT_BACKGROUND 7 對使用者很重要的程式,使用者不可感知其存在
- PROCESS_STATE_IMPORTANT_FOREGROUND 6 對使用者很重要的程式,使用者可感知其存在
- PROCESS_STATE_TOP_SLEEPING 5 與PROCESS_STATE_TOP一樣,但此時裝置正處於休眠狀態
- PROCESS_STATE_FOREGROUND_SERVICE 4 擁有給一個前臺Service
- PROCESS_STATE_BOUND_FOREGROUND_SERVICE 3 擁有給一個前臺Service,且由系統繫結
- PROCESS_STATE_TOP 2 擁有當前使用者可見的top Activity
- PROCESS_STATE_PERSISTENT_UI 1 persistent系統程式,並正在執行UI操作
- PROCESS_STATE_PERSISTENT 0 persistent系統程式
- PROCESS_STATE_NONEXISTENT -1 不存在的程式
根據上面說描述的adj值和state值,我們又可以按照重要性程度的不同,將程式劃分為五級:
前臺程式
使用者當前操作所必需的程式。如果一個程式滿足以下任一條件,即視為前臺程式:
- 託管使用者正在互動的 Activity(已呼叫 Activity 的 onResume() 方法)
- 託管某個 Service,後者繫結到使用者正在互動的 Activity
- 託管正在“前臺”執行的 Service(服務已呼叫 startForeground())
- 託管正執行一個生命週期回撥的 Service(onCreate()、onStart() 或 onDestroy())
- 託管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前臺程式都為數不多。只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們。 此時,裝置往往已達到記憶體分頁狀態,因此需要終止一些前臺程式來確保使用者介面正常響應。
可見程式
沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程式。 如果一個程式滿足以下任一條件,即視為可見程式:
- 託管不在前臺、但仍對使用者可見的 Activity(已呼叫其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話方塊,允許在其後顯示上一 Activity,則有可能會發生這種情況。
- 託管繫結到可見(或前臺)Activity 的 Service。
可見程式被視為是極其重要的程式,除非為了維持所有前臺程式同時執行而必須終止,否則系統不會終止這些程式。
服務程式
正在執行已使用 startService() 方法啟動的服務且不屬於上述兩個更高類別程式的程式。儘管服務程式與使用者所見內容沒有直接關聯,但是它們通常在執行一些使用者關 心的操作(例如,在後臺播放音樂或從網路下載資料)。因此,除非記憶體不足以維持所有前臺程式和可見程式同時執行,否則系統會讓服務程式保持執行狀態。
後臺程式
包含目前對使用者不可見的 Activity 的程式(已呼叫 Activity 的 onStop() 方法)。這些程式對使用者體驗沒有直接影響,系統可能隨時終止它們,以回收記憶體供前臺程式、可見程式或服務程式使用。 通常會有很多後臺程式在執行,因此它們會儲存在 LRU (最近最少使用)列表中,以確保包含使用者最近檢視的 Activity 的程式最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並儲存了其當前狀態,則終止其程式不會對使用者體驗產生明顯影響,因為當使用者導航回該 Activity 時,Activity 會恢復其所有可見狀態。
空程式
不含任何活動應用元件的程式。保留這種程式的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程式快取和底層核心快取之間保持平衡,系統往往會終止這些程式。