From daa91e4aa0a3e9143f58574063e945dfdbd78cd7 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Wed, 6 Dec 2017 14:13:01 -0800 Subject: [PATCH] Recycle ActivityStarter instances. This changelist adds the ability to reuse ActivityStarter instances by reclaiming spent ones. By default, ActivityStarters will now be reset and be part of a pool for future activity start uses. Bug: 64750076 Test: atest FrameworksServicesTests:StartActivityControllerTests Test: atest FrameworksServicesTests:ActivityStarterTests Change-Id: I77261ba6f397471f4aeb9ef6dfe5edcf55f0007f --- .../android/server/am/ActivityStartController.java | 19 +- .../com/android/server/am/ActivityStarter.java | 285 ++++++++++++++++++--- .../server/am/ActivityStartControllerTests.java | 20 +- .../android/server/am/ActivityStarterTests.java | 1 + 4 files changed, 284 insertions(+), 41 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java index a97b93ca01ba..aed49e0037e1 100644 --- a/services/core/java/com/android/server/am/ActivityStartController.java +++ b/services/core/java/com/android/server/am/ActivityStartController.java @@ -105,7 +105,8 @@ public class ActivityStartController { /** * TODO(b/64750076): Capture information necessary for dump and * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object - * around */ + * around + */ private ActivityStarter mLastStarter; ActivityStartController(ActivityManagerService service) { @@ -130,10 +131,16 @@ public class ActivityStartController { * considered invalid and no longer modified or used. */ ActivityStarter obtainStarter(Intent intent, String reason) { - final ActivityStarter starter = mFactory.obtainStarter(); - mLastStarter = starter; + return mFactory.obtain().setIntent(intent).setReason(reason); + } + + void onExecutionComplete(ActivityStarter starter) { + if (mLastStarter == null) { + mLastStarter = mFactory.obtain(); + } - return starter.setIntent(intent).setReason(reason); + mLastStarter.set(starter); + mFactory.recycle(starter); } /** @@ -142,6 +149,10 @@ public class ActivityStartController { */ void postStartActivityProcessingForLastStarter(ActivityRecord r, int result, ActivityStack targetStack) { + if (mLastStarter == null) { + return; + } + mLastStarter.postStartActivityProcessing(r, result, targetStack); } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index dda8e9c18229..ab60868dad78 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -101,6 +101,7 @@ import android.os.UserManager; import android.service.voice.IVoiceInteractionSession; import android.text.TextUtils; import android.util.EventLog; +import android.util.Pools.SynchronizedPool; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -130,7 +131,6 @@ class ActivityStarter { private final ActivityManagerService mService; private final ActivityStackSupervisor mSupervisor; private final ActivityStartInterceptor mInterceptor; - private final ActivityStartController mController; // Share state variable among methods when starting an activity. @@ -208,18 +208,34 @@ class ActivityStarter { * this instance. * @return an {@link ActivityStarter} */ - ActivityStarter obtainStarter(); + ActivityStarter obtain(); + + /** + * Recycles a starter for reuse. + */ + void recycle(ActivityStarter starter); } /** * Default implementation of {@link StarterFactory}. */ static class DefaultFactory implements Factory { + /** + * The maximum count of starters that should be active at one time: + * 1. last ran starter (for logging and post activity processing) + * 2. current running starter + * 3. starter from re-entry in (2) + */ + private final int MAX_STARTER_COUNT = 3; + private ActivityStartController mController; private ActivityManagerService mService; private ActivityStackSupervisor mSupervisor; private ActivityStartInterceptor mInterceptor; + private SynchronizedPool mStarterPool = + new SynchronizedPool<>(MAX_STARTER_COUNT); + DefaultFactory(ActivityManagerService service, ActivityStackSupervisor supervisor, ActivityStartInterceptor interceptor) { mService = service; @@ -233,9 +249,20 @@ class ActivityStarter { } @Override - public ActivityStarter obtainStarter() { - // TODO(b/64750076): Investigate recycling instances to reduce object creation overhead. - return new ActivityStarter(mController, mService, mSupervisor, mInterceptor); + public ActivityStarter obtain() { + ActivityStarter starter = mStarterPool.acquire(); + + if (starter == null) { + starter = new ActivityStarter(mController, mService, mSupervisor, mInterceptor); + } + + return starter; + } + + @Override + public void recycle(ActivityStarter starter) { + starter.reset(true /* clearRequest*/); + mStarterPool.release(starter); } } @@ -288,6 +315,76 @@ class ActivityStarter { * {@see ActivityStarter#startActivityMayWait}. */ boolean mayWait; + + /** + * Sets values back to the initial state, clearing any held references. + */ + void reset() { + caller = null; + intent = null; + ephemeralIntent = null; + resolvedType = null; + activityInfo = null; + resolveInfo = null; + voiceSession = null; + voiceInteractor = null; + resultTo = null; + resultWho = null; + requestCode = 0; + callingPid = 0; + callingUid = 0; + callingPackage = null; + realCallingPid = 0; + realCallingUid = 0; + startFlags = 0; + activityOptions = null; + ignoreTargetSecurity = false; + componentSpecified = false; + outActivity = null; + inTask = null; + reason = null; + profilerInfo = null; + globalConfig = null; + waitOptions = null; + userId = 0; + waitResult = null; + mayWait = false; + } + + /** + * Adopts all values from passed in request. + */ + void set(Request request) { + caller = request.caller; + intent = request.intent; + ephemeralIntent = request.ephemeralIntent; + resolvedType = request.resolvedType; + activityInfo = request.activityInfo; + resolveInfo = request.resolveInfo; + voiceSession = request.voiceSession; + voiceInteractor = request.voiceInteractor; + resultTo = request.resultTo; + resultWho = request.resultWho; + requestCode = request.requestCode; + callingPid = request.callingPid; + callingUid = request.callingUid; + callingPackage = request.callingPackage; + realCallingPid = request.realCallingPid; + realCallingUid = request.realCallingUid; + startFlags = request.startFlags; + activityOptions = request.activityOptions; + ignoreTargetSecurity = request.ignoreTargetSecurity; + componentSpecified = request.componentSpecified; + outActivity = request.outActivity; + inTask = request.inTask; + reason = request.reason; + profilerInfo = request.profilerInfo; + globalConfig = request.globalConfig; + waitOptions = request.waitOptions; + userId = request.userId; + waitResult = request.waitResult; + mayWait = request.mayWait; + } } ActivityStarter(ActivityStartController controller, ActivityManagerService service, @@ -296,6 +393,52 @@ class ActivityStarter { mService = service; mSupervisor = supervisor; mInterceptor = interceptor; + reset(true); + } + + /** + * Effectively duplicates the starter passed in. All state and request values will be + * mirrored. + * @param starter + */ + void set(ActivityStarter starter) { + mStartActivity = starter.mStartActivity; + mIntent = starter.mIntent; + mCallingUid = starter.mCallingUid; + mOptions = starter.mOptions; + + mLaunchTaskBehind = starter.mLaunchTaskBehind; + mLaunchFlags = starter.mLaunchFlags; + mLaunchMode = starter.mLaunchMode; + + mLaunchBounds.set(starter.mLaunchBounds); + + mNotTop = starter.mNotTop; + mDoResume = starter.mDoResume; + mStartFlags = starter.mStartFlags; + mSourceRecord = starter.mSourceRecord; + mPreferredDisplayId = starter.mPreferredDisplayId; + + mInTask = starter.mInTask; + mAddingToTask = starter.mAddingToTask; + mReuseTask = starter.mReuseTask; + + mNewTaskInfo = starter.mNewTaskInfo; + mNewTaskIntent = starter.mNewTaskIntent; + mSourceStack = starter.mSourceStack; + + mTargetStack = starter.mTargetStack; + mMovedToFront = starter.mMovedToFront; + mNoAnimation = starter.mNoAnimation; + mKeepCurTransition = starter.mKeepCurTransition; + mAvoidMoveToFront = starter.mAvoidMoveToFront; + + mVoiceSession = starter.mVoiceSession; + mVoiceInteractor = starter.mVoiceInteractor; + + mIntentDelivered = starter.mIntentDelivered; + + mRequest.set(starter.mRequest); } ActivityRecord getStartActivity() { @@ -313,25 +456,47 @@ class ActivityStarter { * @return The starter result. */ int execute() { - // TODO(b/64750076): Look into passing request directly to these methods to allow - // for transactional diffs and preprocessing. - if (mRequest.mayWait) { - return startActivityMayWait(mRequest.caller, mRequest.callingUid, - mRequest.callingPackage, mRequest.intent, mRequest.resolvedType, - mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, - mRequest.resultWho, mRequest.requestCode, mRequest.startFlags, - mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig, - mRequest.waitOptions, mRequest.ignoreTargetSecurity, mRequest.userId, - mRequest.inTask, mRequest.reason); - } else { - return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent, - mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo, - mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, - mRequest.resultWho, mRequest.requestCode, mRequest.callingPid, - mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid, - mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions, - mRequest.ignoreTargetSecurity, mRequest.componentSpecified, - mRequest.outActivity, mRequest.inTask, mRequest.reason); + try { + // TODO(b/64750076): Look into passing request directly to these methods to allow + // for transactional diffs and preprocessing. + if (mRequest.mayWait) { + return startActivityMayWait(mRequest.caller, mRequest.callingUid, + mRequest.callingPackage, mRequest.intent, mRequest.resolvedType, + mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, + mRequest.resultWho, mRequest.requestCode, mRequest.startFlags, + mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig, + mRequest.waitOptions, mRequest.ignoreTargetSecurity, mRequest.userId, + mRequest.inTask, mRequest.reason); + } else { + return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent, + mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo, + mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, + mRequest.resultWho, mRequest.requestCode, mRequest.callingPid, + mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid, + mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions, + mRequest.ignoreTargetSecurity, mRequest.componentSpecified, + mRequest.outActivity, mRequest.inTask, mRequest.reason); + } + } finally { + onExecutionComplete(); + } + } + + /** + * Starts an activity based on the provided {@link ActivityRecord} and environment parameters. + * Note that this method is called internally as well as part of {@link #startActivity}. + * + * @return The start result. + */ + int startResolvedActivity(final ActivityRecord r, ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, + ActivityRecord[] outActivity) { + try { + return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, + doResume, options, inTask, outActivity); + } finally { + onExecutionComplete(); } } @@ -369,6 +534,14 @@ class ActivityStarter { return result != START_ABORTED ? result : START_SUCCESS; } + /** + * Called when execution is complete. Sets state indicating completion and proceeds with + * recycling if appropriate. + */ + private void onExecutionComplete() { + mController.onExecutionComplete(this); + } + private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent, String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, @@ -651,7 +824,7 @@ class ActivityStarter { mController.doPendingActivityLaunches(false); - return startResolvedActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, + return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true /* doResume */, options, inTask, outActivity); } @@ -924,16 +1097,10 @@ class ActivityStarter { } } - /** - * Starts an activity based on the provided {@link ActivityRecord} and environment parameters. - * Note that this method is called internally as well as part of {@link #startActivity}. - * - * @return The start result. - */ - int startResolvedActivity(final ActivityRecord r, ActivityRecord sourceRecord, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, - ActivityRecord[] outActivity) { + private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, + ActivityRecord[] outActivity) { int result = START_CANCELED; try { mService.mWindowManager.deferSurfaceLayout(); @@ -1189,9 +1356,57 @@ class ActivityStarter { return START_SUCCESS; } + /** + * Resets the {@link ActivityStarter} state. + * @param clearRequest whether the request should be reset to default values. + */ + void reset(boolean clearRequest) { + mStartActivity = null; + mIntent = null; + mCallingUid = -1; + mOptions = null; + + mLaunchTaskBehind = false; + mLaunchFlags = 0; + mLaunchMode = INVALID_LAUNCH_MODE; + + mLaunchBounds.setEmpty(); + + mNotTop = null; + mDoResume = false; + mStartFlags = 0; + mSourceRecord = null; + mPreferredDisplayId = INVALID_DISPLAY; + + mInTask = null; + mAddingToTask = false; + mReuseTask = null; + + mNewTaskInfo = null; + mNewTaskIntent = null; + mSourceStack = null; + + mTargetStack = null; + mMovedToFront = false; + mNoAnimation = false; + mKeepCurTransition = false; + mAvoidMoveToFront = false; + + mVoiceSession = null; + mVoiceInteractor = null; + + mIntentDelivered = false; + + if (clearRequest) { + mRequest.reset(); + } + } + private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask, boolean doResume, int startFlags, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { + reset(false /* clearRequest */); + mStartActivity = r; mIntent = r.intent; mOptions = options; diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java index 7160d7fc8c2f..a44f5c4f8fff 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import android.content.Intent; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -61,14 +62,13 @@ public class ActivityStartControllerTests extends ActivityTestsBase { mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory); mStarter = spy(new ActivityStarter(mController, mService, mService.mStackSupervisor, mock(ActivityStartInterceptor.class))); - doReturn(mStarter).when(mFactory).obtainStarter(); + doReturn(mStarter).when(mFactory).obtain(); } /** * Ensures that pending launches are processed. */ @Test - @Presubmit public void testPendingActivityLaunches() { final Random random = new Random(); @@ -88,4 +88,20 @@ public class ActivityStartControllerTests extends ActivityTestsBase { verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null), eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null)); } + + + /** + * Ensures instances are recycled after execution. + */ + @Test + public void testRecycling() throws Exception { + final Intent intent = new Intent(); + final ActivityStarter optionStarter = new ActivityStarter(mController, mService, + mService.mStackSupervisor, mock(ActivityStartInterceptor.class)); + optionStarter + .setIntent(intent) + .setReason("Test") + .execute(); + verify(mFactory, times(1)).recycle(eq(optionStarter)); + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java index e8194dd8e777..589a89bcbddf 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java @@ -63,6 +63,7 @@ import static android.app.ActivityManager.START_PERMISSION_DENIED; import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED; import com.android.internal.os.BatteryStatsImpl; +import com.android.server.am.ActivityStarter.Factory; /** * Tests for the {@link ActivityStarter} class. -- 2.11.0