Improve handling of low memory.
Now classify background processes into a set of bins of how much
memory they should try to clear. The last bin also involves
destroying all activities in that process.
Removed the old code for the simulator that is no longer needed
(yay). The debugging features it had are now integrated into the
regular oom adj code.
Small fixes to load average service.
Change-Id: Ic8df401714b188c73b50dbc8f8e6345b58f1f3a0
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index b1da69f..93d8164 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -52,6 +52,7 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -83,6 +84,8 @@
static final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION;
static final boolean DEBUG_TASKS = ActivityManagerService.DEBUG_TASKS;
+ static final boolean DEBUG_STATES = false;
+
static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS;
// How long we wait until giving up on the last activity telling us it
@@ -392,19 +395,25 @@
}
final int indexOfTokenLocked(IBinder token) {
- int count = mHistory.size();
-
- // convert the token to an entry in the history.
- int index = -1;
- for (int i=count-1; i>=0; i--) {
- Object o = mHistory.get(i);
- if (o == token) {
- index = i;
- break;
- }
+ try {
+ ActivityRecord r = (ActivityRecord)token;
+ return mHistory.indexOf(r);
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Bad activity token: " + token, e);
+ return -1;
}
+ }
- return index;
+ final ActivityRecord isInStackLocked(IBinder token) {
+ try {
+ ActivityRecord r = (ActivityRecord)token;
+ if (mHistory.contains(r)) {
+ return r;
+ }
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Bad activity token: " + token, e);
+ }
+ return null;
}
private final boolean updateLRUListLocked(ActivityRecord r) {
@@ -604,6 +613,8 @@
// As part of the process of launching, ActivityThread also performs
// a resume.
r.state = ActivityState.RESUMED;
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + r
+ + " (starting new instance)");
r.stopped = false;
mResumedActivity = r;
r.task.touchActiveTime();
@@ -617,6 +628,8 @@
// should look like we asked it to pause+stop (but remain visible),
// and it has done so and reported back the current icicle and
// other state.
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r
+ + " (starting in stopped state)");
r.state = ActivityState.STOPPED;
r.stopped = true;
}
@@ -797,7 +810,8 @@
resumeTopActivityLocked(null);
return;
}
- if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev);
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev);
+ else if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev);
mResumedActivity = null;
mPausingActivity = prev;
mLastPausedActivity = prev;
@@ -879,6 +893,8 @@
r = mHistory.get(index);
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r
+ + (timeout ? " (due to timeout)" : " (pause complete)"));
r.state = ActivityState.PAUSED;
completePauseLocked();
} else {
@@ -891,6 +907,22 @@
}
}
+ final void activityStoppedLocked(ActivityRecord r, Bundle icicle, Bitmap thumbnail,
+ CharSequence description) {
+ r.icicle = icicle;
+ r.haveState = true;
+ r.updateThumbnail(thumbnail, description);
+ r.stopped = true;
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)");
+ r.state = ActivityState.STOPPED;
+ if (!r.finishing) {
+ if (r.configDestroy) {
+ destroyActivityLocked(r, true, false);
+ resumeTopActivityLocked(null);
+ }
+ }
+ }
+
private final void completePauseLocked() {
ActivityRecord prev = mPausingActivity;
if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev);
@@ -914,7 +946,7 @@
// instance right now, we need to first completely stop
// the current instance before starting the new one.
if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev);
- destroyActivityLocked(prev, true);
+ destroyActivityLocked(prev, true, false);
} else {
mStoppingActivities.add(prev);
if (mStoppingActivities.size() > 3) {
@@ -1371,6 +1403,7 @@
mService.updateCpuStats();
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + next + " (in existing)");
next.state = ActivityState.RESUMED;
mResumedActivity = next;
next.task.touchActiveTime();
@@ -1447,6 +1480,8 @@
} catch (Exception e) {
// Whoops, need to restart this activity!
+ if (DEBUG_STATES) Slog.v(TAG, "Resume failed; resetting state to "
+ + lastState + ": " + next);
next.state = lastState;
mResumedActivity = lastResumedActivity;
Slog.i(TAG, "Restarting because process died: " + next);
@@ -2960,6 +2995,8 @@
r.resumeKeyDispatchingLocked();
try {
r.stopped = false;
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
+ + " (stop requested)");
r.state = ActivityState.STOPPING;
if (DEBUG_VISBILITY) Slog.v(
TAG, "Stopping visible=" + r.visible + " for " + r);
@@ -2977,9 +3014,10 @@
Slog.w(TAG, "Exception thrown during pause", e);
// Just in case, assume it to be stopped.
r.stopped = true;
+ if (DEBUG_STATES) Slog.v(TAG, "Stop failed; moving to STOPPED: " + r);
r.state = ActivityState.STOPPED;
if (r.configDestroy) {
- destroyActivityLocked(r, true);
+ destroyActivityLocked(r, true, false);
}
}
}
@@ -3145,7 +3183,7 @@
for (i=0; i<NF; i++) {
ActivityRecord r = (ActivityRecord)finishes.get(i);
synchronized (mService) {
- destroyActivityLocked(r, true);
+ destroyActivityLocked(r, true, false);
}
}
@@ -3340,6 +3378,8 @@
checkReadyForSleepLocked();
}
}
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
+ + " (finish requested)");
r.state = ActivityState.STOPPING;
mService.updateOomAdjLocked();
return r;
@@ -3353,6 +3393,7 @@
mResumedActivity = null;
}
final ActivityState prevState = r.state;
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to FINISHING: " + r);
r.state = ActivityState.FINISHING;
if (mode == FINISH_IMMEDIATELY
@@ -3360,7 +3401,7 @@
|| prevState == ActivityState.INITIALIZING) {
// If this activity is already stopped, we can just finish
// it right now.
- return destroyActivityLocked(r, true) ? null : r;
+ return destroyActivityLocked(r, true, true) ? null : r;
} else {
// Need to go through the full pause cycle to get this
// activity into the stopped state and then finish it.
@@ -3378,7 +3419,8 @@
* processing going away, in which case there is no remaining client-side
* state to destroy so only the cleanup here is needed.
*/
- final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices) {
+ final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices,
+ boolean setState) {
if (mResumedActivity == r) {
mResumedActivity = null;
}
@@ -3389,6 +3431,11 @@
r.configDestroy = false;
r.frozenBeforeDestroy = false;
+ if (setState) {
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (cleaning up)");
+ r.state = ActivityState.DESTROYED;
+ }
+
// Make sure this record is no longer in the pending finishes list.
// This could happen, for example, if we are trimming activities
// down to the max limit while they are still waiting to finish.
@@ -3428,6 +3475,8 @@
r.makeFinishing();
mHistory.remove(r);
r.takeFromHistory();
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r
+ + " (removed from history)");
r.state = ActivityState.DESTROYED;
mService.mWindowManager.removeAppToken(r);
if (VALIDATE_TOKENS) {
@@ -3453,6 +3502,22 @@
}
}
+ final void destroyActivitiesLocked(ProcessRecord owner, boolean oomAdj) {
+ for (int i=mHistory.size()-1; i>=0; i--) {
+ ActivityRecord r = mHistory.get(i);
+ if (owner != null && r.app != owner) {
+ continue;
+ }
+ // We can destroy this one if we have its icicle saved and
+ // it is not in the process of pausing/stopping/finishing.
+ if (r.app != null && r.haveState && !r.visible && r.stopped && !r.finishing
+ && r.state != ActivityState.DESTROYING
+ && r.state != ActivityState.DESTROYED) {
+ destroyActivityLocked(r, true, oomAdj);
+ }
+ }
+ }
+
/**
* Destroy the current CLIENT SIDE instance of an activity. This may be
* called both when actually finishing an activity, or when performing
@@ -3460,7 +3525,7 @@
* but then create a new client-side object for this same HistoryRecord.
*/
final boolean destroyActivityLocked(ActivityRecord r,
- boolean removeFromApp) {
+ boolean removeFromApp, boolean oomAdj) {
if (DEBUG_SWITCH) Slog.v(
TAG, "Removing activity: token=" + r
+ ", app=" + (r.app != null ? r.app.processName : "(null)"));
@@ -3470,7 +3535,7 @@
boolean removedFromHistory = false;
- cleanUpActivityLocked(r, false);
+ cleanUpActivityLocked(r, false, false);
final boolean hadApp = r.app != null;
@@ -3488,7 +3553,7 @@
if (r.app.activities.size() == 0) {
// No longer have activities, so update location in
// LRU list.
- mService.updateLruProcessLocked(r.app, true, false);
+ mService.updateLruProcessLocked(r.app, oomAdj, false);
}
}
@@ -3513,12 +3578,23 @@
r.app = null;
r.nowVisible = false;
+ // If the activity is finishing, we need to wait on removing it
+ // from the list to give it a chance to do its cleanup. During
+ // that time it may make calls back with its token so we need to
+ // be able to find it on the list and so we don't want to remove
+ // it from the list yet. Otherwise, we can just immediately put
+ // it in the destroyed state since we are not removing it from the
+ // list.
if (r.finishing && !skipDestroy) {
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYING: " + r
+ + " (destroy requested)");
r.state = ActivityState.DESTROYING;
Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG);
msg.obj = r;
mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
} else {
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r
+ + " (destroy skipped)");
r.state = ActivityState.DESTROYED;
}
} else {
@@ -3527,6 +3603,8 @@
removeActivityFromHistoryLocked(r);
removedFromHistory = true;
} else {
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r
+ + " (no app)");
r.state = ActivityState.DESTROYED;
}
}
@@ -3919,7 +3997,7 @@
if (r.app == null || r.app.thread == null) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Switch is destroying non-running " + r);
- destroyActivityLocked(r, true);
+ destroyActivityLocked(r, true, false);
} else if (r.state == ActivityState.PAUSING) {
// A little annoying: we are waiting for this activity to
// finish pausing. Let's not do anything now, but just