/*
|
* Copyright (C) 2019 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.am;
|
|
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
|
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
|
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
|
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
|
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
|
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
|
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
|
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
|
import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
|
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
|
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
|
import static android.app.ActivityManager.PROCESS_STATE_TOP;
|
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
|
import static android.os.Process.SCHED_OTHER;
|
import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
|
import static android.os.Process.THREAD_GROUP_DEFAULT;
|
import static android.os.Process.THREAD_GROUP_RESTRICTED;
|
import static android.os.Process.THREAD_GROUP_TOP_APP;
|
import static android.os.Process.setProcessGroup;
|
import static android.os.Process.setThreadPriority;
|
import static android.os.Process.setThreadScheduler;
|
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
|
import static com.android.server.am.ActivityManagerService.DISPATCH_OOM_ADJ_OBSERVER_MSG;
|
import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG;
|
import static com.android.server.am.ActivityManagerService.TAG_BACKUP;
|
import static com.android.server.am.ActivityManagerService.TAG_LRU;
|
import static com.android.server.am.ActivityManagerService.TAG_OOM_ADJ;
|
import static com.android.server.am.ActivityManagerService.TAG_PROCESS_OBSERVERS;
|
import static com.android.server.am.ActivityManagerService.TAG_PSS;
|
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
|
import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
|
|
import android.app.ActivityManager;
|
import android.app.usage.UsageEvents;
|
import android.content.Context;
|
import android.os.Debug;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.PowerManagerInternal;
|
import android.os.Process;
|
import android.os.RemoteException;
|
import android.os.SystemClock;
|
import android.os.Trace;
|
import android.os.UserHandle;
|
import android.util.ArrayMap;
|
import android.util.ArraySet;
|
import android.util.Slog;
|
import android.util.proto.ProtoOutputStream;
|
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.app.procstats.ProcessStats;
|
import com.android.server.LocalServices;
|
import com.android.server.ServiceThread;
|
import com.android.server.wm.ActivityServiceConnectionsHolder;
|
import com.android.server.wm.WindowProcessController;
|
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
|
/**
|
* All of the code required to compute proc states and oom_adj values.
|
*/
|
public final class OomAdjuster {
|
private static final String TAG = "OomAdjuster";
|
|
static final String OOM_ADJ_REASON_METHOD = "updateOomAdj";
|
static final String OOM_ADJ_REASON_NONE = OOM_ADJ_REASON_METHOD + "_meh";
|
static final String OOM_ADJ_REASON_ACTIVITY = OOM_ADJ_REASON_METHOD + "_activityChange";
|
static final String OOM_ADJ_REASON_FINISH_RECEIVER = OOM_ADJ_REASON_METHOD + "_finishReceiver";
|
static final String OOM_ADJ_REASON_START_RECEIVER = OOM_ADJ_REASON_METHOD + "_startReceiver";
|
static final String OOM_ADJ_REASON_BIND_SERVICE = OOM_ADJ_REASON_METHOD + "_bindService";
|
static final String OOM_ADJ_REASON_UNBIND_SERVICE = OOM_ADJ_REASON_METHOD + "_unbindService";
|
static final String OOM_ADJ_REASON_START_SERVICE = OOM_ADJ_REASON_METHOD + "_startService";
|
static final String OOM_ADJ_REASON_GET_PROVIDER = OOM_ADJ_REASON_METHOD + "_getProvider";
|
static final String OOM_ADJ_REASON_REMOVE_PROVIDER = OOM_ADJ_REASON_METHOD + "_removeProvider";
|
static final String OOM_ADJ_REASON_UI_VISIBILITY = OOM_ADJ_REASON_METHOD + "_uiVisibility";
|
static final String OOM_ADJ_REASON_WHITELIST = OOM_ADJ_REASON_METHOD + "_whitelistChange";
|
static final String OOM_ADJ_REASON_PROCESS_BEGIN = OOM_ADJ_REASON_METHOD + "_processBegin";
|
static final String OOM_ADJ_REASON_PROCESS_END = OOM_ADJ_REASON_METHOD + "_processEnd";
|
|
/**
|
* For some direct access we need to power manager.
|
*/
|
PowerManagerInternal mLocalPowerManager;
|
|
/**
|
* Service for compacting background apps.
|
*/
|
AppCompactor mAppCompact;
|
|
ActivityManagerConstants mConstants;
|
|
final long[] mTmpLong = new long[3];
|
|
/**
|
* Current sequence id for oom_adj computation traversal.
|
*/
|
int mAdjSeq = 0;
|
|
/**
|
* Keep track of the number of service processes we last found, to
|
* determine on the next iteration which should be B services.
|
*/
|
int mNumServiceProcs = 0;
|
int mNewNumAServiceProcs = 0;
|
int mNewNumServiceProcs = 0;
|
|
/**
|
* Keep track of the non-cached/empty process we last found, to help
|
* determine how to distribute cached/empty processes next time.
|
*/
|
int mNumNonCachedProcs = 0;
|
|
/**
|
* Keep track of the number of cached hidden procs, to balance oom adj
|
* distribution between those and empty procs.
|
*/
|
int mNumCachedHiddenProcs = 0;
|
|
/** Track all uids that have actively running processes. */
|
ActiveUids mActiveUids;
|
|
/**
|
* The handler to execute {@link #setProcessGroup} (it may be heavy if the process has many
|
* threads) for reducing the time spent in {@link #applyOomAdjLocked}.
|
*/
|
private final Handler mProcessGroupHandler;
|
|
private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet();
|
|
private final ActivityManagerService mService;
|
private final ProcessList mProcessList;
|
|
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
|
mService = service;
|
mProcessList = processList;
|
mActiveUids = activeUids;
|
|
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
|
mConstants = mService.mConstants;
|
mAppCompact = new AppCompactor(mService);
|
|
// The process group is usually critical to the response time of foreground app, so the
|
// setter should apply it as soon as possible.
|
final ServiceThread adjusterThread = new ServiceThread(TAG, TOP_APP_PRIORITY_BOOST,
|
false /* allowIo */);
|
adjusterThread.start();
|
Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP);
|
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup");
|
final int pid = msg.arg1;
|
final int group = msg.arg2;
|
try {
|
setProcessGroup(pid, group);
|
} catch (Exception e) {
|
if (DEBUG_ALL) {
|
Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
|
}
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
}
|
return true;
|
});
|
}
|
|
void initSettings() {
|
mAppCompact.init();
|
}
|
|
/**
|
* Update OomAdj for a specific process.
|
* @param app The process to update
|
* @param oomAdjAll If it's ok to call updateOomAdjLocked() for all running apps
|
* if necessary, or skip.
|
* @param oomAdjReason
|
* @return whether updateOomAdjLocked(app) was successful.
|
*/
|
@GuardedBy("mService")
|
boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll,
|
String oomAdjReason) {
|
final ProcessRecord TOP_APP = mService.getTopAppLocked();
|
final boolean wasCached = app.cached;
|
|
mAdjSeq++;
|
|
// This is the desired cached adjusment we want to tell it to use.
|
// If our app is currently cached, we know it, and that is it. Otherwise,
|
// we don't know it yet, and it needs to now be cached we will then
|
// need to do a complete oom adj.
|
final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
|
? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
|
boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
|
SystemClock.uptimeMillis());
|
if (oomAdjAll
|
&& (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
|
// Changed to/from cached state, so apps after it in the LRU
|
// list may also be changed.
|
updateOomAdjLocked(oomAdjReason);
|
}
|
return success;
|
}
|
|
@GuardedBy("mService")
|
private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
|
ProcessRecord TOP_APP, boolean doingAll, long now) {
|
if (app.thread == null) {
|
return false;
|
}
|
|
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false);
|
|
return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
|
}
|
|
@GuardedBy("mService")
|
void updateOomAdjLocked(String oomAdjReason) {
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReason);
|
mService.mOomAdjProfiler.oomAdjStarted();
|
final ProcessRecord TOP_APP = mService.getTopAppLocked();
|
final long now = SystemClock.uptimeMillis();
|
final long nowElapsed = SystemClock.elapsedRealtime();
|
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
|
final int N = mProcessList.getLruSizeLocked();
|
|
// Reset state in all uid records.
|
for (int i = mActiveUids.size() - 1; i >= 0; i--) {
|
final UidRecord uidRec = mActiveUids.valueAt(i);
|
if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
|
"Starting update of " + uidRec);
|
uidRec.reset();
|
}
|
|
if (mService.mAtmInternal != null) {
|
mService.mAtmInternal.rankTaskLayersIfNeeded();
|
}
|
|
mAdjSeq++;
|
mNewNumServiceProcs = 0;
|
mNewNumAServiceProcs = 0;
|
|
final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
|
final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
|
- emptyProcessLimit;
|
|
// Let's determine how many processes we have running vs.
|
// how many slots we have for background processes; we may want
|
// to put multiple processes in a slot of there are enough of
|
// them.
|
final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
|
- ProcessList.CACHED_APP_MIN_ADJ + 1) / 2
|
/ ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
|
int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
|
if (numEmptyProcs > cachedProcessLimit) {
|
// If there are more empty processes than our limit on cached
|
// processes, then use the cached process limit for the factor.
|
// This ensures that the really old empty processes get pushed
|
// down to the bottom, so if we are running low on memory we will
|
// have a better chance at keeping around more cached processes
|
// instead of a gazillion empty processes.
|
numEmptyProcs = cachedProcessLimit;
|
}
|
int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots;
|
if (emptyFactor < 1) emptyFactor = 1;
|
int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1)
|
/ numSlots;
|
if (cachedFactor < 1) cachedFactor = 1;
|
int stepCached = -1;
|
int stepEmpty = -1;
|
int numCached = 0;
|
int numCachedExtraGroup = 0;
|
int numEmpty = 0;
|
int numTrimming = 0;
|
int lastCachedGroup = 0;
|
int lastCachedGroupImportance = 0;
|
int lastCachedGroupUid = 0;
|
|
mNumNonCachedProcs = 0;
|
mNumCachedHiddenProcs = 0;
|
|
// First update the OOM adjustment for each of the
|
// application processes based on their current state.
|
int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
|
int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
|
int curCachedImpAdj = 0;
|
int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
|
int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
|
|
boolean retryCycles = false;
|
|
// need to reset cycle state before calling computeOomAdjLocked because of service conns
|
for (int i = N - 1; i >= 0; i--) {
|
ProcessRecord app = mProcessList.mLruProcesses.get(i);
|
app.containsCycle = false;
|
app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
|
app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
|
}
|
for (int i = N - 1; i >= 0; i--) {
|
ProcessRecord app = mProcessList.mLruProcesses.get(i);
|
if (!app.killedByAm && app.thread != null) {
|
app.procStateChanged = false;
|
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false);
|
|
// if any app encountered a cycle, we need to perform an additional loop later
|
retryCycles |= app.containsCycle;
|
|
// If we haven't yet assigned the final cached adj
|
// to the process, do that now.
|
if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
|
switch (app.getCurProcState()) {
|
case PROCESS_STATE_CACHED_ACTIVITY:
|
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
|
case ActivityManager.PROCESS_STATE_CACHED_RECENT:
|
// Figure out the next cached level, taking into account groups.
|
boolean inGroup = false;
|
if (app.connectionGroup != 0) {
|
if (lastCachedGroupUid == app.uid
|
&& lastCachedGroup == app.connectionGroup) {
|
// This is in the same group as the last process, just tweak
|
// adjustment by importance.
|
if (app.connectionImportance > lastCachedGroupImportance) {
|
lastCachedGroupImportance = app.connectionImportance;
|
if (curCachedAdj < nextCachedAdj
|
&& curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
|
curCachedImpAdj++;
|
}
|
}
|
inGroup = true;
|
} else {
|
lastCachedGroupUid = app.uid;
|
lastCachedGroup = app.connectionGroup;
|
lastCachedGroupImportance = app.connectionImportance;
|
}
|
}
|
if (!inGroup && curCachedAdj != nextCachedAdj) {
|
stepCached++;
|
curCachedImpAdj = 0;
|
if (stepCached >= cachedFactor) {
|
stepCached = 0;
|
curCachedAdj = nextCachedAdj;
|
nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
|
if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
|
nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
|
}
|
}
|
}
|
// This process is a cached process holding activities...
|
// assign it the next cached value for that type, and then
|
// step that cached level.
|
app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
|
app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
|
if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
|
+ " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
|
+ " curCachedImpAdj=" + curCachedImpAdj + ")");
|
break;
|
default:
|
// Figure out the next cached level.
|
if (curEmptyAdj != nextEmptyAdj) {
|
stepEmpty++;
|
if (stepEmpty >= emptyFactor) {
|
stepEmpty = 0;
|
curEmptyAdj = nextEmptyAdj;
|
nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
|
if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
|
nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
|
}
|
}
|
}
|
// For everything else, assign next empty cached process
|
// level and bump that up. Note that this means that
|
// long-running services that have dropped down to the
|
// cached level will be treated as empty (since their process
|
// state is still as a service), which is what we want.
|
app.setCurRawAdj(curEmptyAdj);
|
app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
|
if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
|
+ " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
|
+ ")");
|
break;
|
}
|
}
|
}
|
}
|
|
// Cycle strategy:
|
// - Retry computing any process that has encountered a cycle.
|
// - Continue retrying until no process was promoted.
|
// - Iterate from least important to most important.
|
int cycleCount = 0;
|
while (retryCycles && cycleCount < 10) {
|
cycleCount++;
|
retryCycles = false;
|
|
for (int i = 0; i < N; i++) {
|
ProcessRecord app = mProcessList.mLruProcesses.get(i);
|
if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
|
app.adjSeq--;
|
app.completedAdjSeq--;
|
}
|
}
|
|
for (int i = 0; i < N; i++) {
|
ProcessRecord app = mProcessList.mLruProcesses.get(i);
|
if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
|
if (computeOomAdjLocked(app, app.getCurRawAdj(), TOP_APP, true, now,
|
true)) {
|
retryCycles = true;
|
}
|
}
|
}
|
}
|
|
lastCachedGroup = lastCachedGroupUid = 0;
|
|
for (int i = N - 1; i >= 0; i--) {
|
ProcessRecord app = mProcessList.mLruProcesses.get(i);
|
if (!app.killedByAm && app.thread != null) {
|
applyOomAdjLocked(app, true, now, nowElapsed);
|
|
// Count the number of process types.
|
switch (app.getCurProcState()) {
|
case PROCESS_STATE_CACHED_ACTIVITY:
|
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
|
mNumCachedHiddenProcs++;
|
numCached++;
|
if (app.connectionGroup != 0) {
|
if (lastCachedGroupUid == app.info.uid
|
&& lastCachedGroup == app.connectionGroup) {
|
// If this process is the next in the same group, we don't
|
// want it to count against our limit of the number of cached
|
// processes, so bump up the group count to account for it.
|
numCachedExtraGroup++;
|
} else {
|
lastCachedGroupUid = app.info.uid;
|
lastCachedGroup = app.connectionGroup;
|
}
|
} else {
|
lastCachedGroupUid = lastCachedGroup = 0;
|
}
|
if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
|
app.kill("cached #" + numCached, true);
|
}
|
break;
|
case 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++;
|
if (numEmpty > emptyProcessLimit) {
|
app.kill("empty #" + numEmpty, true);
|
}
|
}
|
break;
|
default:
|
mNumNonCachedProcs++;
|
break;
|
}
|
|
if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
|
// If this is an isolated process, there are no services
|
// running in it, and it's not a special process with a
|
// custom entry point, then the process is no longer
|
// needed. We agressively kill these because we can by
|
// definition not re-use the same process again, and it is
|
// good to avoid having whatever code was running in them
|
// left sitting around after no longer needed.
|
app.kill("isolated not needed", true);
|
} else {
|
// Keeping this process, update its uid.
|
final UidRecord uidRec = app.uidRecord;
|
if (uidRec != null) {
|
uidRec.ephemeral = app.info.isInstantApp();
|
if (uidRec.getCurProcState() > app.getCurProcState()) {
|
uidRec.setCurProcState(app.getCurProcState());
|
}
|
if (app.hasForegroundServices()) {
|
uidRec.foregroundServices = true;
|
}
|
}
|
}
|
|
if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
|
&& !app.killedByAm) {
|
numTrimming++;
|
}
|
}
|
}
|
|
mService.incrementProcStateSeqAndNotifyAppsLocked();
|
|
mNumServiceProcs = mNewNumServiceProcs;
|
|
boolean allChanged = mService.updateLowMemStateLocked(numCached, numEmpty, numTrimming);
|
|
if (mService.mAlwaysFinishActivities) {
|
// Need to do this on its own message because the stack may not
|
// be in a consistent state at this point.
|
mService.mAtmInternal.scheduleDestroyAllActivities("always-finish");
|
}
|
|
if (allChanged) {
|
mService.requestPssAllProcsLocked(now, false,
|
mService.mProcessStats.isMemFactorLowered());
|
}
|
|
ArrayList<UidRecord> becameIdle = null;
|
|
// Update from any uid changes.
|
if (mLocalPowerManager != null) {
|
mLocalPowerManager.startUidChanges();
|
}
|
for (int i = mActiveUids.size() - 1; i >= 0; i--) {
|
final UidRecord uidRec = mActiveUids.valueAt(i);
|
int uidChange = UidRecord.CHANGE_PROCSTATE;
|
if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
|
&& (uidRec.setProcState != uidRec.getCurProcState()
|
|| uidRec.setWhitelist != uidRec.curWhitelist)) {
|
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
|
+ ": proc state from " + uidRec.setProcState + " to "
|
+ uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist
|
+ " to " + uidRec.curWhitelist);
|
if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
|
&& !uidRec.curWhitelist) {
|
// UID is now in the background (and not on the temp whitelist). Was it
|
// previously in the foreground (or on the temp whitelist)?
|
if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
|
|| uidRec.setWhitelist) {
|
uidRec.lastBackgroundTime = nowElapsed;
|
if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
|
// Note: the background settle time is in elapsed realtime, while
|
// the handler time base is uptime. All this means is that we may
|
// stop background uids later than we had intended, but that only
|
// happens because the device was sleeping so we are okay anyway.
|
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
|
mConstants.BACKGROUND_SETTLE_TIME);
|
}
|
}
|
if (uidRec.idle && !uidRec.setIdle) {
|
uidChange = UidRecord.CHANGE_IDLE;
|
if (becameIdle == null) {
|
becameIdle = new ArrayList<>();
|
}
|
becameIdle.add(uidRec);
|
}
|
} else {
|
if (uidRec.idle) {
|
uidChange = UidRecord.CHANGE_ACTIVE;
|
EventLogTags.writeAmUidActive(uidRec.uid);
|
uidRec.idle = false;
|
}
|
uidRec.lastBackgroundTime = 0;
|
}
|
final boolean wasCached = uidRec.setProcState
|
> ActivityManager.PROCESS_STATE_RECEIVER;
|
final boolean isCached = uidRec.getCurProcState()
|
> ActivityManager.PROCESS_STATE_RECEIVER;
|
if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
|
uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
|
}
|
uidRec.setProcState = uidRec.getCurProcState();
|
uidRec.setWhitelist = uidRec.curWhitelist;
|
uidRec.setIdle = uidRec.idle;
|
mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
|
mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
|
mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
|
if (uidRec.foregroundServices) {
|
mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
|
}
|
}
|
}
|
if (mLocalPowerManager != null) {
|
mLocalPowerManager.finishUidChanges();
|
}
|
|
if (becameIdle != null) {
|
// If we have any new uids that became idle this time, we need to make sure
|
// they aren't left with running services.
|
for (int i = becameIdle.size() - 1; i >= 0; i--) {
|
mService.mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
|
}
|
}
|
|
if (mService.mProcessStats.shouldWriteNowLocked(now)) {
|
mService.mHandler.post(new ActivityManagerService.ProcStatsRunnable(mService,
|
mService.mProcessStats));
|
}
|
|
// Run this after making sure all procstates are updated.
|
mService.mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
|
|
if (DEBUG_OOM_ADJ) {
|
final long duration = SystemClock.uptimeMillis() - now;
|
if (false) {
|
Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms",
|
new RuntimeException("here").fillInStackTrace());
|
} else {
|
Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
|
}
|
}
|
mService.mOomAdjProfiler.oomAdjEnded();
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
}
|
|
private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
|
new ComputeOomAdjWindowCallback();
|
|
/** These methods are called inline during computeOomAdjLocked(), on the same thread */
|
private final class ComputeOomAdjWindowCallback
|
implements WindowProcessController.ComputeOomAdjCallback {
|
|
ProcessRecord app;
|
int adj;
|
boolean foregroundActivities;
|
int procState;
|
int schedGroup;
|
int appUid;
|
int logUid;
|
int processStateCurTop;
|
|
void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
|
int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
|
this.app = app;
|
this.adj = adj;
|
this.foregroundActivities = foregroundActivities;
|
this.procState = procState;
|
this.schedGroup = schedGroup;
|
this.appUid = appUid;
|
this.logUid = logUid;
|
this.processStateCurTop = processStateCurTop;
|
}
|
|
@Override
|
public void onVisibleActivity() {
|
// App has a visible activity; only upgrade adjustment.
|
if (adj > ProcessList.VISIBLE_APP_ADJ) {
|
adj = ProcessList.VISIBLE_APP_ADJ;
|
app.adjType = "vis-activity";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
|
}
|
}
|
if (procState > processStateCurTop) {
|
procState = processStateCurTop;
|
app.adjType = "vis-activity";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to vis-activity (top): " + app);
|
}
|
}
|
if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
}
|
app.cached = false;
|
app.empty = false;
|
foregroundActivities = true;
|
}
|
|
@Override
|
public void onPausedActivity() {
|
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
|
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
|
app.adjType = "pause-activity";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app);
|
}
|
}
|
if (procState > processStateCurTop) {
|
procState = processStateCurTop;
|
app.adjType = "pause-activity";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to pause-activity (top): " + app);
|
}
|
}
|
if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
}
|
app.cached = false;
|
app.empty = false;
|
foregroundActivities = true;
|
}
|
|
@Override
|
public void onStoppingActivity(boolean finishing) {
|
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
|
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
|
app.adjType = "stop-activity";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise adj to stop-activity: " + app);
|
}
|
}
|
|
// For the process state, we will at this point consider the process to be cached. It
|
// will be cached either as an activity or empty depending on whether the activity is
|
// finishing. We do this so that we can treat the process as cached for purposes of
|
// memory trimming (determining current memory level, trim command to send to process)
|
// since there can be an arbitrary number of stopping processes and they should soon all
|
// go into the cached state.
|
if (!finishing) {
|
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
|
procState = PROCESS_STATE_LAST_ACTIVITY;
|
app.adjType = "stop-activity";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to stop-activity: " + app);
|
}
|
}
|
}
|
app.cached = false;
|
app.empty = false;
|
foregroundActivities = true;
|
}
|
|
@Override
|
public void onOtherActivity() {
|
if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
|
procState = PROCESS_STATE_CACHED_ACTIVITY;
|
app.adjType = "cch-act";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to cached activity: " + app);
|
}
|
}
|
}
|
}
|
|
private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,
|
ProcessRecord TOP_APP, boolean doingAll, long now, boolean cycleReEval) {
|
if (mAdjSeq == app.adjSeq) {
|
if (app.adjSeq == app.completedAdjSeq) {
|
// This adjustment has already been computed successfully.
|
return false;
|
} else {
|
// The process is being computed, so there is a cycle. We cannot
|
// rely on this process's state.
|
app.containsCycle = true;
|
|
return false;
|
}
|
}
|
|
if (app.thread == null) {
|
app.adjSeq = mAdjSeq;
|
app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
|
app.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
|
app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
|
app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
|
app.completedAdjSeq = app.adjSeq;
|
return false;
|
}
|
|
app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
|
app.adjSource = null;
|
app.adjTarget = null;
|
app.empty = false;
|
app.cached = false;
|
|
final WindowProcessController wpc = app.getWindowProcessController();
|
final int appUid = app.info.uid;
|
final int logUid = mService.mCurOomAdjUid;
|
|
int prevAppAdj = app.curAdj;
|
int prevProcState = app.getCurProcState();
|
|
if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
|
// The max adjustment doesn't allow this app to be anything
|
// below foreground, so it is not worth doing work for it.
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
mService.reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
|
}
|
app.adjType = "fixed";
|
app.adjSeq = mAdjSeq;
|
app.setCurRawAdj(app.maxAdj);
|
app.setHasForegroundActivities(false);
|
app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
|
app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
|
// System processes can do UI, and when they do we want to have
|
// them trim their memory after the user leaves the UI. To
|
// facilitate this, here we need to determine whether or not it
|
// is currently showing UI.
|
app.systemNoUi = true;
|
if (app == TOP_APP) {
|
app.systemNoUi = false;
|
app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
|
app.adjType = "pers-top-activity";
|
} else if (app.hasTopUi()) {
|
// sched group/proc state adjustment is below
|
app.systemNoUi = false;
|
app.adjType = "pers-top-ui";
|
} else if (wpc.hasVisibleActivities()) {
|
app.systemNoUi = false;
|
}
|
if (!app.systemNoUi) {
|
if (mService.mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
|
// screen on, promote UI
|
app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
|
app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
|
} else {
|
// screen off, restrict UI scheduling
|
app.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
|
app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
|
}
|
}
|
app.setCurRawProcState(app.getCurProcState());
|
app.curAdj = app.maxAdj;
|
app.completedAdjSeq = app.adjSeq;
|
// if curAdj is less than prevAppAdj, then this process was promoted
|
return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
|
}
|
|
app.systemNoUi = false;
|
|
final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState();
|
|
// Determine the importance of the process, starting with most
|
// important to least, and assign an appropriate OOM adjustment.
|
int adj;
|
int schedGroup;
|
int procState;
|
int cachedAdjSeq;
|
|
boolean foregroundActivities = false;
|
mTmpBroadcastQueue.clear();
|
if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP && app == TOP_APP) {
|
// The last app on the list is the foreground app.
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
|
app.adjType = "top-activity";
|
foregroundActivities = true;
|
procState = PROCESS_STATE_CUR_TOP;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
|
}
|
} else if (app.runningRemoteAnimation) {
|
adj = ProcessList.VISIBLE_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
|
app.adjType = "running-remote-anim";
|
procState = PROCESS_STATE_CUR_TOP;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
|
}
|
} else if (app.getActiveInstrumentation() != null) {
|
// Don't want to kill running instrumentation.
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
app.adjType = "instrumentation";
|
procState = PROCESS_STATE_FOREGROUND_SERVICE;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
|
}
|
} else if (mService.isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
|
// An app that is currently receiving a broadcast also
|
// counts as being in the foreground for OOM killer purposes.
|
// It's placed in a sched group based on the nature of the
|
// broadcast as reflected by which queue it's active in.
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
schedGroup = (mTmpBroadcastQueue.contains(mService.mFgBroadcastQueue))
|
? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
|
app.adjType = "broadcast";
|
procState = ActivityManager.PROCESS_STATE_RECEIVER;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
|
}
|
} else if (app.executingServices.size() > 0) {
|
// An app that is currently executing a service callback also
|
// counts as being in the foreground.
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
schedGroup = app.execServicesFg ?
|
ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
|
app.adjType = "exec-service";
|
procState = PROCESS_STATE_SERVICE;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
|
}
|
//Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
|
} else if (app == TOP_APP) {
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
|
app.adjType = "top-sleeping";
|
foregroundActivities = true;
|
procState = PROCESS_STATE_CUR_TOP;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app);
|
}
|
} else {
|
// As far as we know the process is empty. We may change our mind later.
|
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
|
// At this point we don't actually know the adjustment. Use the cached adj
|
// value that the caller wants us to.
|
adj = cachedAdj;
|
procState = PROCESS_STATE_CACHED_EMPTY;
|
app.cached = true;
|
app.empty = true;
|
app.adjType = "cch-empty";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
|
}
|
}
|
|
// Examine all activities if not already foreground.
|
if (!foregroundActivities && wpc.hasActivities()) {
|
mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState,
|
schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP);
|
final int minLayer = wpc.computeOomAdjFromActivities(
|
ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback);
|
|
adj = mTmpComputeOomAdjWindowCallback.adj;
|
foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities;
|
procState = mTmpComputeOomAdjWindowCallback.procState;
|
schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup;
|
|
if (adj == ProcessList.VISIBLE_APP_ADJ) {
|
adj += minLayer;
|
}
|
}
|
|
if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) {
|
procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
|
app.adjType = "cch-rec";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
|
}
|
}
|
|
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|
|| procState > PROCESS_STATE_FOREGROUND_SERVICE_LOCATION) {
|
if (app.hasForegroundServices()) {
|
// The user is aware of this app, so make it visible.
|
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
|
if (app.hasLocationForegroundServices()) {
|
procState = PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
|
app.adjType = "fg-service-location";
|
|
} else {
|
procState = PROCESS_STATE_FOREGROUND_SERVICE;
|
app.adjType = "fg-service";
|
}
|
app.cached = false;
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + app.adjType + ": "
|
+ app + " ");
|
}
|
} else if (app.hasOverlayUi()) {
|
// The process is display an overlay UI.
|
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
|
procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
|
app.cached = false;
|
app.adjType = "has-overlay-ui";
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
|
}
|
}
|
}
|
|
// If the app was recently in the foreground and moved to a foreground service status,
|
// allow it to get a higher rank in memory for some time, compared to other foreground
|
// services so that it can finish performing any persistence/processing of in-memory state.
|
if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
|
&& (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
|
|| app.setProcState <= PROCESS_STATE_TOP)) {
|
adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
|
app.adjType = "fg-service-act";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
|
}
|
}
|
|
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|
|| procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
|
if (app.forcingToImportant != null) {
|
// This is currently used for toasts... they are not interactive, and
|
// we don't want them to cause the app to become fully foreground (and
|
// thus out of background check), so we yes the best background level we can.
|
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
|
procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
|
app.cached = false;
|
app.adjType = "force-imp";
|
app.adjSource = app.forcingToImportant;
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
|
}
|
}
|
}
|
|
if (mService.mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) {
|
if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
|
// We don't want to kill the current heavy-weight process.
|
adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
|
app.cached = false;
|
app.adjType = "heavy";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
|
}
|
}
|
if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
|
procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
|
app.adjType = "heavy";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
|
}
|
}
|
}
|
|
if (wpc.isHomeProcess()) {
|
if (adj > ProcessList.HOME_APP_ADJ) {
|
// This process is hosting what we currently consider to be the
|
// home app, so we don't want to let it go into the background.
|
adj = ProcessList.HOME_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
|
app.cached = false;
|
app.adjType = "home";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
|
}
|
}
|
if (procState > ActivityManager.PROCESS_STATE_HOME) {
|
procState = ActivityManager.PROCESS_STATE_HOME;
|
app.adjType = "home";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
|
}
|
}
|
}
|
|
if (wpc.isPreviousProcess() && app.hasActivities()) {
|
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
|
// This was the previous process that showed UI to the user.
|
// We want to try to keep it around more aggressively, to give
|
// a good experience around switching between two apps.
|
adj = ProcessList.PREVIOUS_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
|
app.cached = false;
|
app.adjType = "previous";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
|
}
|
}
|
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
|
procState = PROCESS_STATE_LAST_ACTIVITY;
|
app.adjType = "previous";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
|
}
|
}
|
}
|
|
if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
|
+ " reason=" + app.adjType);
|
|
// By default, we use the computed adjustment. It may be changed if
|
// there are applications dependent on our services or providers, but
|
// this gives us a baseline and makes sure we don't get into an
|
// infinite recursion. If we're re-evaluating due to cycles, use the previously computed
|
// values.
|
app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj()));
|
app.setCurRawProcState(!cycleReEval
|
? procState
|
: Math.min(procState, app.getCurRawProcState()));
|
|
app.hasStartedServices = false;
|
app.adjSeq = mAdjSeq;
|
|
final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
|
if (backupTarget != null && app == backupTarget.app) {
|
// If possible we want to avoid killing apps while they're being backed up
|
if (adj > ProcessList.BACKUP_APP_ADJ) {
|
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
|
adj = ProcessList.BACKUP_APP_ADJ;
|
if (procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
|
procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
|
}
|
app.adjType = "backup";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
|
}
|
app.cached = false;
|
}
|
if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
|
procState = ActivityManager.PROCESS_STATE_BACKUP;
|
app.adjType = "backup";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
|
}
|
}
|
}
|
|
for (int is = app.services.size() - 1;
|
is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|
|| procState > PROCESS_STATE_TOP);
|
is--) {
|
ServiceRecord s = app.services.valueAt(is);
|
if (s.startRequested) {
|
app.hasStartedServices = true;
|
if (procState > PROCESS_STATE_SERVICE) {
|
procState = PROCESS_STATE_SERVICE;
|
app.adjType = "started-services";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to started service: " + app);
|
}
|
}
|
if (app.hasShownUi && !wpc.isHomeProcess()) {
|
// If this process has shown some UI, let it immediately
|
// go to the LRU list because it may be pretty heavy with
|
// UI stuff. We'll tag it with a label just to help
|
// debug and understand what is going on.
|
if (adj > ProcessList.SERVICE_ADJ) {
|
app.adjType = "cch-started-ui-services";
|
}
|
} else {
|
if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
|
// This service has seen some activity within
|
// recent memory, so we will keep its process ahead
|
// of the background processes.
|
if (adj > ProcessList.SERVICE_ADJ) {
|
adj = ProcessList.SERVICE_ADJ;
|
app.adjType = "started-services";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise adj to started service: " + app);
|
}
|
app.cached = false;
|
}
|
}
|
// If we have let the service slide into the background
|
// state, still have some text describing what it is doing
|
// even though the service no longer has an impact.
|
if (adj > ProcessList.SERVICE_ADJ) {
|
app.adjType = "cch-started-services";
|
}
|
}
|
}
|
|
ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections();
|
for (int conni = serviceConnections.size() - 1;
|
conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|
|| procState > PROCESS_STATE_TOP);
|
conni--) {
|
ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
|
for (int i = 0;
|
i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
|
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|
|| procState > PROCESS_STATE_TOP);
|
i++) {
|
// XXX should compute this based on the max of
|
// all connected clients.
|
ConnectionRecord cr = clist.get(i);
|
if (cr.binding.client == app) {
|
// Binding to oneself is not interesting.
|
continue;
|
}
|
|
boolean trackedProcState = false;
|
if ((cr.flags& Context.BIND_WAIVE_PRIORITY) == 0) {
|
ProcessRecord client = cr.binding.client;
|
computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
|
|
if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
|
continue;
|
}
|
|
int clientAdj = client.getCurRawAdj();
|
int clientProcState = client.getCurRawProcState();
|
|
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
|
// If the other app is cached for any reason, for purposes here
|
// we are going to consider it empty. The specific cached state
|
// doesn't propagate except under certain conditions.
|
clientProcState = PROCESS_STATE_CACHED_EMPTY;
|
}
|
String adjType = null;
|
if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
|
// Not doing bind OOM management, so treat
|
// this guy more like a started service.
|
if (app.hasShownUi && !wpc.isHomeProcess()) {
|
// If this process has shown some UI, let it immediately
|
// go to the LRU list because it may be pretty heavy with
|
// UI stuff. We'll tag it with a label just to help
|
// debug and understand what is going on.
|
if (adj > clientAdj) {
|
adjType = "cch-bound-ui-services";
|
}
|
app.cached = false;
|
clientAdj = adj;
|
clientProcState = procState;
|
} else {
|
if (now >= (s.lastActivity
|
+ mConstants.MAX_SERVICE_INACTIVITY)) {
|
// This service has not seen activity within
|
// recent memory, so allow it to drop to the
|
// LRU list if there is no other reason to keep
|
// it around. We'll also tag it with a label just
|
// to help debug and undertand what is going on.
|
if (adj > clientAdj) {
|
adjType = "cch-bound-services";
|
}
|
clientAdj = adj;
|
}
|
}
|
}
|
if (adj > clientAdj) {
|
// If this process has recently shown UI, and
|
// the process that is binding to it is less
|
// important than being visible, then we don't
|
// care about the binding as much as we care
|
// about letting this process get into the LRU
|
// list to be killed and restarted if needed for
|
// memory.
|
if (app.hasShownUi && !wpc.isHomeProcess()
|
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
|
if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
|
adjType = "cch-bound-ui-services";
|
}
|
} else {
|
int newAdj;
|
if ((cr.flags&(Context.BIND_ABOVE_CLIENT
|
|Context.BIND_IMPORTANT)) != 0) {
|
if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
|
newAdj = clientAdj;
|
} else {
|
// make this service persistent
|
newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
procState = ActivityManager.PROCESS_STATE_PERSISTENT;
|
cr.trackProcState(procState, mAdjSeq, now);
|
trackedProcState = true;
|
}
|
} else if ((cr.flags & Context.BIND_NOT_PERCEPTIBLE) != 0
|
&& clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
|
&& adj > ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
|
newAdj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
|
} else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
|
&& clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
|
&& adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
|
newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
|
} else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
|
newAdj = clientAdj;
|
} else {
|
if (adj > ProcessList.VISIBLE_APP_ADJ) {
|
// TODO: Is this too limiting for apps bound from TOP?
|
newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
|
} else {
|
newAdj = adj;
|
}
|
}
|
if (!client.cached) {
|
app.cached = false;
|
}
|
if (adj > newAdj) {
|
adj = newAdj;
|
app.setCurRawAdj(adj);
|
adjType = "service";
|
}
|
}
|
}
|
if ((cr.flags & (Context.BIND_NOT_FOREGROUND
|
| Context.BIND_IMPORTANT_BACKGROUND)) == 0) {
|
// This will treat important bound services identically to
|
// the top app, which may behave differently than generic
|
// foreground work.
|
final int curSchedGroup = client.getCurrentSchedulingGroup();
|
if (curSchedGroup > schedGroup) {
|
if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
|
schedGroup = curSchedGroup;
|
} else {
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
}
|
}
|
if (clientProcState < PROCESS_STATE_TOP) {
|
// Special handling for above-top states (persistent
|
// processes). These should not bring the current process
|
// into the top state, since they are not on top. Instead
|
// give them the best bound state after that.
|
final int bestState = cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)
|
? PROCESS_STATE_FOREGROUND_SERVICE_LOCATION
|
: PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
|
if ((cr.flags & Context.BIND_FOREGROUND_SERVICE) != 0) {
|
clientProcState = bestState;
|
} else if (mService.mWakefulness
|
== PowerManagerInternal.WAKEFULNESS_AWAKE
|
&& (cr.flags & Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
|
!= 0) {
|
clientProcState = bestState;
|
} else {
|
clientProcState =
|
PROCESS_STATE_IMPORTANT_FOREGROUND;
|
}
|
} else if (clientProcState == PROCESS_STATE_TOP) {
|
if (cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
|
// Go at most to BOUND_TOP, unless requested to elevate
|
// to client's state.
|
clientProcState = PROCESS_STATE_BOUND_TOP;
|
}
|
} else if (clientProcState
|
<= PROCESS_STATE_FOREGROUND_SERVICE) {
|
if (cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
|
clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
|
}
|
}
|
} else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) {
|
if (clientProcState <
|
PROCESS_STATE_TRANSIENT_BACKGROUND) {
|
clientProcState =
|
PROCESS_STATE_TRANSIENT_BACKGROUND;
|
}
|
} else {
|
if (clientProcState <
|
PROCESS_STATE_IMPORTANT_BACKGROUND) {
|
clientProcState =
|
PROCESS_STATE_IMPORTANT_BACKGROUND;
|
}
|
}
|
|
if (schedGroup < ProcessList.SCHED_GROUP_TOP_APP
|
&& (cr.flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0) {
|
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
|
}
|
|
if (!trackedProcState) {
|
cr.trackProcState(clientProcState, mAdjSeq, now);
|
}
|
|
if (procState > clientProcState) {
|
procState = clientProcState;
|
app.setCurRawProcState(procState);
|
if (adjType == null) {
|
adjType = "service";
|
}
|
}
|
if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
|
&& (cr.flags & Context.BIND_SHOWING_UI) != 0) {
|
app.setPendingUiClean(true);
|
}
|
if (adjType != null) {
|
app.adjType = adjType;
|
app.adjTypeCode = ActivityManager.RunningAppProcessInfo
|
.REASON_SERVICE_IN_USE;
|
app.adjSource = cr.binding.client;
|
app.adjSourceProcState = clientProcState;
|
app.adjTarget = s.instanceName;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
|
+ ": " + app + ", due to " + cr.binding.client
|
+ " adj=" + adj + " procState="
|
+ ProcessList.makeProcStateString(procState));
|
}
|
}
|
}
|
if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
|
app.treatLikeActivity = true;
|
}
|
final ActivityServiceConnectionsHolder a = cr.activity;
|
if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
|
if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
|
&& a.isActivityVisible()) {
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
app.setCurRawAdj(adj);
|
if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
|
if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
|
schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
|
} else {
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
}
|
}
|
app.cached = false;
|
app.adjType = "service";
|
app.adjTypeCode = ActivityManager.RunningAppProcessInfo
|
.REASON_SERVICE_IN_USE;
|
app.adjSource = a;
|
app.adjSourceProcState = procState;
|
app.adjTarget = s.instanceName;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise to service w/activity: " + app);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
for (int provi = app.pubProviders.size() - 1;
|
provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|
|| procState > PROCESS_STATE_TOP);
|
provi--) {
|
ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
|
for (int i = cpr.connections.size() - 1;
|
i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
|
|| procState > PROCESS_STATE_TOP);
|
i--) {
|
ContentProviderConnection conn = cpr.connections.get(i);
|
ProcessRecord client = conn.client;
|
if (client == app) {
|
// Being our own client is not interesting.
|
continue;
|
}
|
computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
|
|
if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
|
continue;
|
}
|
|
int clientAdj = client.getCurRawAdj();
|
int clientProcState = client.getCurRawProcState();
|
|
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
|
// If the other app is cached for any reason, for purposes here
|
// we are going to consider it empty.
|
clientProcState = PROCESS_STATE_CACHED_EMPTY;
|
}
|
String adjType = null;
|
if (adj > clientAdj) {
|
if (app.hasShownUi && !wpc.isHomeProcess()
|
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
|
adjType = "cch-ui-provider";
|
} else {
|
adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
|
? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
|
app.setCurRawAdj(adj);
|
adjType = "provider";
|
}
|
app.cached &= client.cached;
|
}
|
|
if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
|
if (adjType == null) {
|
adjType = "provider";
|
}
|
if (clientProcState == PROCESS_STATE_TOP) {
|
clientProcState = PROCESS_STATE_BOUND_TOP;
|
} else {
|
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
|
}
|
}
|
|
conn.trackProcState(clientProcState, mAdjSeq, now);
|
if (procState > clientProcState) {
|
procState = clientProcState;
|
app.setCurRawProcState(procState);
|
}
|
if (client.getCurrentSchedulingGroup() > schedGroup) {
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
}
|
if (adjType != null) {
|
app.adjType = adjType;
|
app.adjTypeCode = ActivityManager.RunningAppProcessInfo
|
.REASON_PROVIDER_IN_USE;
|
app.adjSource = client;
|
app.adjSourceProcState = clientProcState;
|
app.adjTarget = cpr.name;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
|
+ ": " + app + ", due to " + client
|
+ " adj=" + adj + " procState="
|
+ ProcessList.makeProcStateString(procState));
|
}
|
}
|
}
|
// If the provider has external (non-framework) process
|
// dependencies, ensure that its adjustment is at least
|
// FOREGROUND_APP_ADJ.
|
if (cpr.hasExternalProcessHandles()) {
|
if (adj > ProcessList.FOREGROUND_APP_ADJ) {
|
adj = ProcessList.FOREGROUND_APP_ADJ;
|
app.setCurRawAdj(adj);
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
app.cached = false;
|
app.adjType = "ext-provider";
|
app.adjTarget = cpr.name;
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise adj to external provider: " + app);
|
}
|
}
|
if (procState > PROCESS_STATE_IMPORTANT_FOREGROUND) {
|
procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
|
app.setCurRawProcState(procState);
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to external provider: " + app);
|
}
|
}
|
}
|
}
|
|
if (app.lastProviderTime > 0 &&
|
(app.lastProviderTime + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
|
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
|
adj = ProcessList.PREVIOUS_APP_ADJ;
|
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
|
app.cached = false;
|
app.adjType = "recent-provider";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise adj to recent provider: " + app);
|
}
|
}
|
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
|
procState = PROCESS_STATE_LAST_ACTIVITY;
|
app.adjType = "recent-provider";
|
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
|
reportOomAdjMessageLocked(TAG_OOM_ADJ,
|
"Raise procstate to recent provider: " + app);
|
}
|
}
|
}
|
|
if (procState >= PROCESS_STATE_CACHED_EMPTY) {
|
if (app.hasClientActivities()) {
|
// This is a cached process, but with client activities. Mark it so.
|
procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
|
app.adjType = "cch-client-act";
|
} else if (app.treatLikeActivity) {
|
// This is a cached process, but somebody wants us to treat it like it has
|
// an activity, okay!
|
procState = PROCESS_STATE_CACHED_ACTIVITY;
|
app.adjType = "cch-as-act";
|
}
|
}
|
|
if (adj == ProcessList.SERVICE_ADJ) {
|
if (doingAll) {
|
app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
|
mNewNumServiceProcs++;
|
//Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
|
if (!app.serviceb) {
|
// This service isn't far enough down on the LRU list to
|
// normally be a B service, but if we are low on RAM and it
|
// is large we want to force it down since we would prefer to
|
// keep launcher over it.
|
if (mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
|
&& app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
|
app.serviceHighRam = true;
|
app.serviceb = true;
|
//Slog.i(TAG, "ADJ " + app + " high ram!");
|
} else {
|
mNewNumAServiceProcs++;
|
//Slog.i(TAG, "ADJ " + app + " not high ram!");
|
}
|
} else {
|
app.serviceHighRam = false;
|
}
|
}
|
if (app.serviceb) {
|
adj = ProcessList.SERVICE_B_ADJ;
|
}
|
}
|
|
app.setCurRawAdj(adj);
|
|
//Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
|
// " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
|
if (adj > app.maxAdj) {
|
adj = app.maxAdj;
|
if (app.maxAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
|
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
|
}
|
}
|
|
// Put bound foreground services in a special sched group for additional
|
// restrictions on screen off
|
if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
|
&& mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
|
if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
|
schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
|
}
|
}
|
|
// Do final modification to adj. Everything we do between here and applying
|
// the final setAdj must be done in this function, because we will also use
|
// it when computing the final cached adj later. Note that we don't need to
|
// worry about this for max adj above, since max adj will always be used to
|
// keep it out of the cached vaues.
|
app.curAdj = app.modifyRawOomAdj(adj);
|
app.setCurrentSchedulingGroup(schedGroup);
|
app.setCurProcState(procState);
|
app.setCurRawProcState(procState);
|
app.setHasForegroundActivities(foregroundActivities);
|
app.completedAdjSeq = mAdjSeq;
|
|
// if curAdj or curProcState improved, then this process was promoted
|
return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
|
}
|
|
/**
|
* Checks if for the given app and client, there's a cycle that should skip over the client
|
* for now or use partial values to evaluate the effect of the client binding.
|
* @param app
|
* @param client
|
* @param procState procstate evaluated so far for this app
|
* @param adj oom_adj evaluated so far for this app
|
* @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first
|
* evaluation.
|
* @return whether to skip using the client connection at this time
|
*/
|
private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
|
int procState, int adj, boolean cycleReEval) {
|
if (client.containsCycle) {
|
// We've detected a cycle. We should retry computeOomAdjLocked later in
|
// case a later-checked connection from a client would raise its
|
// priority legitimately.
|
app.containsCycle = true;
|
// If the client has not been completely evaluated, check if it's worth
|
// using the partial values.
|
if (client.completedAdjSeq < mAdjSeq) {
|
if (cycleReEval) {
|
// If the partial values are no better, skip until the next
|
// attempt
|
if (client.getCurRawProcState() >= procState
|
&& client.getCurRawAdj() >= adj) {
|
return true;
|
}
|
// Else use the client's partial procstate and adj to adjust the
|
// effect of the binding
|
} else {
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
/** Inform the oomadj observer of changes to oomadj. Used by tests. */
|
@GuardedBy("mService")
|
void reportOomAdjMessageLocked(String tag, String msg) {
|
Slog.d(tag, msg);
|
if (mService.mCurOomAdjObserver != null) {
|
mService.mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
|
}
|
}
|
|
/** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
|
@GuardedBy("mService")
|
private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
|
long nowElapsed) {
|
boolean success = true;
|
|
if (app.getCurRawAdj() != app.setRawAdj) {
|
app.setRawAdj = app.getCurRawAdj();
|
}
|
|
int changes = 0;
|
|
// don't compact during bootup
|
if (mAppCompact.useCompaction() && mService.mBooted) {
|
// Cached and prev/home compaction
|
if (app.curAdj != app.setAdj) {
|
// Perform a minor compaction when a perceptible app becomes the prev/home app
|
// Perform a major compaction when any app enters cached
|
// reminder: here, setAdj is previous state, curAdj is upcoming state
|
if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
|
(app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
|
app.curAdj == ProcessList.HOME_APP_ADJ)) {
|
mAppCompact.compactAppSome(app);
|
} else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
|
|| app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
|
&& app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
|
&& app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
|
mAppCompact.compactAppFull(app);
|
}
|
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
|
&& app.setAdj < ProcessList.FOREGROUND_APP_ADJ
|
// Because these can fire independent of oom_adj/procstate changes, we need
|
// to throttle the actual dispatch of these requests in addition to the
|
// processing of the requests. As a result, there is throttling both here
|
// and in AppCompactor.
|
&& mAppCompact.shouldCompactPersistent(app, now)) {
|
mAppCompact.compactAppPersistent(app);
|
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
|
&& app.getCurProcState()
|
== ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
|
&& mAppCompact.shouldCompactBFGS(app, now)) {
|
mAppCompact.compactAppBfgs(app);
|
}
|
}
|
|
if (app.curAdj != app.setAdj) {
|
ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
|
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
|
String msg = "Set " + app.pid + " " + app.processName + " adj "
|
+ app.curAdj + ": " + app.adjType;
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
|
}
|
app.setAdj = app.curAdj;
|
app.verifiedAdj = ProcessList.INVALID_ADJ;
|
}
|
|
final int curSchedGroup = app.getCurrentSchedulingGroup();
|
if (app.setSchedGroup != curSchedGroup) {
|
int oldSchedGroup = app.setSchedGroup;
|
app.setSchedGroup = curSchedGroup;
|
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
|
String msg = "Setting sched group of " + app.processName
|
+ " to " + curSchedGroup + ": " + app.adjType;
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
|
}
|
if (app.waitingToKill != null && app.curReceivers.isEmpty()
|
&& app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
|
app.kill(app.waitingToKill, true);
|
success = false;
|
} else {
|
int processGroup;
|
switch (curSchedGroup) {
|
case ProcessList.SCHED_GROUP_BACKGROUND:
|
processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
|
break;
|
case ProcessList.SCHED_GROUP_TOP_APP:
|
case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
|
processGroup = THREAD_GROUP_TOP_APP;
|
break;
|
case ProcessList.SCHED_GROUP_RESTRICTED:
|
processGroup = THREAD_GROUP_RESTRICTED;
|
break;
|
default:
|
processGroup = THREAD_GROUP_DEFAULT;
|
break;
|
}
|
mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
|
0 /* unused */, app.pid, processGroup));
|
try {
|
if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
|
// do nothing if we already switched to RT
|
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
|
app.getWindowProcessController().onTopProcChanged();
|
if (mService.mUseFifoUiScheduling) {
|
// Switch UI pipeline for app to SCHED_FIFO
|
app.savedPriority = Process.getThreadPriority(app.pid);
|
mService.scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
|
if (app.renderThreadTid != 0) {
|
mService.scheduleAsFifoPriority(app.renderThreadTid,
|
/* suppressLogs */true);
|
if (DEBUG_OOM_ADJ) {
|
Slog.d("UI_FIFO", "Set RenderThread (TID " +
|
app.renderThreadTid + ") to FIFO");
|
}
|
} else {
|
if (DEBUG_OOM_ADJ) {
|
Slog.d("UI_FIFO", "Not setting RenderThread TID");
|
}
|
}
|
} else {
|
// Boost priority for top app UI and render threads
|
setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
|
if (app.renderThreadTid != 0) {
|
try {
|
setThreadPriority(app.renderThreadTid,
|
TOP_APP_PRIORITY_BOOST);
|
} catch (IllegalArgumentException e) {
|
// thread died, ignore
|
}
|
}
|
}
|
}
|
} else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
|
curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
|
app.getWindowProcessController().onTopProcChanged();
|
if (mService.mUseFifoUiScheduling) {
|
try {
|
// Reset UI pipeline to SCHED_OTHER
|
setThreadScheduler(app.pid, SCHED_OTHER, 0);
|
setThreadPriority(app.pid, app.savedPriority);
|
if (app.renderThreadTid != 0) {
|
setThreadScheduler(app.renderThreadTid,
|
SCHED_OTHER, 0);
|
setThreadPriority(app.renderThreadTid, -4);
|
}
|
} catch (IllegalArgumentException e) {
|
Slog.w(TAG,
|
"Failed to set scheduling policy, thread does not exist:\n"
|
+ e);
|
} catch (SecurityException e) {
|
Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
|
}
|
} else {
|
// Reset priority for top app UI and render threads
|
setThreadPriority(app.pid, 0);
|
if (app.renderThreadTid != 0) {
|
setThreadPriority(app.renderThreadTid, 0);
|
}
|
}
|
}
|
} catch (Exception e) {
|
if (DEBUG_ALL) {
|
Slog.w(TAG, "Failed setting thread priority of " + app.pid, e);
|
}
|
}
|
}
|
}
|
if (app.repForegroundActivities != app.hasForegroundActivities()) {
|
app.repForegroundActivities = app.hasForegroundActivities();
|
changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
|
}
|
if (app.getReportedProcState() != app.getCurProcState()) {
|
app.setReportedProcState(app.getCurProcState());
|
if (app.thread != null) {
|
try {
|
if (false) {
|
//RuntimeException h = new RuntimeException("here");
|
Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
|
+ " to " + app /*, h*/);
|
}
|
app.thread.setProcessState(app.getReportedProcState());
|
} catch (RemoteException e) {
|
}
|
}
|
}
|
if (app.setProcState == PROCESS_STATE_NONEXISTENT
|
|| ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
|
if (false && mService.mTestPssMode
|
&& app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
|
// Experimental code to more aggressively collect pss while
|
// running test... the problem is that this tends to collect
|
// the data right when a process is transitioning between process
|
// states, which will tend to give noisy data.
|
long start = SystemClock.uptimeMillis();
|
long startTime = SystemClock.currentThreadTimeMillis();
|
long pss = Debug.getPss(app.pid, mTmpLong, null);
|
long endTime = SystemClock.currentThreadTimeMillis();
|
mService.recordPssSampleLocked(app, app.getCurProcState(), pss,
|
mTmpLong[0], mTmpLong[1], mTmpLong[2],
|
ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now);
|
mService.mPendingPssProcesses.remove(app);
|
Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
|
+ " to " + app.getCurProcState() + ": "
|
+ (SystemClock.uptimeMillis()-start) + "ms");
|
}
|
app.lastStateTime = now;
|
app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
|
app.procStateMemTracker, mService.mTestPssMode,
|
mService.mAtmInternal.isSleeping(), now);
|
if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from "
|
+ ProcessList.makeProcStateString(app.setProcState) + " to "
|
+ ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
|
+ (app.nextPssTime-now) + ": " + app);
|
} else {
|
if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
|
&& now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
|
mService.mTestPssMode)))) {
|
if (mService.requestPssLocked(app, app.setProcState)) {
|
app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
|
app.procStateMemTracker, mService.mTestPssMode,
|
mService.mAtmInternal.isSleeping(), now);
|
}
|
} else if (false && DEBUG_PSS) {
|
Slog.d(TAG_PSS,
|
"Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
|
}
|
}
|
if (app.setProcState != app.getCurProcState()) {
|
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
|
String msg = "Proc state change of " + app.processName
|
+ " to " + ProcessList.makeProcStateString(app.getCurProcState())
|
+ " (" + app.getCurProcState() + ")" + ": " + app.adjType;
|
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
|
}
|
boolean setImportant = app.setProcState < PROCESS_STATE_SERVICE;
|
boolean curImportant = app.getCurProcState() < PROCESS_STATE_SERVICE;
|
if (setImportant && !curImportant) {
|
// This app is no longer something we consider important enough to allow to use
|
// arbitrary amounts of battery power. Note its current CPU time to later know to
|
// kill it if it is not behaving well.
|
app.setWhenUnimportant(now);
|
app.lastCpuTime = 0;
|
}
|
// Inform UsageStats of important process state change
|
// Must be called before updating setProcState
|
maybeUpdateUsageStatsLocked(app, nowElapsed);
|
|
maybeUpdateLastTopTime(app, now);
|
|
app.setProcState = app.getCurProcState();
|
if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
|
app.notCachedSinceIdle = false;
|
}
|
if (!doingAll) {
|
mService.setProcessTrackerStateLocked(app,
|
mService.mProcessStats.getMemFactorLocked(), now);
|
} else {
|
app.procStateChanged = true;
|
}
|
} else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
|
> mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
|
// For apps that sit around for a long time in the interactive state, we need
|
// to report this at least once a day so they don't go idle.
|
maybeUpdateUsageStatsLocked(app, nowElapsed);
|
} else if (!app.reportedInteraction && (nowElapsed - app.getFgInteractionTime())
|
> mConstants.SERVICE_USAGE_INTERACTION_TIME) {
|
// For foreground services that sit around for a long time but are not interacted with.
|
maybeUpdateUsageStatsLocked(app, nowElapsed);
|
}
|
|
if (changes != 0) {
|
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
|
"Changes in " + app + ": " + changes);
|
ActivityManagerService.ProcessChangeItem item =
|
mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
|
item.changes = changes;
|
item.foregroundActivities = app.repForegroundActivities;
|
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
|
"Item " + Integer.toHexString(System.identityHashCode(item))
|
+ " " + app.toShortString() + ": changes=" + item.changes
|
+ " foreground=" + item.foregroundActivities
|
+ " type=" + app.adjType + " source=" + app.adjSource
|
+ " target=" + app.adjTarget);
|
}
|
|
return success;
|
}
|
|
// ONLY used for unit testing in OomAdjusterTests.java
|
@VisibleForTesting
|
void maybeUpdateUsageStats(ProcessRecord app, long nowElapsed) {
|
synchronized (mService) {
|
maybeUpdateUsageStatsLocked(app, nowElapsed);
|
}
|
}
|
|
@GuardedBy("mService")
|
private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
|
if (DEBUG_USAGE_STATS) {
|
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
|
+ "] state changes: old = " + app.setProcState + ", new = "
|
+ app.getCurProcState());
|
}
|
if (mService.mUsageStatsService == null) {
|
return;
|
}
|
boolean isInteraction;
|
// To avoid some abuse patterns, we are going to be careful about what we consider
|
// to be an app interaction. Being the top activity doesn't count while the display
|
// is sleeping, nor do short foreground services.
|
if (app.getCurProcState() <= PROCESS_STATE_TOP
|
|| app.getCurProcState() == PROCESS_STATE_BOUND_TOP) {
|
isInteraction = true;
|
app.setFgInteractionTime(0);
|
} else if (app.getCurProcState() <= PROCESS_STATE_FOREGROUND_SERVICE) {
|
if (app.getFgInteractionTime() == 0) {
|
app.setFgInteractionTime(nowElapsed);
|
isInteraction = false;
|
} else {
|
isInteraction = nowElapsed > app.getFgInteractionTime()
|
+ mConstants.SERVICE_USAGE_INTERACTION_TIME;
|
}
|
} else {
|
isInteraction =
|
app.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
|
app.setFgInteractionTime(0);
|
}
|
if (isInteraction
|
&& (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
|
> mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
|
app.setInteractionEventTime(nowElapsed);
|
String[] packages = app.getPackageList();
|
if (packages != null) {
|
for (int i = 0; i < packages.length; i++) {
|
mService.mUsageStatsService.reportEvent(packages[i], app.userId,
|
UsageEvents.Event.SYSTEM_INTERACTION);
|
}
|
}
|
}
|
app.reportedInteraction = isInteraction;
|
if (!isInteraction) {
|
app.setInteractionEventTime(0);
|
}
|
}
|
|
private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
|
if (app.setProcState <= PROCESS_STATE_TOP
|
&& app.getCurProcState() > PROCESS_STATE_TOP) {
|
app.lastTopTime = nowUptime;
|
}
|
}
|
|
/**
|
* Look for recently inactive apps and mark them idle after a grace period. If idled, stop
|
* any background services and inform listeners.
|
*/
|
@GuardedBy("mService")
|
void idleUidsLocked() {
|
final int N = mActiveUids.size();
|
if (N <= 0) {
|
return;
|
}
|
final long nowElapsed = SystemClock.elapsedRealtime();
|
final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
|
long nextTime = 0;
|
if (mLocalPowerManager != null) {
|
mLocalPowerManager.startUidChanges();
|
}
|
for (int i = N - 1; i >= 0; i--) {
|
final UidRecord uidRec = mActiveUids.valueAt(i);
|
final long bgTime = uidRec.lastBackgroundTime;
|
if (bgTime > 0 && !uidRec.idle) {
|
if (bgTime <= maxBgTime) {
|
EventLogTags.writeAmUidIdle(uidRec.uid);
|
uidRec.idle = true;
|
uidRec.setIdle = true;
|
mService.doStopUidLocked(uidRec.uid, uidRec);
|
} else {
|
if (nextTime == 0 || nextTime > bgTime) {
|
nextTime = bgTime;
|
}
|
}
|
}
|
}
|
if (mLocalPowerManager != null) {
|
mLocalPowerManager.finishUidChanges();
|
}
|
if (nextTime > 0) {
|
mService.mHandler.removeMessages(IDLE_UIDS_MSG);
|
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
|
nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
|
}
|
}
|
|
@GuardedBy("mService")
|
final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
|
boolean changed = false;
|
for (int i = mActiveUids.size() - 1; i >= 0; i--) {
|
final UidRecord uidRec = mActiveUids.valueAt(i);
|
if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) {
|
uidRec.curWhitelist = onWhitelist;
|
changed = true;
|
}
|
}
|
if (changed) {
|
updateOomAdjLocked(OOM_ADJ_REASON_WHITELIST);
|
}
|
}
|
|
@GuardedBy("mService")
|
final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
|
boolean changed = false;
|
final UidRecord uidRec = mActiveUids.get(uid);
|
if (uidRec != null && uidRec.curWhitelist != onWhitelist) {
|
uidRec.curWhitelist = onWhitelist;
|
updateOomAdjLocked(OOM_ADJ_REASON_WHITELIST);
|
}
|
}
|
|
@GuardedBy("mService")
|
void dumpProcessListVariablesLocked(ProtoOutputStream proto) {
|
proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
|
proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
|
proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS,
|
mNumNonCachedProcs);
|
proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
|
proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS,
|
mNewNumServiceProcs);
|
|
}
|
|
@GuardedBy("mService")
|
void dumpSequenceNumbersLocked(PrintWriter pw) {
|
pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
|
}
|
|
@GuardedBy("mService")
|
void dumpProcCountsLocked(PrintWriter pw) {
|
pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs
|
+ " (" + mProcessList.getLruSizeLocked() + " total)"
|
+ " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
|
+ " mNumServiceProcs=" + mNumServiceProcs
|
+ " mNewNumServiceProcs=" + mNewNumServiceProcs);
|
}
|
|
@GuardedBy("mService")
|
void dumpAppCompactorSettings(PrintWriter pw) {
|
mAppCompact.dump(pw);
|
}
|
}
|