首先吐槽下某米的手机,质量不错,去年这时候收到的,用到如今除了摄像头里进灰去售后免费修了一次以外,其余的都还好。java
美中不足就在于其搭载的MIUI v5不能彻底释放APQ8064T 2G内存的潜力,刚打开的程序,每每切换到后台以后没多久就被“终结”了,想再切换回去只能等待系统从新加载一遍应用。android
我最开始怀疑是系统占用资源过多所致,可是每次查看内存,总还有700MB可用(这里MIUI的任务管理器和Android自带的应用管理器给出的结果不一致,MIUI给出的数值通常较小,但也有700MB)。并且手中那台老掉渣内存仅有1G的Moto Atrix即便放一晚上也不会自动关掉你以前打开的程序,这就否认了内存不足这个猜想。app
而后我转而怀疑是MIUI的进程管理自动关掉了空闲的后台程序。因而我在任务管理器里把全部进程都上了锁(就是长按HOME以后把App图标往下拉),以后自动关闭的状况虽然会好一些,可是仍不能彻底根除。哪怕是占用内存不多的程序,好比设置等,闲置一段时间后仍然会被kill掉。框架
既然仍是找不到幕后杀手,就只有查看Logcat,看看凶手有没有留下蛛丝马迹了。ide
通过一番搜索,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两个家伙在不停地干掉个人后台程序。ui
Google之,试图找到已有的解决办法,结果仅有的几篇相关文章也没把问题的根本缘由说明白,只是诸如“为何我开发的程序在后台被关闭了”等等泛泛的讨论。this
搜索无果后,我决定直接查看Android源码,Google搜索 "No longer want" site:android.googlesource.com/google
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,这下踏实了,也不用为了进程问题去刷不稳定的第三方系统了。