After the release of version 9.13.0, there suddenly appeared a first crash, no more talk about the stack

A seemingly ordinary crash was reported at the start of the homepage of Hantao, but a very strange problem was subsequently caused. This article makes a detailed record of this problem.

First check

After finding the problem, I found the log and made a preliminary analysis. In order to see what scenario would cause the crash, I first looked at the log.

The above log printed out the life cycle log of taobao before the problem and the stack when the last line crashed, where Welcome is the Welcome page of taobao and TBMainActivity is the home page of taobao (other activities are not important and will not be paid attention to for the time being). One very strange thing is that TBMainActivity executes two onCreate life cycles, but there is no onDestory between them, and TBMainActivity starts in singleTask mode. As an Android developer, the startup mode of an Activity is very familiar. When starting an Activity in singleTask mode, if the Activity is already in the stack, The action should be to remove all activities from the Activity and reuse the Activity directly.

The first reaction to this log is that there is a special case online, mobile shopping will have two front pages in the stack!!

But throw out the conclusion that no, because the startup mode of the first page of very clear, could not exist two instances in a stack, everybody says the two instances should be here in two stack, I should think so, just temporarily abandoned the direction, the focus of the screening on the crash took two front page, Not why pull up two home page.

Thought he had solved the case

After several logs were pulled and observed, a common point was found. Before each crash, the welcome page was pulled up on the home page. It was judged that the welcome page was quickly finished when the back button was clicked on the home page. As a result, the first home page did not finish and then pulled up a new one, so the protection logic was done when the home page finish and the first gray scale was performed.

A few days later, I observed the effect and found that the number of crashes had greatly decreased from more than 20% to 6%. I finally felt relieved and thought that I had solved the case.

A few minutes later, the crash dropped back to the normal water level. We have to look into it.

This one really cracked the case

Last time gray scale failed, I reflected, still want to go back to crash itself. Returning to Crash itself, our own code on the stack appears in the red box in the figure:

FragmentTabHost is the Fragment manager on the homepage of Mobile Shopping, but this code hasn’t changed in several versions. Code see the TabHost onAttachedToWindow function, finally call FragmentManager executePendingTransactions method in the class of switching request submitted fragments. The intermediate code is a stack call all the way to the FragmentManagerImpl moveToState method. The system source code is as follows:

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { ...... case Fragment.CREATED: if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (! f.mFromLayout) { ViewGroup container = null; if (f.mContainerId ! = 0) { container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && ! f.mRestored) { throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + f.getResources().getResourceName(f.mContainerId) + ") for fragment " + f));  } } f.mContainer = container; f.mView = f.performCreateView(f.getLayoutInflater( f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView ! = null) { f.mInnerView = f.mView; if (Build.VERSION.SDK_INT >= 11) { ViewCompat.setSaveFromParentEnabled(f.mView, false); } else { f.mView = NoSaveStateFrameLayout.wrap(f.mView); } if (container ! = null) { Animation anim = loadAnimation(f, transit, true, transitionStyle); if (anim ! = null) { setHWLayerAnimListenerIfAlpha(f.mView, anim); f.mView.startAnimation(anim); } container. AddView (f.view); } if (f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); } else { f.mInnerView = null; } } f.performActivityCreated(f.mSavedFragmentState); if (f.mView ! = null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; }... }Copy the code

Container is a ViewGroup, and the addView parameter f. view is assigned to performCreateView. Container is a ViewGroup.

View performCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (mChildFragmentManager ! = null) { mChildFragmentManager.noteStateNotSaved(); } return onCreateView(inflater, container, savedInstanceState); }Copy the code

The performCreateView function is called by the Fragment onCreateView. This function is implemented by the Fragment itself.

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { ...... if (mRootView == null) { View view = workflow.onCreateView(inflater, container, savedInstanceState); return view; }... return mRootView; }Copy the code

If mRootView is null, call onCreateView from coldStartUpWorkflow to create a view and return, If mRootView is not null, remove the parent node of the view and nothing is wrong. That problem only occurs when a view created in coldStartUpWorkflow might have a problem.

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); return create(inflater,container); } private View create(LayoutInflater inflater, ViewGroup container){ View rootView = null; . rootView = RevisionReuseViewController.getInstance().getHomePageView(inflater, container); . return rootView; }Copy the code

This function is called onCreateView, but the view object is obtained from a function that starts with get.

public class RevisionReuseViewController { private WeakReference<View> homePageViewWR; private RevisionReuseViewController(){} private static class SingleHandler { private static RevisionReuseViewController instance = new RevisionReuseViewController(); } public static RevisionReuseViewController getInstance() { return RevisionReuseViewController.SingleHandler.instance; } public View getHomePageView(LayoutInflater inflater, ViewGroup container) { View view = homePageViewWR == null ? null : homePageViewWR.get(); if (view == null) { homePageViewWR = new WeakReference<>(inflater.inflate(R.layout.activity_homepage, container, false)); } return homePageViewWR.get(); }}Copy the code

RevisionReuseViewController this singleton should make the caching logic, and trigger the logic did not remove the cache of the view of the parent node.

Determined the problem, immediately the second gray scale, sure enough, crash no longer appeared.

But the question remains, why are there two home pages?

The two front page

Crash was solved, but the more difficult problem was still behind. Crash only exposed the problem, that is to say, two home pages may cause the crash, but not all two home pages cause the crash. We still don’t know how serious the problem is online. Will there be a lot of unexpected problems.

Probably many students will say that there are two singleTask activities, they can be in two stacks. But when we first grayscale, we have already confirmed that they are in a stack, this time the clue is completely broken.

God never shuts one door but he opens another, on the front page classmate the day before the repair code integration into the hand of tao, suddenly received a certain message, a classmate told me that they have a crash, want me to confirm, after I saw a stack found to solve the problem in front of the stack is consistent, you tell him, this version repair, low probability for the time being you don’t have tube.

But he told me there was going to be a crash.

Let him show me the way, and it will happen. The code to see a crash cause: the original student in the code called startActivtyForResult to start the home page.

The internal call to startActivity is implemented by startActivityForResult, but my intuition tells me that this is the problem, so I wrote a demo to verify my idea.

The demo is very simple. There are two activities. The first activity is in singleTask mode, and one button is clicked to invoke the second activity normally. The first button calls startActivity to start the first activity, the second button starts the first activity with startActivityForResult, and starts the requsetCode passing 1. Specific code will not be posted, a very simple demo. When I click the first button for the second activity, I see only one activity left in the stack, as expected. But when I clicked the second button, something magical happened. There are obviously two MainActivities in a TaskRecord, and I confirm that the MainActivity is singleTask. StartActivityForResult is also called in startActivity, but the value passed is -1. Is this the value that determines the behavior?

The truth

The following code is from Android 9.0.

public void startActivity(Intent intent) { this.startActivity(intent, null); } @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options ! = null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { options = transferSpringboardActivityOptions(options); Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar ! = null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { // If this start is requesting a result, we can avoid making // the activity visible until the result is received. Setting // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the // activity hidden during this time, to avoid flickering. // This can only be done when a result is requested because // that guarantees we will get information back when the // activity is finished, no matter what happens to it. mStartedActivity = true; } cancelInputsAndStartExitTransition(options); // TODO Consider clearing/flushing other event sources and events for child windows. } else { if (options ! = null) { mParent.startActivityFromChild(this, intent, requestCode, options); } else { // Note we want to go through this method for compatibility with // existing applications that may have overridden it. mParent.startActivityFromChild(this, intent, requestCode); }}} You can see that startActivity is called to startActivityForResult, StartActivityForResult calls in the Instrumentation execStartActivity perform follow-up process (below the mParent else. StartActivityFromChild This is where the process is eventually called. public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ...... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityTaskManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target ! = null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; }Copy the code

You can see that startActivity is called to startActivityForResult, StartActivityForResult calls in the Instrumentation execStartActivity perform follow-up process (below the mParent else. StartActivityFromChild This is where the process is eventually called.

public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ...... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityTaskManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target ! = null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; }Copy the code

Has nothing to do inside delete some logic, the final call to startActivity function in ActivityTaskManagerService continue to process.


int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,
            boolean validateIncomingUser) {
        enforceNotIsolatedCaller("startActivityAsUser");

        userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
                Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");

        // TODO: Switch to user app stacks here.
        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setMayWait(userId)
                .execute();

    }
Copy the code

ActivityTaskManagerService startActivity method just convert the control, the final call to startActivityAsUser, the control to the ActivityStarter, passing parameters, Finally, execute the execute function to execute the subsequent flow.

int execute() { try { if (mRequest.mayWait) { return startActivityMayWait(mRequest.caller, mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid, mRequest.intent, mRequest.resolvedType, mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, mRequest.resultWho, mRequest.requestCode, mRequest.startFlags, mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig, mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId, mRequest.inTask, mRequest.reason, mRequest.allowPendingRemoteAnimationRegistryLookup, mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart); } 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, mRequest.allowPendingRemoteAnimationRegistryLookup, mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart); } } finally { onExecutionComplete(); }}Copy the code

Because the mRequest.mayWait parameter is true, startActivityMayWait is called.

private int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, int requestRealCallingPid, int requestRealCallingUid, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration globalConfig, SafeActivityOptions options, boolean ignoreTargetSecurity, int userId, TaskRecord inTask, String reason, boolean allowPendingRemoteAnimationRegistryLookup, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { ...... int res = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason, allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent, allowBackgroundActivityStart); . return res; }Copy the code

Remove some of the redundant logic, leave the critical path, and see that you move to startActivity to continue,

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent, String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int realCallingPid, int realCallingUid, int startFlags, SafeActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask, String reason, boolean allowPendingRemoteAnimationRegistryLookup, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { ...... mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord, inTask, allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent, allowBackgroundActivityStart); . return getExternalResult(mLastStartActivityResult); }Copy the code

The middle of the operation, eventually called the following


private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
        int result = START_CANCELED;
        final ActivityStack startedActivityStack;
        try {
            mService.mWindowManager.deferSurfaceLayout();
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
        } finally {
            
        }

        postStartActivityProcessing(r, result, startedActivityStack);

        return result;
}

Copy the code

I’m finally going to call startActivityUnchecked and let’s see what’s going on in there.

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity, boolean restrictedBgActivity) { setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, voiceInteractor, restrictedBgActivity); . // If the activity being launched is the same as the one currently at the top, then // we need to check if it should only be launched once. final ActivityStack topStack = mRootActivityContainer.getTopDisplayFocusedStack(); final ActivityRecord topFocused = topStack.getTopActivity(); final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop); final boolean dontStart = top ! = null && mStartActivity.resultTo == null && top.mActivityComponent.equals(mStartActivity.mActivityComponent) && top.mUserId == mStartActivity.mUserId && top.attachedToProcess() && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) ! = 0 || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) // This allows home activity to automatically launch on secondary display when // display added, if home was the top activity on default display, instead of // sending new intent to the home activity on default display. && (! top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId); if (dontStart) { // For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; if (mDoResume) { mRootActivityContainer.resumeFocusedStacksTopActivities(); } ActivityOptions.abort(mOptions); if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) ! = 0) { // We don't need to start a new activity, and the client said not to do // anything if that is the case, so this is it! return START_RETURN_INTENT_TO_CALLER; } deliverNewIntent(top); // Don't use mStartActivity.task to show the toast. We're not starting a new activity // but reusing 'top'. Fields in mStartActivity may not be fully initialized. mSupervisor.handleNonResizableTaskIfNeeded(top.getTaskRecord(), preferredWindowingMode, mPreferredDisplayId, topStack); return START_DELIVERED_TO_TOP; }... }Copy the code

Taking some of the logic out of the way, you can see the variable dontStart. The value of the following variable controls the reuse process during the activity startup process. When it is true, By resumeFocusedStacksTopActivities directly from the stack up an existing activity, when he is false new pull up an activity to receive the request.

boolean resumeFocusedStacksTopActivities( ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) { if (! mStackSupervisor.readyToResume()) { return false; } boolean result = false; if (targetStack ! = null && (targetStack.isTopStackOnDisplay() || getTopDisplayFocusedStack() == targetStack)) { result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { boolean resumedOnDisplay = false; final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); if (! stack.isFocusableAndVisible() || topRunningActivity == null) { continue; } if (stack == targetStack) { // Simply update the result for targetStack because the targetStack had // already resumed  in above. We don't want to resume it again, especially in // some cases, it would cause a second launch failure if app process was dead. resumedOnDisplay |= result; continue; } if (display.isTopStack(stack) && topRunningActivity.isState(RESUMED)) { // Kick off any lingering app transitions form  the MoveTaskToFront operation, // but only consider the top task and stack on that display. stack.executeAppTransition(targetOptions); } else { resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target); } } if (! resumedOnDisplay) { // In cases when there are no valid activities (e.g. device just booted or launcher // crashed) it's  possible that nothing was resumed on a display. Requesting resume // of top activity in focused stack explicitly will make sure that at least home // activity is started and resumed, and no recursion occurs. final ActivityStack focusedStack = display.getFocusedStack(); if (focusedStack ! = null) { focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions); } } } return result; }Copy the code

ResumeFocusedStacksTopActivities function first found in the target stack, positioning to the target need to pull up the activity in the stack, Eventually resume directly by resumeTopActivityUncheckedLocked function activity, specific process here do not do, students can be interested in reading the source code.

Going back to the dontStart variable, the name appears to be whether a new activity needs to be started, and it has one in its assignment logic


mStartActivity.resultTo == null
Copy the code

So this logic, and this resultTo is assigned at what point, you see that when we go into startActivityUnchecked we call setInitialState to initialize some state.

private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask, boolean doResume, int startFlags, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, boolean restrictedBgActivity) { reset(false /* clearRequest */); mStartActivity = r; mIntent = r.intent; mOptions = options; . }Copy the code

This function initializes the mStartActivity global variable with the ActivityRecord passed in, continuing back to where the ActivityRecord was built.

final class ActivityRecord extends ConfigurationContainer { ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller, int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType, ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo, String _resultWho, int _reqCode, boolean _componentSpecified, boolean _rootVoiceInteraction, ActivityStackSupervisor supervisor, ActivityOptions options, ActivityRecord sourceRecord) { mAtmService = _service; mRootActivityContainer = _service.mRootActivityContainer; appToken = new Token(this, _intent); info = aInfo; launchedFromPid = _launchedFromPid; launchedFromUid = _launchedFromUid; launchedFromPackage = _launchedFromPackage; mUserId = UserHandle.getUserId(aInfo.applicationInfo.uid); intent = _intent; shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; componentSpecified = _componentSpecified; rootVoiceInteraction = _rootVoiceInteraction; mLastReportedConfiguration = new MergedConfiguration(_configuration); resultTo = _resultTo; resultWho = _resultWho; requestCode = _reqCode; setState(INITIALIZING, "ActivityRecord ctor"); frontOfTask = false; launchFailed = false; stopped = false; delayedResume = false; finishing = false; deferRelaunchUntilPaused = false; keysPaused = false; inHistory = false; visible = false; nowVisible = false; mDrawn = false; idle = false; hasBeenLaunched = false; mStackSupervisor = supervisor; } } private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent, String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int realCallingPid, int realCallingUid, int startFlags, SafeActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { ...... ActivityRecord resultRecord = null; if (resultTo ! = null) { sourceRecord = mRootActivityContainer.isInAnyStack(resultTo); if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Will send result to " + resultTo + " " + sourceRecord); if (sourceRecord ! = null) { if (requestCode >= 0 && ! sourceRecord.finishing) { resultRecord = sourceRecord; }}}... ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid, callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, componentSpecified, voiceSession ! = null, mSupervisor, checkedOptions, sourceRecord); . }Copy the code

The resultTo variable in ActivityRecord is assigned to the startActivity function. This variable determines whether to assign based on whether the incoming requestCode is greater than 0.

The requestCode is -1 when we call startActivity directly, The call to startActivityForResult passes in a value greater than 0, which misses the logic for the activity to reuse during startup. The result is an activity that is a singleTask but has two instances on the same stack.

conclusion

This is understandable. When we call startActivityForResult, we expect the Activity to return to the current Activity after it exits. However, if we use singleTask’s reuse logic, It will clear the original Activity, and can not return, so we can say that this is a bug, also can say that Android does this layer of backstop for the developer students.

However, the singleTask Activity can only have one instance in the stack.

— — — — — — — — — — — — — — — — — — — — — — —

The author | nine chongqing

Edit | orange

New retail product | alibaba tao technology