MIUI後臺程序頻繁退出緣由探究及解決

 

首先吐槽下某米的手機,質量不錯,去年這時候收到的,用到如今除了攝像頭裏進灰去售後免費修了一次以外,其餘的都還好。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

搜到了這個,https://android.googlesource.com/platform/frameworks/base/+/ee7621c0f5de6eca2cfb9fb2b6117fb61e13cc41%5E!/spa

commit描述裏面寫了對empty process以及hidden process分開處理;之前是兩者統一處理,共用一個上限(mProcessLimit),如今empty和hidden有了獨立的上限,可是不知道該commit的版本是否和MIUI的Android版本一致。先無論這個,直接查看2s的MIUI v5對應的Android版本,是4.1.1:

https://android.googlesource.com/platform/frameworks/base/+/android-4.1.1_r6.1/services/java/com/android/server/am/ActivityManagerService.java

這個是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,這下踏實了,也不用爲了進程問題去刷不穩定的第三方系統了。

相關文章
相關標籤/搜索