上一篇文章從Native角度講解了Android進程管理的相關概念,本文將繼續從上層的Framework中的進程啟動、銷毀場景和優先級處理、以及它們與四大組件的種種關聯,來逐步解析Android進程管理的其他關鍵要素。
進程的啟動
Android Framework層是運行在ART虛擬機之上的,對于一般的JAVA進程是不具備主動創建進程的能力的。我們都知道Android中存在四大組件:Activity、Service、BroadcastReceiver和ContentProvider,那么為什么這樣劃分呢,一個主要的原因是在四大組件的啟動階段,如果宿主進程沒有正在運行,那么系統服務進程system_server將會先拉起進程,然后才會繼續啟動組件。簡而言之,正因為AMS將安卓中進程的創建、銷毀及優先級進行了封裝,應用側開發者才能夠在對進程無感知的情況的同時使用四大組件。

啟動四大組件方法 startActivity, sendBroadcast, startService/bindService 和 getContentProviderImpl 之所以能夠啟動進程,是因為它們都會通過發送 binder 消息到 AMS 進行處理,當 AMS 發現對應組件沒有在 xml 聲明的進程啟動時,會先拉起該進程,然后再啟動對應組件。
也就是說,四大組件正是App進程的入口。它們拉起進程的方式都是一致的,基本流程:
- Zygote進程啟動,初始化虛擬機及基本環境,同時進入一個無限循環,提供創建 java 進程服務
- AMS 通過調用 Process.start() 方法,發送 socket 信息到 Zygote 進程
- Zygote 進程處理消息,并通過fork創建應用進程
- 應用進程反射調用 ActivityThread.main 方法,進入自身邏輯 (初始化 Looper/Binder 等主要服務 )
- AMS 創建進程成功后,會將進程以 ProcessRecord 的形式進行封裝、管理。
彩蛋:實用的進程相關adb命令
Java進程的創建與銷毀實時
adb logcat -b events | grep proc**
07-16 14:19:23.357 4774 4800 I am_proc_start: [0,30314,10063,com.koushikdutta.vysor,activity,com.koushikdutta.vysor/.StartActivity]
07-16 14:19:23.375 4774 6027 I am_proc_bound: [0,30314,com.koushikdutta.vysor]
07-16 14:19:31.621 4774 5281 I am_proc_died: [0,30314,com.koushikdutta.vysor,900,17]
獲取應用進程中組件詳細信息
adb shell dumpsys activity p <packageName>
*APP* UID 10115 ProcessRecord{4114d9d 18987:com.ss.android.ugc.aweme/u0a115}
user #0 uid=10115 gids={50115, 20115, 9997, 3002, 3003, 3001}
requiredAbi=armeabi-v7a instructionSet=arm
class=com.ss.android.ugc.aweme.app.host.HostApplication
...
// adj, procState優先級信息
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
curSchedGroup=2 setSchedGroup=2 systemNoUi=false trimMemoryLevel=0
curProcState=2 repProcState=2 pssProcState=5 setProcState=2
...
**Activities**:
- ActivityRecord{5e38d19 u0 com.ss.android.ugc.aweme/.main.MainActivity t948}
**Services**:
- ServiceRecord{13f23bc u0 com.ss.android.ugc.aweme/com.tt.miniapphost.process.base.HostCrossProcessCallService}
...
**Connections**:
- ConnectionRecord{77ae579 u0 CR com.ss.android.ugc.aweme/com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService:@30b4240}
...
**Published Providers**:
- com.umeng.message.provider.MessageProvider
-> ContentProviderRecord{76d000a u0 com.ss.android.ugc.aweme/com.umeng.message.provider.MessageProvider}
...
**Connected Providers**:
- c887614/com.android.providers.settings/.SettingsProvider->18987:com.ss.android.ugc.aweme/u0a115 s1/1 u0/0 +19h23m3s147ms
- c4d7fba/com.android.providers.media/.MediaProvider->18987:com.ss.android.ugc.aweme/u0a115 s0/1 u1/2 +1m10s935ms
**Receivers**:
- ReceiverList{4afbc5 18987 com.ss.android.ugc.aweme/10115/u0 remote:687d23c}
...
獲取應用優先級信息
adb shell dumpsys activity o
Proc 0: fore T/A/TOP trm: 0 18987:com.ss.android.ugc.aweme/u0a115 (top-activity)
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
state: cur=TOP set=TOP lastPss=268MB lastSwapPss=0.00 lastCachedPss=0.00
cached=false empty=false hasAboveClient=false
進程的優先級
Android Framework中進程優先級的衡量單位有兩種,除了 adj (對應 lmk 的 oom_score_adj)之外,新添了 procState 變量。這兩個優先級相輔相成,adj 更多的用在給 lmk 判斷該進程是否應該被殺死,而procState 更多的給 Framework 層的服務給進程狀態"定級",例如,AMS可以簡單粗暴的通過判斷 procState 是否小于等于 PROCESS_STATE_IMPORTANT_FOREGROUND 來判斷該進程是否為"前臺進程":
[ActivityManagerService.java]
@Override
public boolean isAppForeground(int uid) {
synchronized (this) {
UidRecord uidRec = mactiveUids.get(uid);
if (uidRec == null || uidRec.idle) {
return false;
}
return uidRec.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
}
}
這里的 UidRecord 是 Android 常用的權限管理方案,如果沒有做修改的話,普通應用創建的進程都是在同一個 uid 里的。在系統側,Android 常常通過 uid 來劃分對應的進程是否具有相應的優先級。
關于Android中adj與procState具體值的含義,可以查看文章最后的附錄表格,具體場景僅供參考。如果需要細致判定當前進程優先級的狀態,通過源碼分析最為直觀。
adj、schedGroup、procState和adjType的核心計算步驟是在AMS中的computeOomAdjLocked方法完成的,如圖:

這個方法的核心執行無外乎以下幾個功能:
- 首先檢查該進程是否處于高優場景中,如前臺Activity、正在運行RemoteAnimation,正在處理廣播和Service等
- 該進程是否是某種特殊進程,如Home,height weight和backup
- 是否存在進程因為service和content provider互相綁定提升優先級的情況
- 如果都不是上述情況,處于的優先級都比較低,最低的是cache進程,AMS對系統整體cache進程個數有閾值限制,超過上限就會觸發清理操作
彩蛋:使用framework中的"自相矛盾"進行保活
進程保活無外乎也是一種普通應用對操作系統的攻擊手段,對于一般的攻擊手段,我把它理解成使用開發者約定俗成的規則漏洞來突破普通用戶原有權限的行為。
比如說 buffer overflow,是利用了舊版本C編譯器在切換函數堆棧時,沒有有效檢查堆棧信息作用范圍的漏洞。使得普通應用用戶可以利用超長的棧內信息覆蓋調原有的堆棧跳轉信息,使得應用執行其它意想不到的程序。通過這種方式可以獲取到root權限;又比如說 TCP 劫持,是利用了發送方與接收方沒有很好的 SEQ和ACK 序列的校驗手段,使得中間者可以通過控制 SEQ和ACK的數值來控制正常通信方的流程。
方式一:bindService 互相綁定以提升優先級
這種優先級方式分為兩種
第一種,依賴高優先級 client:
if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
switch (procState) {
case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
// Something else is keeping it at this level, just leave it.
break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
case ActivityManager.PROCESS_STATE_SERVICE:
procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
app.adjType = mayBeTopType;
app.adjSource = mayBeTopSource;
app.adjTarget = mayBeTopTarget;
...
}
}
maybeTop 為 true 需要 ContentProvider 或者 Service 的 client 正在前臺顯示 Activity
同理,通過讓用戶選擇該client來提供服務,例如WallPaperService,可以讓system_server成為client,以此來提高進程優先級。
第二種,兩個進程互相綁定:
if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
clientProcState =
ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else if (mWakefulness
== PowerManagerInternal.WAKEFULNESS_AWAKE &&
(cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
!= 0) {
clientProcState =
ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else {
clientProcState =
ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
}
...
if (procState > clientProcState) {
procState = clientProcState;
if (adjType == null) {
adjType = "service";
}
}
通過設置 flag,兩個普通的進程可以通過互相"攀升"來提升優先級
方式二、監聽鎖屏廣播拉起Activity,監聽解鎖廣播銷毀Activity:
具體步驟:
- 啟動一個Service,通過動態廣播receiver接收鎖屏、開屏廣播
- 滅屏時,啟動Activity到前臺
- 開屏時,將Activity進行 finish,確保用戶無感知
adb shell ps | grep <pacakgeName>
ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
All known processes:
*APP* UID 10064 ProcessRecord{f59eb2f 30917:com.bytedance.gcsuppression.demo/u0a64}
...
oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
curSchedGroup=1 setSchedGroup=1 systemNoUi=false trimMemoryLevel=0
curProcState=5 repProcState=5 pssProcState=5 setProcState=5 lastStateTime=-23s716ms
(該方法在 Android 8.0 上測試過仍有效,更高版本尚未測試;因為是利用了 service 與動態廣播的特性,所以 framework 很難對這個邏輯做出應對,除非是在系統側維護一個 service 黑名單)
進程的清理

雖然殺進程有這么多的花樣,但常見的方式就三種:
清理名稱觸發場景功能kill粒度最細,用于進程自殺、AMS 查殺單個緩存進程等殺死單個 java 進程killBackground普通應用也可以通過 AM 服務調用殺死系統中 background 進程force-stop以 package 為單位,強力殺死和該包有關聯的進程并禁止大部分自啟殺死該包下所有進程,關閉并銷毀相關組件
普通的查殺方式如kill和killBackground都只是殺死單個或部分進程,當進程死亡后,會因為 binder fd 的釋放通過死亡回調通知 AMS 進程已死,這期間 AMS 也有可能因為 Service 等組件重新將進程拉起。考慮到拉活方案花樣多,應用中的其它 java 進程或 c 進程仍然可以通過啟動組件的方式重新將進程拉起來。
在一般的原生機器上,force-stop雖然很少使用,一般只在設置里的"強制停止"按鈕觸發。除了殺死進程之外也會銷毀組件等信息,同時也會更改 PMS 中對應包名的狀態,避免再次拉活。其主要功能如下:

彩蛋:當我們上滑應用卡片時Android在做什么
- systemui 進程中的 com.android.systemui/.recents.RecentsActivity 接收到卡片滑動事件
- systemui 獲取到 AMS 服務,再調用 am.removeTask 方法來移除 Task 和殺死顯示該界面的進程;注意這個方法普通應用是沒有調用權限的。
- 最后通過 AMS 的 cleanUpRemovedTaskLocked 方法來殺死進程,這個方法的具體實現如下:
void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
// 1. 在最近任務中移除 Task
if (removeFromRecents) {
mRecentTasks.remove(tr);
}
...
// 2. 選擇需要查殺的進程
final String pkg = component.getPackageName();
ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mProcessNames.getMap();
for (int i = 0; i < pmap.size(); i++) {
SparseArray<ProcessRecord> uids = pmap.valueAt(i);
for (int j = 0; j < uids.size(); j++) {
ProcessRecord proc = uids.valueAt(j);
...
// 2.1 如果 Task 所在進程還有別的 Task 在最近任務中顯示,那么該進程不會被殺
for (int k = 0; k < proc.activities.size(); k++) {
TaskRecord otherTask = proc.activities.get(k).getTask();
if (tr.taskId != otherTask.taskId && otherTask.inRecents) {
return;
}
}
// 2.2 進程有前臺service的不會被殺,典型的有音樂播放軟件
if (proc.foregroundServices) {
return;
}
// Add process to kill list.
procsToKill.add(proc);
}
}
// 3. 正式查殺
for (int i = 0; i < procsToKill.size(); i++) {
ProcessRecord pr = procsToKill.get(i);
// 如果滿足這兩個條件才會立即被殺
if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
&& pr.curReceivers.isEmpty()) {
pr.kill("remove task", true);
} else {
pr.waitingToKill = "remove task";
}
}
}
彩蛋:反保活的終結者
Android 中的進程查殺最強武器是force-stop,本質上它會通過遍歷并殺死所有和應用有關的Java進程:
private final boolean killPackageProcessesLocked(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit, boolean evenPersistent, String reason) {
ArrayList<ProcessRecord> procs = new ArrayList<>();
final int NP = mProcessNames.getMap().size();
for (int ip=0; ip<NP; ip++) {
SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
final int NA = apps.size();
for (int ia=0; ia<NA; ia++) {
ProcessRecord app = apps.valueAt(ia);
...
// continue if the process do not need to kill
app.removed = true;
procs.add(app);
}
}
int N = procs.size();
// force-stop 的本質是通過遍歷所有與應用有關的進程,依次查殺來實現殺死所有進程的
for (int i=0; i<N; i++) {
removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, reason);
}
updateOomAdjLocked();
return N > 0;
}
想要做反保活的話,我們只需要在 removeProcessLocked 的遍歷之前,將 java 進程和在其同一cgroup的c進程一起通過發送信號hang住,再通過循環依次殺死所有進程,這種方法基本能杜絕所有拉活方案。
總結
進程管理與大家日常開發息息相關,也是在Android系統中是個舉足輕重的模塊。相信通過本系列的兩篇文章,大家已經對進程啟動期間涉及到fork、對進程的優先級管理涉及到adj、對進程的調度這些概念已經有了更加深入的理解了。