1.问题描述

小程序应用是部分生态应用的入口,类似于launcher的生态应用的入口合集。测试同学反馈从小程序点击进入真实应用之后,多操作几次会发现从应用无法返回小程序应用,直接返回的是Launcher。

2.问题调查

1
2
3
4
5
6
7
8
I/ActivityManager( 784): Killing 4672:com.android.keychain/1000 (adj 906): empty #17
I/am_kill ( 784): [0,4672,com.android.keychain,906,empty #17]
I/ActivityManager( 784): Killing 4451:com.android.printspooler/u0a73 (adj 906): empty #17
I/am_kill ( 784): [0,4451,com.android.printspooler,906,empty #17]
I/ActivityManager( 784): Killing 4803:com.android.managedprovisioning/u0a10 (adj 906): empty #17
I/am_kill ( 784): [0,4803,com.android.managedprovisioning,906,empty #17]
I/ActivityManager( 784): Killing 4982:com.qualcomm.timeservice/u0a95 (adj 906): empty #17
I/am_kill ( 784): [0,4982,com.qualcomm.timeservice,906,empty #17]

从以上日志中搜索am_kill可以看到有很多应用被杀掉。

为什么会被杀呢? 通常情况下内存不够用了,AMS处通过杀进程来回收部分内存。因此看一下内存情况。

1
dumpsys meminfo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
225,572K: Cached
41,353K: com.gxatek.cockpit.settings (pid 23328 / activities)
33,412K: com.gxatek.cockpit.diagnostic (pid 27166 / activities)
33,314K: com.gxatek.cockpit.diagnostic (pid 27231 / activities)
30,598K: com.gxatek.cockpit.globalsearch (pid 32533 / activities)
20,520K: com.gxa.service.weather (pid 4621)
19,520K: com.gxa.service.weather (pid 4596)
14,718K: com.android.support.car.lenspicker (pid 27122)
14,063K: android.process.acore (pid 14054)
13,764K: com.android.deskclock (pid 2167)
13,464K: com.android.deskclock (pid 2189)
13,138K: com.android.providers.calendar (pid 3033)
12,145K: com.android.mtp (pid 32723)
12,049K: com.android.printspooler (pid 13469)
11,199K: com.android.externalstorage (pid 32696)
8,613K: android.process.media (pid 2887)

我们当前车机系统是多用户系统,可以看到很多进程会有多个进程存在。而且Cached空进程项展示的进程会有很多重复的进程。通过如下源码可以看到空进程项最大只有16个,如果超过的话,会杀掉LRU中最前面的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void updateOomAdjLocked() {
......
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
mNumCachedHiddenProcs++;
numCached++;
if (numCached > cachedProcessLimit) {
app.kill("cached #" + numCached, true);
}
break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
&& app.lastActivityTime < oldTime) {
app.kill("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
/ 1000) + "s", true);
} else {
numEmpty++;
// 如果大于emptyProcessLimit就会杀掉进程。
if (numEmpty > emptyProcessLimit) {
app.kill("empty #" + numEmpty, true);
}
}
break;
default:
mNumNonCachedProcs++;
break;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerConstants.java
// emptyProcessLimit定义
final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;

private void updateMaxCachedProcesses() {
CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
// 计算空进程限制值
CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);

// Note the trim levels do NOT depend on the override process limit, we want
// to consider the same level the point where we do trimming regardless of any
// additional enforced limit.
final int rawMaxEmptyProcesses = computeEmptyProcessLimit(MAX_CACHED_PROCESSES);
CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses/2;
CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
}
1
2
3
4
5
6
7
public static int computeEmptyProcessLimit(int totalProcessLimit) {
return totalProcessLimit/2;
}

private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
// 最终确认rawMaxEmptyProcesses为16

因此如果Cached进程进程超过16个,也会触发杀进程机制。因此为了保证小程序后台进程进入被系统放入Cached进程不被杀,就要限制Cached进程尽量少于16。

3.解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// frameworks/base / services/core/java/com/android/server/am/ActivityManagerService.java
ArrayList<String> mCachePackageList= new ArrayList<>();

final void updateOomAdjLocked() {
......
mCachePackageList.clear();
for (int i=N-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
applyOomAdjLocked(app, true, now, nowElapsed);

// Count the number of process types.
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
if (mCachePackageList.contains(app.info.processName)) {
// 如果cached中已经有进程的话,直接干掉
app.kill("multi user process:" + app.info.processName, true);
break;
} else {
// 没有的话就记录一下
mCachePackageList.add(app.info.processName);
}
mNumCachedHiddenProcs++;
numCached++;
if (numCached > cachedProcessLimit) {
app.kill("cached #" + numCached, true);
}
break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
if (mCachePackageList.contains(app.info.processName)) {
// 如果cached中已经有进程的话,直接干掉
app.kill("multi user process:" + app.info.processName, true);
break;
} else {
// 没有的话就记录一下
mCachePackageList.add(app.info.processName);
}
if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
&& app.lastActivityTime < oldTime) {
app.kill("empty for "
+ ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
/ 1000) + "s", true);
} else {
numEmpty++;
if (numEmpty > emptyProcessLimit) {
app.kill("empty #" + numEmpty, true);
}
}
break;
default:
mNumNonCachedProcs++;
break;
}
}

从上面逻辑可以看到修复的方案就是当Cached的进程中如果有相同进程的话,就直接杀掉,如果没有的话就保存下来。

此方案不能完全解决小程序后台被杀的情况,但是可以防止小程序进程被放入Cached进程被杀。

当从生态应用回到小程序应用的时候,进程也不会是后台进程了,优先级升高了,因此也不会被放入Cached进程中。