/** * Retrieve the state of all activities known by the group. For activities that have previously * run and are now stopped or finished, the last saved state is used. For the current running * activity, its {@link Activity#onSaveInstanceState} is called to retrieve its current state. * * @return a Bundle holding the newly created state of all known activities * @see #dispatchCreate */ public Bundle saveInstanceState() { Bundle state = null; // FIXME: child activities will freeze as part of onPaused. Do we // need to do this here? final int N = mActivityArray.size(); for (int i = 0; i < N; i++) { final LocalActivityRecord r = mActivityArray.get(i); if (state == null) { state = new Bundle(); } if ((r.instanceState != null || r.curState == RESUMED) && r.activity != null) { // We need to save the state now, if we don't currently // already have it or the activity is currently resumed. final Bundle childState = new Bundle(); r.activity.performSaveInstanceState(childState); r.instanceState = childState; } if (r.instanceState != null) { state.putBundle(r.id, r.instanceState); } } return state; }
private Window performDestroy(LocalActivityRecord r, boolean finish) { Window win; win = r.window; if (r.curState == RESUMED && !finish) { performPause(r, finish); } if (localLOGV) Log.v(TAG, r.id + ": destroying"); mActivityThread.performDestroyActivity(r, finish); r.activity = null; r.window = null; if (finish) { r.instanceState = null; } r.curState = DESTROYED; return win; }
private void performPause(LocalActivityRecord r, boolean finishing) { boolean needState = r.instanceState == null; Bundle instanceState = mActivityThread.performPauseActivity(r, finishing, needState); if (needState) { r.instanceState = instanceState; } }
/** * Restore a state that was previously returned by {@link #saveInstanceState}. This adds to the * activity group information about all activity IDs that had previously been saved, even if they * have not been started yet, so if the user later navigates to them the correct state will be * restored. * * <p>Note: This does <b>not</b> change the current running activity, or start whatever activity * was previously running when the state was saved. That is up to the client to do, in whatever * way it thinks is best. * * @param state a previously saved state; does nothing if this is null * @see #saveInstanceState */ public void dispatchCreate(Bundle state) { if (state != null) { for (String id : state.keySet()) { try { final Bundle astate = state.getBundle(id); LocalActivityRecord r = mActivities.get(id); if (r != null) { r.instanceState = astate; } else { r = new LocalActivityRecord(id, null); r.instanceState = astate; mActivities.put(id, r); mActivityArray.add(r); } } catch (Exception e) { // Recover from -all- app errors. Log.e(TAG, "Exception thrown when restoring LocalActivityManager state", e); } } } mCurState = CREATED; }
/** * Start a new activity running in the group. Every activity you start must have a unique string * ID associated with it -- this is used to keep track of the activity, so that if you later call * startActivity() again on it the same activity object will be retained. * * <p>When there had previously been an activity started under this id, it may either be destroyed * and a new one started, or the current one re-used, based on these conditions, in order: * * <ul> * <li>If the Intent maps to a different activity component than is currently running, the * current activity is finished and a new one started. * <li>If the current activity uses a non-multiple launch mode (such as singleTop), or the * Intent has the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag set, then the current * activity will remain running and its {@link Activity#onNewIntent(Intent) * Activity.onNewIntent()} method called. * <li>If the new Intent is the same (excluding extras) as the previous one, and the new Intent * does not have the {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set, then the current activity * will remain running as-is. * <li>Otherwise, the current activity will be finished and a new one started. * </ul> * * <p>If the given Intent can not be resolved to an available Activity, this method throws {@link * android.content.ActivityNotFoundException}. * * <p>Warning: There is an issue where, if the Intent does not include an explicit component, we * can restore the state for a different activity class than was previously running when the state * was saved (if the set of available activities changes between those points). * * @param id Unique identifier of the activity to be started * @param intent The Intent describing the activity to be started * @return Returns the window of the activity. The caller needs to take care of adding this window * to a view hierarchy, and likewise dealing with removing the old window if the activity has * changed. * @throws android.content.ActivityNotFoundException */ public Window startActivity(String id, Intent intent) { if (mCurState == INITIALIZING) { throw new IllegalStateException( "Activities can't be added until the containing group has been created."); } boolean adding = false; boolean sameIntent = false; ActivityInfo aInfo = null; // Already have information about the new activity id? LocalActivityRecord r = mActivities.get(id); if (r == null) { // Need to create it... r = new LocalActivityRecord(id, intent); adding = true; } else if (r.intent != null) { sameIntent = r.intent.filterEquals(intent); if (sameIntent) { // We are starting the same activity. aInfo = r.activityInfo; } } if (aInfo == null) { aInfo = mActivityThread.resolveActivityInfo(intent); } // Pause the currently running activity if there is one and only a single // activity is allowed to be running at a time. if (mSingleMode) { LocalActivityRecord old = mResumed; // If there was a previous activity, and it is not the current // activity, we need to stop it. if (old != null && old != r && mCurState == RESUMED) { moveToState(old, STARTED); } } if (adding) { // It's a brand new world. mActivities.put(id, r); mActivityArray.add(r); } else if (r.activityInfo != null) { // If the new activity is the same as the current one, then // we may be able to reuse it. if (aInfo == r.activityInfo || (aInfo.name.equals(r.activityInfo.name) && aInfo.packageName.equals(r.activityInfo.packageName))) { if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE || (intent.getFlags() & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) { // The activity wants onNewIntent() called. ArrayList<ReferrerIntent> intents = new ArrayList<>(1); intents.add(new ReferrerIntent(intent, mParent.getPackageName())); if (localLOGV) Log.v(TAG, r.id + ": new intent"); mActivityThread.performNewIntents(r, intents); r.intent = intent; moveToState(r, mCurState); if (mSingleMode) { mResumed = r; } return r.window; } if (sameIntent && (intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_TOP) == 0) { // We are showing the same thing, so this activity is // just resumed and stays as-is. r.intent = intent; moveToState(r, mCurState); if (mSingleMode) { mResumed = r; } return r.window; } } // The new activity is different than the current one, or it // is a multiple launch activity, so we need to destroy what // is currently there. performDestroy(r, true); } r.intent = intent; r.curState = INITIALIZING; r.activityInfo = aInfo; moveToState(r, mCurState); // When in single mode keep track of the current activity if (mSingleMode) { mResumed = r; } return r.window; }
private void moveToState(LocalActivityRecord r, int desiredState) { if (r.curState == RESTORED || r.curState == DESTROYED) { // startActivity() has not yet been called, so nothing to do. return; } if (r.curState == INITIALIZING) { // Get the lastNonConfigurationInstance for the activity HashMap<String, Object> lastNonConfigurationInstances = mParent.getLastNonConfigurationChildInstances(); Object instanceObj = null; if (lastNonConfigurationInstances != null) { instanceObj = lastNonConfigurationInstances.get(r.id); } Activity.NonConfigurationInstances instance = null; if (instanceObj != null) { instance = new Activity.NonConfigurationInstances(); instance.activity = instanceObj; } // We need to have always created the activity. if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent); if (r.activityInfo == null) { r.activityInfo = mActivityThread.resolveActivityInfo(r.intent); } r.activity = mActivityThread.startActivityNow( mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance); if (r.activity == null) { return; } r.window = r.activity.getWindow(); r.instanceState = null; r.curState = STARTED; if (desiredState == RESUMED) { if (localLOGV) Log.v(TAG, r.id + ": resuming"); mActivityThread.performResumeActivity(r, true); r.curState = RESUMED; } // Don't do anything more here. There is an important case: // if this is being done as part of onCreate() of the group, then // the launching of the activity gets its state a little ahead // of our own (it is now STARTED, while we are only CREATED). // If we just leave things as-is, we'll deal with it as the // group's state catches up. return; } switch (r.curState) { case CREATED: if (desiredState == STARTED) { if (localLOGV) Log.v(TAG, r.id + ": restarting"); mActivityThread.performRestartActivity(r); r.curState = STARTED; } if (desiredState == RESUMED) { if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming"); mActivityThread.performRestartActivity(r); mActivityThread.performResumeActivity(r, true); r.curState = RESUMED; } return; case STARTED: if (desiredState == RESUMED) { // Need to resume it... if (localLOGV) Log.v(TAG, r.id + ": resuming"); mActivityThread.performResumeActivity(r, true); r.instanceState = null; r.curState = RESUMED; } if (desiredState == CREATED) { if (localLOGV) Log.v(TAG, r.id + ": stopping"); mActivityThread.performStopActivity(r, false); r.curState = CREATED; } return; case RESUMED: if (desiredState == STARTED) { if (localLOGV) Log.v(TAG, r.id + ": pausing"); performPause(r, mFinishing); r.curState = STARTED; } if (desiredState == CREATED) { if (localLOGV) Log.v(TAG, r.id + ": pausing"); performPause(r, mFinishing); if (localLOGV) Log.v(TAG, r.id + ": stopping"); mActivityThread.performStopActivity(r, false); r.curState = CREATED; } return; } }