功能:1、实现低内存时候不杀应用 2、应用保活
代码路径:
frameworks/base/service/score/java/com/android/server/am/ActivityManagerServiceEx.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/core/java/android/app/ProcessProtection.java
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
frameworks/base//core/res/res/values/config_ex.xml
frameworks/base/services/core/java/com/android/server/am/LmKillerTracker.java
一、应用保活
应用保活:1、保持进程不被系统杀死 2、进程被系统杀死之后,可以重新复活。
应用保活原理:提高应用的oom_adj值,这样应用不管切换到后台的时候,都让该进程保持前台进程,这样就不容易被系统杀死,如果adj的值越大就越容易被系统杀死。
如何提高adj值就可以实现应用保活
在ActivityManagerServiceEx.java文件中构造函数中提高adj值,其中该updateOomAdjLocked()方法就是更新adj值
static HashMap sPreProtectAreaList;
static {
sPreProtectAreaList = new HashMap();
}
public ActivityManagerServiceEx(Context systemContext) {
super(systemContext);
mIsInHome = true;
mExHandler = new ExHandler((Looper) mHandler.getLooper());
addProtectArea("包名", new ProtectArea(0, 0, ProcessProtection.PROCESS_STATUS_RUNNING));
}
public void addProtectArea(final String processName, final ProtectArea area) {
if(processName == null || area == null) {
return;
}
if (DEBUG_AMSEX) Slog.d(TAG, "addProtectArea, processName: " + processName
+ " ProtectArea: " + area);
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (ActivityManagerServiceEx.this) {
sPreProtectAreaList.put(processName, area);
updateOomAdjLocked();
}
}
});
}
}
在ActivityManagerService.java文件中开启进程会调用startProcess()方法,然后再次执行startProcessLocked()该方法
@Override
public void startProcess(String processName, ApplicationInfo info,
boolean knownToBeDead, String hostingType, ComponentName hostingName) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "startProcess:"
+ processName);
}
synchronized (ActivityManagerService.this) {
startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
new HostingRecord(hostingType, hostingName),
false /* allowWhileBooting */, false /* isolated */,
true /* keepIfLarge */);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
在ProcessList.java文件中startProcessLocked()方法中更新ProcessRecord 中的protectStatus 、 protectMinAdj 、protectMaxAdj 三个值
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
boolean disableHiddenApiChecks, boolean mountExtStorageFull,
String abiOverride) {
if (app.pendingStart) {
return true;
}
long startTime = SystemClock.elapsedRealtime();
if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
checkSlow(startTime, "startProcess: removing from pids map");
mService.mPidsSelfLocked.remove(app);
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
checkSlow(startTime, "startProcess: done removing from pids map");
app.setPid(0);
app.startSeq = 0;
}
if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v(
TAG_PROCESSES,
"startProcessLocked removing on hold: " + app);
mService.mProcessesOnHold.remove(app);
checkSlow(startTime, "startProcess: starting to update cpu stats");
mService.updateCpuStats();
checkSlow(startTime, "startProcess: done updating cpu stats");
try {
try {
final int userId = UserHandle.getUserId(app.uid);
AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
int uid = app.uid;
int[] gids = null;
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
if (!app.isolated) {
int[] permGids = null;
try {
checkSlow(startTime, "startProcess: getting gids from package manager");
final IPackageManager pm = AppGlobals.getPackageManager();
permGids = pm.getPackageGids(app.info.packageName,
MATCH_DIRECT_BOOT_AUTO, app.userId);
if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) {
mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
} else {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
app.info.packageName);
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
/*
* Add shared application and profile GIDs so applications can share some
* resources like shared libraries and access user-wide resources
*/
if (ArrayUtils.isEmpty(permGids)) {
gids = new int[3];
} else {
gids = new int[permGids.length + 3];
System.arraycopy(permGids, 0, gids, 3, permGids.length);
}
gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
// Replace any invalid GIDs
if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
if (gids[1] == UserHandle.ERR_GID) gids[1] = gids[2];
}
app.mountMode = mountExternal;
checkSlow(startTime, "startProcess: building args");
if (mService.mAtmInternal.isFactoryTestProcess(app.getWindowProcessController())) {
uid = 0;
}
int runtimeFlags = 0;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
// Also turn on CheckJNI for debuggable apps. It's quite
// awkward to turn on otherwise.
runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
// Check if the developer does not want ART verification
if (android.provider.Settings.Global.getInt(mService.mContext.getContentResolver(),
android.provider.Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE, 1) == 0) {
runtimeFlags |= Zygote.DISABLE_VERIFIER;
Slog.w(TAG_PROCESSES, app + ": ART verification disabled");
}
}
// Run the app in safe mode if its manifest requests so or the
// system is booted in safe mode.
if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 ||
mService.mSafeMode == true) {
runtimeFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
}
if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0) {
runtimeFlags |= Zygote.PROFILE_FROM_SHELL;
}
if ("1".equals(SystemProperties.get("debug.checkjni"))) {
runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
}
String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info");
if ("1".equals(genDebugInfoProperty) || "true".equals(genDebugInfoProperty)) {
runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
}
String genMiniDebugInfoProperty = SystemProperties.get("dalvik.vm.minidebuginfo");
if ("1".equals(genMiniDebugInfoProperty) || "true".equals(genMiniDebugInfoProperty)) {
runtimeFlags |= Zygote.DEBUG_GENERATE_MINI_DEBUG_INFO;
}
if ("1".equals(SystemProperties.get("debug.jni.logging"))) {
runtimeFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
}
if ("1".equals(SystemProperties.get("debug.assert"))) {
runtimeFlags |= Zygote.DEBUG_ENABLE_ASSERT;
}
if (mService.mNativeDebuggingApp != null
&& mService.mNativeDebuggingApp.equals(app.processName)) {
// Enable all debug flags required by the native debugger.
runtimeFlags |= Zygote.DEBUG_ALWAYS_JIT; // Don't interpret anything
runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; // Generate debug info
runtimeFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE; // Disbale optimizations
mService.mNativeDebuggingApp = null;
}
if (app.info.isEmbeddedDexUsed()
|| (app.info.isPrivilegedApp()
&& DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) {
app.info.maybeUpdateHiddenApiEnforcementPolicy(
mService.mHiddenApiBlacklist.getPolicy());
@ApplicationInfo.HiddenApiEnforcementPolicy int policy =
app.info.getHiddenApiEnforcementPolicy();
int policyBits = (policy << Zygote.API_ENFORCEMENT_POLICY_SHIFT);
if ((policyBits & Zygote.API_ENFORCEMENT_POLICY_MASK) != policyBits) {
throw new IllegalStateException("Invalid API policy: " + policy);
}
runtimeFlags |= policyBits;
}
String useAppImageCache = SystemProperties.get(
PROPERTY_USE_APP_IMAGE_STARTUP_CACHE, "");
// Property defaults to true currently.
if (!TextUtils.isEmpty(useAppImageCache) && !useAppImageCache.equals("false")) {
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
if (new File(wrapperFileName).exists()) {
invokeWith = "/system/bin/logwrapper " + wrapperFileName;
}
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
if (requiredAbi == null) {
requiredAbi = Build.SUPPORTED_ABIS[0];
}
String instructionSet = null;
if (app.info.primaryCpuAbi != null) {
instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
}
app.gids = gids;
app.setRequiredAbi(requiredAbi);
app.instructionSet = instructionSet;
// the per-user SELinux context must be set
if (TextUtils.isEmpty(app.info.seInfoUser)) {
Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined",
new IllegalStateException("SELinux tag not defined for "
+ app.info.packageName + " (uid " + app.uid + ")"));
}
final String seInfo = app.info.seInfo
+ (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);
// Start the process. It will either succeed and return a result containing
// the PID of the new process, or else throw a RuntimeException.
final String entryPoint = "android.app.ActivityThread";
boolean startSuccess = startProcessLocked(hostingRecord, entryPoint,
app, uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi,
instructionSet, invokeWith, startTime);
if (app.processName != null) {
ProtectArea pa = sPreProtectAreaList.get(app.processName);
if (pa != null) {
//这里就是更新adj
app.protectStatus = pa.mLevel;
app.protectMinAdj = pa.mMinAdj;
app.protectMaxAdj = pa.mMaxAdj;
}
if (DEBUG_AMSEX) {
Slog.d(TAG, "startProcessLocked app.protectLevel :"
+ app.protectStatus + " app.protectMinAdj: " + app.protectMinAdj
+ " app.protectMaxAdj: " + app.protectMaxAdj);
}
}
return startSuccess;
} catch (RuntimeException e) {
Slog.e(ActivityManagerService.TAG, "Failure starting process " + app.processName, e);
// Something went very wrong while trying to start this process; one
// common case is when the package is frozen due to an active
// upgrade. To recover, clean up any active bookkeeping related to
// starting this process. (We already invoked this method once when
// the package was initially frozen through KILL_APPLICATION_MSG, so
// it doesn't hurt to use it again.)
mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
false, false, true, false, false, app.userId, "start failure");
return false;
}
}
备注:cat /proc/{PID}/oom_adj 的值变成0就说明修改成功了
二、低内存应用添加白名单
在LmKillerTracker.java文件中如果系统内存不足的时候,这个线程通过Socket读取进程ID,然后通过doLmkForceStop()方法中shouldForceStop()来过滤白名单的应用,如果应用在白名单中就不杀死应用,最后杀死应用是通过forceStopPackage方法来杀掉进程的
public class LmKillerTracker extends Thread {
private final static String TAG = "LmKillerTracker";
private static final String LMK_TRACKER_SOCKET = "lmfs";
private ActivityManagerServiceEx mService;
private ActivityTaskManagerService mAtm;
private boolean mConnected;
private List mLmKillerBypassPackages = new ArrayList();
public LmKillerTracker() {
mService = (ActivityManagerServiceEx) ActivityManager.getService();
mAtm = mService.mActivityTaskManager;
String[] lmKillerTrackerWhitelist = Resources.getSystem().getStringArray(
com.android.internal.R.array.low_memory_killer_tracker_whitelist);
mLmKillerBypassPackages = Arrays.asList(lmKillerTrackerWhitelist);
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
int retryCount = 0;
LocalSocket lmkillerSocket = null;
mConnected = false;
try {
while(true) {
LocalSocket s = null;
LocalSocketAddress lSAddr;
try {
s = new LocalSocket();
lSAddr = new LocalSocketAddress(LMK_TRACKER_SOCKET,
LocalSocketAddress.Namespace.RESERVED);
s.connect(lSAddr);
} catch (IOException e) {
mConnected = false;
try {
if (s != null) {
s.close();
}
} catch (Exception e2) {
}
if (retryCount == 8) {
Slog.e(TAG, "can't find lmkiller socket after "
+ retryCount + " retry, abort LmkTracker");
return;
} else if ( retryCount >= 0 && retryCount < 8) {
Slog.d(TAG, "retrying " + retryCount);
}
try {
Thread.sleep(500);
} catch (InterruptedException er) {
}
retryCount++;
continue;
}
retryCount = 0;
lmkillerSocket = s;
Slog.i(TAG, "connected to lmkiller");
mConnected = true;
try {
InputStream is = lmkillerSocket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
while (true) {
String line = reader.readLine();
try {
int pid = Integer.parseInt(line);
doLmkForceStop(pid);
} catch (Exception e) {
Slog.e(TAG, "doLmkForceStop encounter exception, read line "
+ line, e);
}
}
} catch (Exception e) {
Slog.e(TAG, "caugth exception, closing lmk tracker", e);
}
try{
if (lmkillerSocket != null) lmkillerSocket.close();
} catch (IOException e) {
}
}
} catch (Exception e) {
}
}
public void doLmkForceStop(int pid) {
Slog.d(TAG, "doLmkForceStop pid=" + pid);
if (pid <= 0) {
return;
}
String pkgName = null;
ProcessRecord pr = null;
ProcessRecord parent = null;
try {
synchronized (mService.mPidsSelfLocked) {
pr = mService.mPidsSelfLocked.get(pid);
}
if (pr == null) return;
if (pr.info != null) {
if (TextUtils.isEmpty(pr.processName)) return;
if (pr.processName.indexOf(":") > 0) {
synchronized (mService) {
parent = mService.getProcessRecordLocked(
pr.info.packageName, pr.uid, true);
}
}
pkgName = pr.info.packageName;
}
} catch (Exception e) {
Slog.w(TAG, "get process record from ams failed", e);
}
if (pkgName == null) return;
// Found nothing, no processes found, abort directly.
if (pr == null && parent == null) return;
// Current process record can not be stopped.
if (pr != null && !shouldForceStop(pr)) return;
// related process record can not be stopped?
boolean main = pkgName.equals(pr.processName);
if (main) {
//parent die, walk thougth to see if we can stop all childs:
Slog.i(TAG, pr.processName+"die, see childs:");
ArrayList list = getRelatedProcesses(pkgName);
boolean shallStop = true;
for (ProcessRecord process : list) {
if (!shouldForceStop(process)) {
shallStop = false;
break;
}
}
if(!shallStop) {
return;
}
} else {
// child die , we shall not stop parent.
if (parent != null) return;
}
mService.forceStopPackage(pkgName, UserHandle.USER_CURRENT);
Slog.i(TAG, "force stop pkg:" + pkgName + ", pid:" + pid
+ " (adj " + pr.setAdj + ") has done.");
}
public boolean isConnected() {
return mConnected;
}
private ArrayList getRelatedProcesses(String pkgName) {
ArrayList list = new ArrayList<>();
synchronized (mService.mPidsSelfLocked) {
final int size = mService.mPidsSelfLocked.size();
for (int i = 0; i < size; i++) {
final int pid =mService.mPidsSelfLocked.keyAt(i);
final ProcessRecord process = mService.mPidsSelfLocked.valueAt(i);
if (process == null) continue;
if (process.info != null && pkgName.equals(process.info.packageName)
&& !process.processName.equals(pkgName)) {
Slog.i(TAG, "get related "+process.processName+",adj:"+process.setAdj+"for package"+pkgName);
list.add(process);
}
}
}
return list;
}
private boolean isSystemApp(ProcessRecord pr) {
if (pr.info.isPrivilegedApp() || pr.info.isSystemApp()
|| pr.info.isUpdatedSystemApp()) {
return true;
}
return false;
}
private boolean isEnabledInputMethod(String pkgName){
if (pkgName == null) return false;
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
List inputMethods;
try {
inputMethods = service.getEnabledInputMethodList(UserHandle.myUserId());
} catch (RemoteException e) {
return false;
}
if (inputMethods == null || inputMethods.size() == 0) return false;
for (InputMethodInfo info : inputMethods){
if (info == null || info.getPackageName() == null) continue;
if (info.getPackageName().equals(pkgName)) return true;
}
return false;
}
private boolean isInstrumentedApp(ProcessRecord pr) {
if (pr.getActiveInstrumentation() != null) return true;
return false;
}
private boolean isInWhitelist(ProcessRecord pr) {
String pkgName = pr.info.packageName;
for (String token : mLmKillerBypassPackages) {
if (pkgName.startsWith(token)) {
return true;
}
}
return false;
}
private boolean shouldForceStop(ProcessRecord pr) {
String pkgName = pr.info.packageName;
WindowProcessController windowProcessController = pr.getWindowProcessController();
int pid = pr.pid;
if (mAtm.hasActivityInTopTask(windowProcessController)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", has activity in top task !");
return false;
} else if (mAtm.hasRelativeToptaskPackageInProcess(windowProcessController)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", has relative top task package !");
return false;
} else if (isSystemApp(pr)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", is system app !");
return false;
} else if (isInstrumentedApp(pr)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", the application being instrumented !");
return false;
} else if (isEnabledInputMethod(pkgName)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", the application is EnabledInputMethod !");
return false;
} else if (isInWhitelist(pr)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", the application is in whitelist !");
return false;
} else if (mService.isProcessHeldWakeLock(pr)) {
Slog.i(TAG, "Skipped process pkgName:" + pkgName + ", pid:" + pid +
", the application is held wake lock !");
return false;
}
return true;
}
}