Merge "Some small tweaks to improve memory management."
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7207e29..227900e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -55,6 +55,7 @@
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.ActionMode;
import android.view.ContextMenu;
@@ -642,6 +643,7 @@
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2 {
private static final String TAG = "Activity";
+ private static final boolean DEBUG_LIFECYCLE = false;
/** Standard activity result: operation canceled. */
public static final int RESULT_CANCELED = 0;
@@ -865,6 +867,7 @@
* @see #onPostCreate
*/
protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
}
@@ -1013,6 +1016,7 @@
* @see #onResume
*/
protected void onStart() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
mCalled = true;
if (!mLoadersStarted) {
@@ -1073,6 +1077,7 @@
* @see #onPause
*/
protected void onResume() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
mCalled = true;
}
@@ -1131,6 +1136,7 @@
final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
/**
@@ -1261,6 +1267,7 @@
* @see #onStop
*/
protected void onPause() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
getApplication().dispatchActivityPaused(this);
mCalled = true;
}
@@ -1347,6 +1354,7 @@
* @see #onDestroy
*/
protected void onStop() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
getApplication().dispatchActivityStopped(this);
mCalled = true;
@@ -1381,6 +1389,7 @@
* @see #isFinishing
*/
protected void onDestroy() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
mCalled = true;
// dismiss any dialogs we are managing.
@@ -1432,6 +1441,7 @@
* @param newConfig The new device configuration.
*/
public void onConfigurationChanged(Configuration newConfig) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
mCalled = true;
mFragments.dispatchConfigurationChanged(newConfig);
@@ -1613,11 +1623,13 @@
}
public void onLowMemory() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
mCalled = true;
mFragments.dispatchLowMemory();
}
public void onTrimMemory(int level) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
mCalled = true;
mFragments.dispatchTrimMemory(level);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 98c4e10..1489b2c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -138,6 +138,7 @@
private static final boolean DEBUG_BACKUP = true;
private static final boolean DEBUG_CONFIGURATION = false;
private static final boolean DEBUG_SERVICE = false;
+ private static final boolean DEBUG_MEMORY_TRIM = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -2779,9 +2780,21 @@
performStopActivityInner(r, null, false, saveState);
}
- private static class StopInfo {
+ private static class StopInfo implements Runnable {
+ ActivityClientRecord activity;
+ Bundle state;
Bitmap thumbnail;
CharSequence description;
+
+ @Override public void run() {
+ // Tell activity manager we have been stopped.
+ try {
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
+ ActivityManagerNative.getDefault().activityStopped(
+ activity.token, state, thumbnail, description);
+ } catch (RemoteException ex) {
+ }
+ }
}
private static final class ProviderRefCount {
@@ -2911,12 +2924,14 @@
QueuedWork.waitToFinish();
}
- // Tell activity manager we have been stopped.
- try {
- ActivityManagerNative.getDefault().activityStopped(
- r.token, r.state, info.thumbnail, info.description);
- } catch (RemoteException ex) {
- }
+ // Schedule the call to tell the activity manager we have
+ // stopped. We don't do this immediately, because we want to
+ // have a chance for any other pending work (in particular memory
+ // trim requests) to complete before you tell the activity
+ // manager to proceed and allow us to go fully into the background.
+ info.activity = r;
+ info.state = r.state;
+ mH.post(info);
}
final void performRestartActivity(IBinder token) {
@@ -3749,6 +3764,7 @@
}
final void handleTrimMemory(int level) {
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
WindowManagerImpl.getDefault().trimMemory(level);
ArrayList<ComponentCallbacks2> callbacks;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index a45a87e..52bd860 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -432,23 +432,24 @@
*/
public void trimMemory(int level) {
if (HardwareRenderer.isAvailable()) {
- // On low and medium end gfx devices
- if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) {
- if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
- // Destroy all hardware surfaces and resources associated to
- // known windows
- synchronized (this) {
- if (mViews == null) return;
- int count = mViews.length;
- for (int i = 0; i < count; i++) {
- mRoots[i].terminateHardwareResources();
- }
+ // On low-end gfx devices we trim when memory is moderate;
+ // on high-end devices we do this when low.
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
+ || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
+ && !ActivityManager.isHighEndGfx(getDefaultDisplay()))) {
+ // Destroy all hardware surfaces and resources associated to
+ // known windows
+ synchronized (this) {
+ if (mViews == null) return;
+ int count = mViews.length;
+ for (int i = 0; i < count; i++) {
+ mRoots[i].terminateHardwareResources();
}
- // Force a full memory flush
- HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
- mNeedsEglTerminate = true;
- return;
}
+ // Force a full memory flush
+ HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ mNeedsEglTerminate = true;
+ return;
}
HardwareRenderer.trimMemory(level);
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index f496c4e..9c955bd 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -886,7 +886,9 @@
// the existing GL resources for the html5 video will be destroyed
// at native side.
// Here we just need to clean up the Surface Texture which is static.
- HTML5VideoInline.cleanupSurfaceTexture();
+ if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ HTML5VideoInline.cleanupSurfaceTexture();
+ }
WebViewClassic.nativeOnTrimMemory(level);
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 42c42c9..78b441a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -13444,7 +13444,7 @@
// an earlier hidden adjustment that isn't really for us... if
// so, use the new hidden adjustment.
if (!recursed && app.hidden) {
- app.curAdj = app.curRawAdj = hiddenAdj;
+ app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj;
}
return app.curRawAdj;
}
@@ -13468,7 +13468,7 @@
// below foreground, so it is not worth doing work for it.
app.adjType = "fixed";
app.adjSeq = mAdjSeq;
- app.curRawAdj = app.maxAdj;
+ app.curRawAdj = app.nonStoppingAdj = app.maxAdj;
app.foregroundActivities = false;
app.keeping = true;
app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -13545,6 +13545,8 @@
app.adjType = "bg-empty";
}
+ boolean hasStoppingActivities = false;
+
// Examine all activities if not already foreground.
if (!app.foregroundActivities && activitiesSize > 0) {
for (int j = 0; j < activitiesSize; j++) {
@@ -13559,15 +13561,20 @@
app.hidden = false;
app.foregroundActivities = true;
break;
- } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED
- || r.state == ActivityState.STOPPING) {
- // Only upgrade adjustment.
+ } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) {
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- app.adjType = "stopping";
+ app.adjType = "pausing";
}
app.hidden = false;
app.foregroundActivities = true;
+ } else if (r.state == ActivityState.STOPPING) {
+ // We will apply the actual adjustment later, because
+ // we want to allow this process to immediately go through
+ // any memory trimming that is in effect.
+ app.hidden = false;
+ app.foregroundActivities = true;
+ hasStoppingActivities = true;
}
}
}
@@ -13625,7 +13632,7 @@
// this gives us a baseline and makes sure we don't get into an
// infinite recursion.
app.adjSeq = mAdjSeq;
- app.curRawAdj = adj;
+ app.curRawAdj = app.nonStoppingAdj = adj;
if (mBackupTarget != null && app == mBackupTarget.app) {
// If possible we want to avoid killing apps while they're being backed up
@@ -13882,6 +13889,28 @@
}
}
+ if (adj == ProcessList.SERVICE_ADJ) {
+ if (doingAll) {
+ app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3);
+ mNewNumServiceProcs++;
+ }
+ if (app.serviceb) {
+ adj = ProcessList.SERVICE_B_ADJ;
+ }
+ } else {
+ app.serviceb = false;
+ }
+
+ app.nonStoppingAdj = adj;
+
+ if (hasStoppingActivities) {
+ // Only upgrade adjustment.
+ if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ app.adjType = "stopping";
+ }
+ }
+
app.curRawAdj = adj;
//Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
@@ -13915,18 +13944,6 @@
}
}
- if (adj == ProcessList.SERVICE_ADJ) {
- if (doingAll) {
- app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3);
- mNewNumServiceProcs++;
- }
- if (app.serviceb) {
- adj = ProcessList.SERVICE_B_ADJ;
- }
- } else {
- app.serviceb = false;
- }
-
app.curAdj = adj;
app.curSchedGroup = schedGroup;
@@ -14138,7 +14155,7 @@
}
// If a process has held a wake lock for more
// than 50% of the time during this period,
- // that sounds pad. Kill!
+ // that sounds bad. Kill!
if (doWakeKills && realtimeSince > 0
&& ((wtimeUsed*100)/realtimeSince) >= 50) {
synchronized (stats) {
@@ -14186,23 +14203,6 @@
computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll);
if (app.curRawAdj != app.setRawAdj) {
- if (false) {
- // Removing for now. Forcing GCs is not so useful anymore
- // with Dalvik, and the new memory level hint facility is
- // better for what we need to do these days.
- if (app.curRawAdj > ProcessList.FOREGROUND_APP_ADJ
- && app.setRawAdj <= ProcessList.FOREGROUND_APP_ADJ) {
- // If this app is transitioning from foreground to
- // non-foreground, have it do a gc.
- scheduleAppGcLocked(app);
- } else if (app.curRawAdj >= ProcessList.HIDDEN_APP_MIN_ADJ
- && app.setRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) {
- // Likewise do a gc when an app is moving in to the
- // background (such as a service stopping).
- scheduleAppGcLocked(app);
- }
- }
-
if (wasKeeping && !app.keeping) {
// This app is no longer something we want to keep. Note
// its current wake lock time to later know to kill it if
@@ -14319,6 +14319,7 @@
if (factor < 1) factor = 1;
int step = 0;
int numHidden = 0;
+ int numTrimming = 0;
// First update the OOM adjustment for each of the
// application processes based on their current state.
@@ -14363,6 +14364,11 @@
app.killedBackground = true;
Process.killProcessQuiet(app.pid);
}
+ if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ
+ && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ
+ && !app.killedBackground) {
+ numTrimming++;
+ }
}
}
@@ -14376,7 +14382,7 @@
// memory they want.
if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/2)) {
final int N = mLruProcesses.size();
- factor = numHidden/3;
+ factor = numTrimming/3;
int minFactor = 2;
if (mHomeProcess != null) minFactor++;
if (mPreviousProcess != null) minFactor++;
@@ -14393,8 +14399,8 @@
int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
for (i=0; i<N; i++) {
ProcessRecord app = mLruProcesses.get(i);
- if (app.curAdj >= ProcessList.HOME_APP_ADJ
- && app.curAdj != ProcessList.SERVICE_B_ADJ
+ if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ
+ && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ
&& !app.killedBackground) {
if (app.trimMemoryLevel < curLevel && app.thread != null) {
try {
@@ -14426,7 +14432,7 @@
break;
}
}
- } else if (app.curAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+ } else if (app.nonStoppingAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) {
if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
&& app.thread != null) {
try {
@@ -14437,7 +14443,7 @@
}
app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
} else {
- if ((app.curAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
+ if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
&& app.pendingUiClean) {
// If this application is now in the background and it
// had done UI, then give it the special trim level to
@@ -14464,7 +14470,7 @@
final int N = mLruProcesses.size();
for (i=0; i<N; i++) {
ProcessRecord app = mLruProcesses.get(i);
- if ((app.curAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
+ if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)
&& app.pendingUiClean) {
if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
&& app.thread != null) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a01ed25..6596e1f 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -97,6 +97,11 @@
// next activity.
static final int PAUSE_TIMEOUT = 500;
+ // How long we wait for the activity to tell us it has stopped before
+ // giving up. This is a good amount of time because we really need this
+ // from the application in order to get its saved state.
+ static final int STOP_TIMEOUT = 10*1000;
+
// How long we can hold the sleep wake lock before giving up.
static final int SLEEP_TIMEOUT = 5*1000;
@@ -280,6 +285,7 @@
static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5;
static final int RESUME_TOP_ACTIVITY_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6;
static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 7;
+ static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 8;
final Handler mHandler = new Handler() {
//public Handler() {
@@ -364,6 +370,17 @@
resumeTopActivityLocked(null);
}
} break;
+ case STOP_TIMEOUT_MSG: {
+ ActivityRecord r = (ActivityRecord)msg.obj;
+ // We don't at this point know if the activity is fullscreen,
+ // so we need to be conservative and assume it isn't.
+ Slog.w(TAG, "Activity stop timeout for " + r);
+ synchronized (mService) {
+ if (r.isInHistory()) {
+ activityStoppedLocked(r, null, null, null);
+ }
+ }
+ } break;
}
}
};
@@ -1000,31 +1017,38 @@
final void activityStoppedLocked(ActivityRecord r, Bundle icicle, Bitmap thumbnail,
CharSequence description) {
if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle);
- 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, "stop-config");
- resumeTopActivityLocked(null);
- } else {
- // Now that this process has stopped, we may want to consider
- // it to be the previous app to try to keep around in case
- // the user wants to return to it.
- ProcessRecord fgApp = null;
- if (mResumedActivity != null) {
- fgApp = mResumedActivity.app;
- } else if (mPausingActivity != null) {
- fgApp = mPausingActivity.app;
- }
- if (r.app != null && fgApp != null && r.app != fgApp
- && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
- && r.app != mService.mHomeProcess) {
- mService.mPreviousProcess = r.app;
- mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+ if (icicle != null) {
+ // If icicle is null, this is happening due to a timeout, so we
+ // haven't really saved the state.
+ r.icicle = icicle;
+ r.haveState = true;
+ r.updateThumbnail(thumbnail, description);
+ }
+ if (!r.stopped) {
+ if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)");
+ mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
+ r.stopped = true;
+ r.state = ActivityState.STOPPED;
+ if (!r.finishing) {
+ if (r.configDestroy) {
+ destroyActivityLocked(r, true, false, "stop-config");
+ resumeTopActivityLocked(null);
+ } else {
+ // Now that this process has stopped, we may want to consider
+ // it to be the previous app to try to keep around in case
+ // the user wants to return to it.
+ ProcessRecord fgApp = null;
+ if (mResumedActivity != null) {
+ fgApp = mResumedActivity.app;
+ } else if (mPausingActivity != null) {
+ fgApp = mPausingActivity.app;
+ }
+ if (r.app != null && fgApp != null && r.app != fgApp
+ && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
+ && r.app != mService.mHomeProcess) {
+ mService.mPreviousProcess = r.app;
+ mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+ }
}
}
}
@@ -3228,6 +3252,9 @@
if (mService.isSleeping()) {
r.setSleeping(true);
}
+ Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG);
+ msg.obj = r;
+ mHandler.sendMessageDelayed(msg, STOP_TIMEOUT);
} catch (Exception e) {
// Maybe just ignore exceptions here... if the process
// has crashed, our death notification will clean things
@@ -3694,6 +3721,7 @@
// Get rid of any pending idle timeouts.
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+ mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
r.finishLaunchTickingLocked();
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index b64261d..4529ecc 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -63,6 +63,7 @@
int hiddenAdj; // If hidden, this is the adjustment to use
int curRawAdj; // Current OOM unlimited adjustment for this process
int setRawAdj; // Last set OOM unlimited adjustment for this process
+ int nonStoppingAdj; // Adjustment not counting any stopping activities
int curAdj; // Current OOM adjustment for this process
int setAdj; // Last set OOM adjustment for this process
int curSchedGroup; // Currently desired scheduling class
@@ -199,6 +200,7 @@
pw.print(" hidden="); pw.print(hiddenAdj);
pw.print(" curRaw="); pw.print(curRawAdj);
pw.print(" setRaw="); pw.print(setRawAdj);
+ pw.print(" nonStopping="); pw.print(nonStoppingAdj);
pw.print(" cur="); pw.print(curAdj);
pw.print(" set="); pw.println(setAdj);
pw.print(prefix); pw.print("curSchedGroup="); pw.print(curSchedGroup);