首先吐槽下某米的手機,質量不錯,去年這時候收到的,用到現在除了攝像頭里進灰去售后免費修了一次之外,其他的都還好。
美中不足就在於其搭載的MIUI v5不能完全釋放APQ8064T 2G內存的潛力,剛打開的程序,往往切換到后台之后沒多久就被“終結”了,想再切換回去只能等待系統重新加載一遍應用。
我最開始懷疑是系統占用資源過多所致,但是每次查看內存,總還有700MB可用(這里MIUI的任務管理器和Android自帶的應用管理器給出的結果不一致,MIUI給出的數值一般較小,但也有700MB)。而且手中那台老掉渣內存僅有1G的Moto Atrix即使放一夜也不會自動關掉你之前打開的程序,這就否定了內存不足這個猜測。
然后我轉而懷疑是MIUI的進程管理自動關掉了空閑的后台程序。於是我在任務管理器里把所有進程都上了鎖(就是長按HOME之后把App圖標往下拉),之后自動關閉的情況雖然會好一些,但是仍不能完全根除。哪怕是占用內存很少的程序,比如設置等,閑置一段時間后仍然會被kill掉。
既然還是找不到幕后殺手,就只有查看Logcat,看看凶手有沒有留下蛛絲馬跡了。
經過一番搜索,Logcat給了我這些:
04-28 14:47:37.844: I/ActivityManager(597): No longer want com.cleanmaster.miui_module (pid 918): hidden #25 04-28 14:47:37.925: I/ActivityManager(597): No longer want com.miui.guardprovider (pid 1342): hidden #25 04-28 14:47:43.020: W/ExtraActivityManagerService(597): No longer want com.miui.networkassistant (pid 1823) for more free memory 04-28 14:47:43.020: I/ActivityManager(597): No longer want com.android.fileexplorer (pid 1805): hidden #25
可以看出是ActivityManager(或者更確切點,ActivityManagerService)和ExtraActivityManagerService兩個家伙在不停地干掉我的后台程序。
Google之,試圖找到已有的解決辦法,結果僅有的幾篇相關文章也沒把問題的根本原因說明白,只是諸如“為什么我開發的程序在后台被關閉了”等等泛泛的討論。
搜索無果后,我決定直接查看Android源碼,Google搜索 "No longer want" site:android.googlesource.com/
commit描述里面寫了對empty process以及hidden process分開處理;以前是二者統一處理,共用一個上限(mProcessLimit),現在empty和hidden有了獨立的上限,但是不知道該commit的版本是否和MIUI的Android版本一致。先不管這個,直接查看2s的MIUI v5對應的Android版本,是4.1.1:
這個是am(Activity Manager)的源碼之一,ActivityManagerService.java。在里面搜索"No longer want",得到:
if (numHidden > mProcessLimit) {
Slog.i(TAG, "No longer want " + app.processName
+ " (pid " + app.pid + "): hidden #" + numHidden);
EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
app.processName, app.setAdj, "too many background");
app.killedBackground = true;
Process.killProcessQuiet(app.pid);
}
看樣子是由於后台進程數量過多,導致系統關閉了多余的hidden進程。並且上面提到的commit中對empty process的處理機制在這里並不存在,hidden進程和empty進程一並作為后台進程處理,並且其數量之和不能超過一個閾值。這個閾值mProcessLimit,其初始化為:
int mProcessLimit = ProcessList.MAX_HIDDEN_APPS;
這樣一來基本可以確定問題的解決方法了:只要增大ActivityManagerService實例的mProcessLimit,或修改ProcessList.MAX_HIDDEN_APPS即可。先看后者,ProcessList.java中,MAX_HIDDEN_APPS為static final,故無法修改,除非自己編譯ROM。有趣的是AOSP中該值為15,而MIUI似乎把這個值增大到了24,以容納其更加臃腫的系統,但是看來還是不夠。
好在ActivityServiceManager中提供了這樣一個方法:
public void setProcessLimit(int max)
可以直接調用之來修改mProcessLimit。那么如何調用一個系統類中的方法呢?ActivityManagerService並不存在於Android SDK的android.jar中,所以在第三方App中直接調用是不可能的。或許可以通過自行編譯一個含有隱藏類的android.jar來實現調用,但這會隨着系統版本更迭而產生很多兼容性問題,故否定。
那么只好祭出我們的大殺器了:Xposed框架!Xposed Framework是一款專門用來修改系統資源及代碼注入的工具:
http://repo.xposed.info/module/de.robv.android.xposed.installer
過程簡述如下:通過Xposed框架,在ActivityManagerService的startRunning()方法之后注入代碼,執行setProcessLimit(40)。
代碼如下:
package com.barius.morebackground;
import java.lang.reflect.Method;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public class XposedModule implements IXposedHookLoadPackage {
private static boolean LOG_ON = true;
private static void LOG(String content) {
if (LOG_ON) {
XposedBridge.log(content);
}
}
private static final String[] TARGET_PACKAGE_NAMES = {
"android",
"com.barius.morebackground"
};
private static final int NEW_PACKAGE_LIMIT = 40;
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
// only want certain target packages
boolean targetFound = false;
int targetIdx = -1;
for (int i = 0; i < TARGET_PACKAGE_NAMES.length; i++) {
if (lpparam.packageName.equals(TARGET_PACKAGE_NAMES[i])) {
targetFound = true;
targetIdx = i;
}
}
if (!targetFound) {
return;
}
LOG("=== MoreBackground Loaded app: " + lpparam.packageName);
switch (targetIdx) {
case 0:
hackActivityManagerService(lpparam);
break;
case 1:
//changeProcessLimit(lpparam);
//checkProcessLimit(lpparam);
break;
}
LOG("=== Job done.");
}
private boolean hackActivityManagerService(final LoadPackageParam lpparam) {
return changeProcessLimit(lpparam);
}
// !!! MASSIVE DESTRUCTION !!! USE WITH CAUTION !!!
private void hookEveryMethod(LoadPackageParam lpparam) {
String targetClassName = "com.android.server.am.ActivityManagerService";
final Class<?> clazz = XposedHelpers.findClass(targetClassName, lpparam.classLoader);
Method[] methods = clazz.getMethods();
for(int i = 0; i < methods.length; i++) {
Method m = methods[i];
XposedBridge.hookMethod(m, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
LOG("--- Called: " + param.method.getName());
}
});
}
}
private boolean changeProcessLimit(LoadPackageParam lpparam) {
final String targetClassName = "com.android.server.am.ActivityManagerService";
final String targetMethodName = "startRunning";
final Class<?> clazz = XposedHelpers.findClass(targetClassName, lpparam.classLoader);
final Method startRunning = XposedHelpers.findMethodExact(clazz, targetMethodName,
String.class, String.class, String.class, String.class);
XposedBridge.hookMethod(startRunning, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
LOG("After " + targetMethodName + "()");
Object _this = param.thisObject;
LOG(_this.getClass().getName());
LOG("--- Using XposedHelper to invoke method");
XposedHelpers.callMethod(_this, "setProcessLimit", NEW_PACKAGE_LIMIT);
LOG("--- ... done");
}
});
return true;
}
private void checkProcessLimit(LoadPackageParam lpparam) {
}
}
運行之后發現進程退出現象明顯好轉(ExtraActivityManagerService還是會殺進程,但這似乎是MIUI的進程管理器,並且上了鎖之后就不會亂殺,先不管了)。Logcat顯示:
04-28 15:17:53.242: I/ActivityManager(597): No longer want com.miui.notes (pid 1786): hidden #41
#41 說明修改成功,后台進程限制被改為40。MIUI報告剩余內存在500MB左右浮動。尚不清楚這么做會對系統耗電量有多大影響,先試試看看吧。
OK,這下踏實了,也不用為了進程問題去刷不穩定的第三方系統了。
