summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2014-08-24 16:45:38 -0700
committerDianne Hackborn <hackbod@google.com>2014-08-26 11:16:59 -0700
commit89ad456ea49cb62615ebdcac83a2515743bbe5fa (patch)
tree4fdd6b948f74930ad10beca0a042e40f36efc50c /core
parent1ce1ba68acbfcbd4100d8c4be7d17a1f0623fd62 (diff)
downloadframeworks_base-89ad456ea49cb62615ebdcac83a2515743bbe5fa.zip
frameworks_base-89ad456ea49cb62615ebdcac83a2515743bbe5fa.tar.gz
frameworks_base-89ad456ea49cb62615ebdcac83a2515743bbe5fa.tar.bz2
Fix issue #16311398: Limit number of documents a process can open
In application processes, monitor for when we start getting close to the Dalvik heap limit, and ask the activity manager to try to prune old activity instances in that case. Add an explicit API for apps to ask that they have their own activity instances cleaned up, if they want. Fix some bugs in launching activities that were not correctly applying the "multi task" behavior in the appropriate situations of document-centric recents. Clean up the activity manager's process removal code to all share a common path. Add a new "Spam" option to ActivityTests, which continually creates new tasks, checking that the activity manager will now prune old tasks rather than letting the app run out of RAM. And while I was was doing this, I found problems with the path for bringing an empty task to the foreground -- it could make a new task instead of re-starting the root activity in the existing task. This is fixed, and some code in the recents UI for working around the bug is removed. And as long as I am doing that, we now have nice hooks in to the activity manager for AppTask to give some APIs for better managing the task, so add those along with more tests for these APIs in ActivityTests. We should look at also having the activity manager try to prune old tasks when it sees app processes being killed, to better balance memory use across multiple processes when some processes may host many documents. That however is for another CL... Change-Id: I2bb81c3f92819350c868c7a7470b35817eb9bea9
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/Activity.java20
-rw-r--r--core/java/android/app/ActivityManager.java34
-rw-r--r--core/java/android/app/ActivityManagerNative.java39
-rw-r--r--core/java/android/app/ActivityThread.java38
-rw-r--r--core/java/android/app/IActivityManager.java4
-rw-r--r--core/java/android/app/IAppTask.aidl5
-rw-r--r--core/java/android/app/Instrumentation.java34
-rw-r--r--core/java/android/provider/Settings.java3
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java31
9 files changed, 195 insertions, 13 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2e66a4c..9b7cc1c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4704,6 +4704,26 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Ask that the local app instance of this activity be released to free up its memory.
+ * This is asking for the activity to be destroyed, but does <b>not</b> finish the activity --
+ * a new instance of the activity will later be re-created if needed due to the user
+ * navigating back to it.
+ *
+ * @return Returns true if the activity was in a state that it has started the process
+ * of destroying its current instance; returns false if for any reason this could not
+ * be done: it is currently visible to the user, it is already being destroyed, it is
+ * being finished, it hasn't yet saved its state, etc.
+ */
+ public boolean releaseInstance() {
+ try {
+ return ActivityManagerNative.getDefault().releaseActivityInstance(mToken);
+ } catch (RemoteException e) {
+ // Empty
+ }
+ return false;
+ }
+
+ /**
* Called when an activity you launched exits, giving you the requestCode
* you started it with, the resultCode it returned, and any additional
* data from it. The <var>resultCode</var> will be
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2667bcd..58790f6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2617,6 +2617,40 @@ public class ActivityManager {
}
/**
+ * Bring this task to the foreground. If it contains activities, they will be
+ * brought to the foreground with it and their instances re-created if needed.
+ * If it doesn't contain activities, the root activity of the task will be
+ * re-launched.
+ */
+ public void moveToFront() {
+ try {
+ mAppTaskImpl.moveToFront();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invalid AppTask", e);
+ }
+ }
+
+ /**
+ * Start an activity in this task. Brings the task to the foreground. If this task
+ * is not currently active (that is, its id < 0), then the activity being started
+ * needs to be started as a new task and the Intent's ComponentName must match the
+ * base ComponenentName of the recent task entry. Otherwise, the activity being
+ * started must <b>not</b> be launched as a new task -- not through explicit intent
+ * flags nor implicitly as the singleTask or singleInstance launch modes.
+ *
+ * <p>See {@link Activity#startActivity(android.content.Intent, android.os.Bundle)
+ * Activity.startActivity} for more information.</p>
+ *
+ * @param intent The Intent describing the new activity to be launched on the task.
+ * @param options Optional launch options.
+ */
+ public void startActivity(Context context, Intent intent, Bundle options) {
+ ActivityThread thread = ActivityThread.currentActivityThread();
+ thread.getInstrumentation().execStartActivityFromAppTask(context,
+ thread.getApplicationThread(), mAppTaskImpl, intent, options);
+ }
+
+ /**
* Modify the {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag in the root
* Intent of this AppTask.
*
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 82af99b..36e8892 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -365,6 +365,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case RELEASE_ACTIVITY_INSTANCE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ boolean res = releaseActivityInstance(token);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
+
+ case RELEASE_SOME_ACTIVITIES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IApplicationThread app = ApplicationThreadNative.asInterface(data.readStrongBinder());
+ releaseSomeActivities(app);
+ reply.writeNoException();
+ return true;
+ }
+
case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -2661,6 +2678,28 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ public boolean releaseActivityInstance(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(RELEASE_ACTIVITY_INSTANCE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean res = reply.readInt() != 0;
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+ public void releaseSomeActivities(IApplicationThread app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(app.asBinder());
+ mRemote.transact(RELEASE_SOME_ACTIVITIES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public boolean willActivityBeVisible(IBinder token) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2136209..0356093 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -102,8 +102,6 @@ import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.google.android.collect.Lists;
-import dalvik.system.VMRuntime;
-
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -201,6 +199,7 @@ public final class ActivityThread {
String mInstrumentedLibDir = null;
boolean mSystemThread = false;
boolean mJitEnabled = false;
+ boolean mSomeActivitiesChanged = false;
// These can be accessed by multiple threads; mPackages is the lock.
// XXX For now we keep around information about all packages we have
@@ -2353,6 +2352,7 @@ public final class ActivityThread {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
+ mSomeActivitiesChanged = true;
if (r.profileFd != null) {
mProfiler.setProfiler(r.profileFile, r.profileFd);
@@ -2495,6 +2495,7 @@ public final class ActivityThread {
public void handleCancelVisibleBehind(IBinder token) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
+ mSomeActivitiesChanged = true;
final Activity activity = r.activity;
if (activity.mVisibleBehind) {
activity.mCalled = false;
@@ -2984,6 +2985,7 @@ public final class ActivityThread {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
+ mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
@@ -3175,6 +3177,7 @@ public final class ActivityThread {
ActivityManagerNative.getDefault().activityPaused(token, r.persistentState);
} catch (RemoteException ex) {
}
+ mSomeActivitiesChanged = true;
}
}
@@ -3413,6 +3416,7 @@ public final class ActivityThread {
info.state = r.state;
info.persistentState = r.persistentState;
mH.post(info);
+ mSomeActivitiesChanged = true;
}
final void performRestartActivity(IBinder token) {
@@ -3446,6 +3450,7 @@ public final class ActivityThread {
TAG, "Handle window " + r + " visibility: " + show);
updateVisibility(r, show);
}
+ mSomeActivitiesChanged = true;
}
private void handleSleeping(IBinder token, boolean sleeping) {
@@ -3743,6 +3748,7 @@ public final class ActivityThread {
// If the system process has died, it's game over for everyone.
}
}
+ mSomeActivitiesChanged = true;
}
public final void requestRelaunchActivity(IBinder token,
@@ -3805,6 +3811,7 @@ public final class ActivityThread {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
+ mSomeActivitiesChanged = true;
Configuration changedConfig = null;
int configChanges = 0;
@@ -4107,6 +4114,8 @@ public final class ActivityThread {
performConfigurationChanged(r.activity, mCompatConfiguration);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
+
+ mSomeActivitiesChanged = true;
}
final void handleProfilerControl(boolean start, ProfilerControlData pcd, int profileType) {
@@ -5045,17 +5054,38 @@ public final class ActivityThread {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
- IActivityManager mgr = ActivityManagerNative.getDefault();
+ final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
+ // Watch for getting close to heap limit.
+ BinderInternal.addGcWatcher(new Runnable() {
+ @Override public void run() {
+ if (!mSomeActivitiesChanged) {
+ return;
+ }
+ Runtime runtime = Runtime.getRuntime();
+ long dalvikMax = runtime.maxMemory();
+ long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
+ if (dalvikUsed > ((3*dalvikMax)/4)) {
+ if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ + " total=" + (runtime.totalMemory()/1024)
+ + " used=" + (dalvikUsed/1024));
+ mSomeActivitiesChanged = false;
+ try {
+ mgr.releaseSomeActivities(mAppThread);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ });
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process",
- UserHandle.myUserId());
+ UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
ContextImpl context = ContextImpl.createAppContext(
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 69e1710..57c4b71 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -97,6 +97,8 @@ public interface IActivityManager extends IInterface {
public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
public boolean finishActivityAffinity(IBinder token) throws RemoteException;
public void finishVoiceTask(IVoiceInteractionSession session) throws RemoteException;
+ public boolean releaseActivityInstance(IBinder token) throws RemoteException;
+ public void releaseSomeActivities(IApplicationThread app) throws RemoteException;
public boolean willActivityBeVisible(IBinder token) throws RemoteException;
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter,
@@ -771,4 +773,6 @@ public interface IActivityManager extends IInterface {
int START_ACTIVITY_AS_CALLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+232;
int ADD_APP_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+233;
int GET_APP_TASK_THUMBNAIL_SIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+234;
+ int RELEASE_ACTIVITY_INSTANCE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+235;
+ int RELEASE_SOME_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+236;
}
diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl
index 4e38c36..37fead9 100644
--- a/core/java/android/app/IAppTask.aidl
+++ b/core/java/android/app/IAppTask.aidl
@@ -17,10 +17,15 @@
package android.app;
import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
/** @hide */
interface IAppTask {
void finishAndRemoveTask();
ActivityManager.RecentTaskInfo getTaskInfo();
+ void moveToFront();
+ int startActivity(IBinder whoThread, String callingPackage,
+ in Intent intent, String resolvedType, in Bundle options);
void setExcludeFromRecents(boolean exclude);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b28d7cc..bc71bad 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1702,6 +1702,40 @@ public class Instrumentation {
return null;
}
+ /**
+ * Special version!
+ * @hide
+ */
+ public void execStartActivityFromAppTask(
+ Context who, IBinder contextThread, IAppTask appTask,
+ Intent intent, Bundle options) {
+ IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (mActivityMonitors != null) {
+ synchronized (mSync) {
+ final int N = mActivityMonitors.size();
+ for (int i=0; i<N; i++) {
+ final ActivityMonitor am = mActivityMonitors.get(i);
+ if (am.match(who, null, intent)) {
+ am.mHits++;
+ if (am.isBlocking()) {
+ return;
+ }
+ break;
+ }
+ }
+ }
+ }
+ try {
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
+ int result = appTask.startActivity(whoThread.asBinder(), who.getBasePackageName(),
+ intent, intent.resolveTypeIfNeeded(who.getContentResolver()), options);
+ checkStartActivityResult(result, intent);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2241716..95d1351 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6320,8 +6320,7 @@ public final class Settings {
* processes as soon as they are no longer needed. If 0, the normal
* extended lifetime is used.
*/
- public static final String ALWAYS_FINISH_ACTIVITIES =
- "always_finish_activities";
+ public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
/**
* Use Dock audio output for media:
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 3b0f0f4..240d9df 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -21,6 +21,7 @@ import android.os.SystemClock;
import android.util.EventLog;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
* Private and debugging Binder APIs.
@@ -28,19 +29,35 @@ import java.lang.ref.WeakReference;
* @see IBinder
*/
public class BinderInternal {
- static WeakReference<GcWatcher> mGcWatcher
+ static WeakReference<GcWatcher> sGcWatcher
= new WeakReference<GcWatcher>(new GcWatcher());
- static long mLastGcTime;
-
+ static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
+ static Runnable[] sTmpWatchers = new Runnable[1];
+ static long sLastGcTime;
+
static final class GcWatcher {
@Override
protected void finalize() throws Throwable {
handleGc();
- mLastGcTime = SystemClock.uptimeMillis();
- mGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
+ sLastGcTime = SystemClock.uptimeMillis();
+ synchronized (sGcWatchers) {
+ sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
+ }
+ for (int i=0; i<sTmpWatchers.length; i++) {
+ if (sTmpWatchers[i] != null) {
+ sTmpWatchers[i].run();
+ }
+ }
+ sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
}
}
-
+
+ public static void addGcWatcher(Runnable watcher) {
+ synchronized (sGcWatchers) {
+ sGcWatchers.add(watcher);
+ }
+ }
+
/**
* Add the calling thread to the IPC thread pool. This function does
* not return until the current process is exiting.
@@ -58,7 +75,7 @@ public class BinderInternal {
* SystemClock.uptimeMillis()} of the last garbage collection.
*/
public static long getLastGcTime() {
- return mLastGcTime;
+ return sLastGcTime;
}
/**