天天看點

一個 Crash 引發的血案背景第一次排查以為自己破案了這次真的破案了兩個首頁真相大白總結

原創 九渝 淘系技術  5月19日

一個 Crash 引發的血案背景第一次排查以為自己破案了這次真的破案了兩個首頁真相大白總結

背景

手淘 9.13.0 版本上線後,突然出現了一個首現 crash,廢話不多說上堆棧

一個 Crash 引發的血案背景第一次排查以為自己破案了這次真的破案了兩個首頁真相大白總結

看起來很普通的一個 crash,報在手淘首頁啟動的瞬間,但是後續引發了一個很奇怪的問題,這篇文章就對這個問題的記錄做一個詳細記錄。

第一次排查

發現問題後,找到日志做了初步分析,想看到到底是什麼場景下會引發這個 crash,先看日志。

一個 Crash 引發的血案背景第一次排查以為自己破案了這次真的破案了兩個首頁真相大白總結

上面日志中列印出來了出問題之前的手淘生命周期日志和最後一行發生 crash 時候的堆棧,其中 Welcome 是手淘的歡迎頁面,TBMainActivity 是手淘首頁(其他的 activity 不是重點,暫時不關注),發現了一個非常奇怪的事情,TBMainActivity 執行了兩次 onCreate 生命周期,但是兩次生命周期之間卻沒有執行 onDestory,而 TBMainActivity 的啟動模式是 singleTask 的。作為 Android 開發同學來說,Activity 的啟動模式再熟悉不過了,當啟動一個 Activity 是 singleTask 模式的時候,如果這個 Activity 已經存在棧中,那麼行為應該是把該 Activity 之上所有 Activity 都直接清除掉,然後直接複用 Activity,借用網上一張圖說明:

一個 Crash 引發的血案背景第一次排查以為自己破案了這次真的破案了兩個首頁真相大白總結

看到這個日志的第一反應是線上存在一種特殊情況,手淘會在棧中同時存在兩個首頁!!

但是抛出結論的時候讓否了,因為首頁的啟動模式很清楚,不可能在一個棧裡面存在兩個執行個體,大家都說這裡的兩個執行個體應該是在兩個棧裡面,我也覺得應該是這樣,就暫時放棄了這個方向的排查,把排查的重心放在拉起兩個首頁 crash,而不是為什麼拉起了兩個首頁。

以為自己破案了

連續拉取好幾份日志觀察之後發現一個共同點,每次發生 crash 之前都是在首頁的時候拉起了 welcome 頁面,判斷是在首頁點選 back 按鍵的時候快速 finish 自己,瞬間使用者又點選了 icon 打開了手淘,導緻第一個首頁還沒有 finish 然後又拉起了一個新的,于是在首頁 finish 的時候做了保護邏輯,進行了第一次灰階。

過幾天觀察效果,發現 crash 數量大幅減少,從占比 20% 多降到 6%,終于松了一口氣,以為自己破案了。

但是一個周六突然接到了告警電話,這個 crash 瘋長,過幾分鐘之後又回落到正常水位,看來還沒有完全破案。事情還得查。

這次真的破案了

上次灰階失敗,我反思了一下,還是要回到 crash 本身。

回到 crash 本身,堆棧中我們自己的代碼出現在圖中紅框中的代碼:

一個 Crash 引發的血案背景第一次排查以為自己破案了這次真的破案了兩個首頁真相大白總結

FragmentTabHost 是手淘首頁中的 Fragment 管理器,但是這塊代碼确實好幾個版本都沒有變動了。代碼中看到 TabHost 在 onAttachedToWindow 函數中,最終調用了FragmentManager 類中的 executePendingTransactions 方法送出了 Fragment 的切換請求。中間代碼就是一路按照堆棧調用,調用到 FragmentManagerImpl 的 moveToState 方法。系統源碼如下:

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);
                                    }
                                    //在這裡調用了 addView
                                    container.addView(f.mView);
                                }
                                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;
                    }
                ......
                }
                    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);                                    }                                    //在這裡調用了 addView                                    container.addView(f.mView);                                }                                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;                    }                ......                }      

在上面代碼中,調用了 container 的 addView 方法,container 是一個個ViewGroup,而這個 addView 的參數 f.View 是在 performCreateView 中進行指派的,系統源碼如下:

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

而 performCreateView 函數最終調用到 Fragment 的 onCreateView 中,這個函數是首頁 Fragment 自己實作的,找負責同學要來代碼繼續看。

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;
    }
      

看起來邏輯是首頁 Fragment 做了自己的緩存處理,如果 mRootView 為 null,則從 coldStartUpWorkflow 中調用 onCreateView 建立一個 view 并傳回,如果 mRootView 不為 null,則去掉 view 的父節點,一點毛病沒有。那問題隻能出現在 coldStartUpWorkflow 中建立的 view 可能有問題。

@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;
    }      

發現這個函數名字雖然叫 onCreateView,但是其中的 view 對象卻是通過一個 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();
    }

}      

RevisionReuseViewController 這個單例裡面竟然自己做了緩存邏輯,而觸發這個邏輯的時候并沒有把緩存的 view 的父節點 remove 掉。

确定了問題所在,馬上第二次灰階,果不其然 crash 不再出現了。

但是還遺留下來一個問題, 為什麼會有兩個首頁呢?

兩個首頁

crash 解決了,但是更棘手的問題還在後面,crash 隻是把這個問題暴露出來了,也就是說兩個首頁有可能引起這個 crash,但是不是所有兩個首頁都引發這個 crash,我們還不知道這個問題線上上到底有多麼嚴重,會不會出現很多意想不到的問題。

可能很多同學會說,出現兩個 singleTask 的 activity ,他們有可能在兩個棧裡面。但是第一次灰階的時候我們已經确定了,他們是在一個棧裡面,這回線索徹底斷了。

天無絕人之路,在首頁同學把修複代碼內建進手淘的前一天,突然接到了一個釘釘消息,一個同學告訴我,他們有個 crash,要我确認,我看到堆棧之後發現跟前面解決的問題堆棧一緻,就告訴他,這個版本修複,低機率的話暫時不用管。

但是他告訴我,我這邊必現這個 crash。

按耐住激動的心,讓他告訴我複現路徑,果然必現。要來代碼看到了一個崩快的原因:原來有同學在代碼裡面調用 startActivtyForResult 啟動了首頁。

想了一下也沒有什麼問題,因為 startActivity 内部調用就是 startActivityForResult 來實作的,但是直覺告訴我就是這裡有問題,于是寫個 demo 驗證一下我的想法。

demo 很簡單,兩個 activity ,第一個 activity 是 singleTask 模式,其中一個按鈕,點選正常調用到第二個 activity,第二個 activity 是标準模式,其中兩個按鈕,第一個按鈕調用 startActivity 拉起第一個 activity,第二個按鈕通過 startActivityForResult 拉起第一個 activity,啟動 requsetCode 傳遞的是1。具體代碼就不貼了,很簡單一個 demo。當我點選第二個 activity 的第一個按鈕的時候,看到棧裡面隻剩下一個 activity,符合預期。但是當我點選第二個按鈕的時候,神奇的一幕出現了。很明顯一個 TaskRecord 裡面有兩個 MainActivity,而且我确認這個 MainActivity 是 singleTask 的。奇怪的是 startActivity 裡面調用的也是 startActivityForResult,隻是傳遞的值是 -1 ,難道真的是這個值決定了這個行為麼?

真相大白

看源碼吧(以下代碼來自 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);
            }
        }
    }
可以看到 startActivity 調用到 startActivityForResult,startActivityForResult 調用了 Instrumentation 中的 execStartActivity 執行後續流程(下面 else 中的 mParent.startActivityFromChild 的流程最終也調用到了此處)。
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;
    }      

可以看到startActivity 調用到 startActivityForResult,startActivityForResult 調用了 Instrumentation 中的 execStartActivity 執行後續流程(下面 else 中的 mParent.startActivityFromChild 的流程最終也調用到了此處)。

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;
    }      

裡面删除一些無關邏輯,最終調用到 ActivityTaskManagerService 中的 startActivity 函數繼續流程。

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();

    }      

ActivityTaskManagerService 的 startActivity 方法隻是轉換了一下控制權,最終調用到 startActivityAsUser中,把控制權交給 ActivityStarter,傳遞參數,并最終執行 execute 函數執行後續流程。

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();
        }
    }      

由于參數重mRequest.mayWait的值為true,是以調用到startActivityMayWait。

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;
}      

去掉一些多餘邏輯,留下關鍵路徑,看到轉移到 startActivity 繼續執行,

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);
    }
      

中間一頓操作,最終調用到下面

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;
}      

終于調用到了 startActivityUnchecked 這個地方看下裡面都做了什麼。

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;
        }
        ......            
}      

把一些亂七八糟的邏輯去除掉,抽絲剝繭,看到了dontStart 這個變量,下面這個變量的值控制了activity 啟動過程中的複用流程,當它是true 的時候,則通過 resumeFocusedStacksTopActivities直接從棧中拉起一個已經存在的activity ,當他是 false 的時候新拉起一個 activity 接收這次請求。

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;
    }      

resumeFocusedStacksTopActivities 函數中首先找到了目标棧,定位到目标棧中需要拉起的 activity,最終通過 resumeTopActivityUncheckedLocked 函數直接 resume activity,具體流程這裡不做展開,有興趣的同學可以自己閱讀源碼。

回到 dontStart 這個變量,名字看起來就是是否需要啟動一個新的 activity,而且他的指派邏輯裡面有一個

mStartActivity.resultTo == null      

這個邏輯,而這個resultTo是在什麼時候指派的呢,看到進入startActivityUnchecked的時候調用了setInitialState函數來初始化一些狀态。

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;
        ......
    }      

在這個函數裡面用傳入的 ActivityRecord 初始化了 mStartActivity 這個全局變量,繼續往回回溯這個 ActivityRecord 是在那裡建構的。

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);
            ......
}      

發現在 startActivity 函數中建構了 ActivityRecord,而 ActivityRecord 裡面的 resultTo 變量是通過 resultRecord 進行指派的,這個變量會根據傳入的 requestCode 是否大于 0 來判斷是否指派。

那就很明白了,我們直接調用 startActivity 的時候 requestCode 是 -1 ,而調用傳入 startActivityForResult 的時候傳入的值大于 0 導緻在這個位置錯過了啟動過程中 activity 複用的邏輯。最終導緻了雖然是一個 singleTask 的 activity 但是卻在同一個棧中同時存在兩個執行個體。

總結

其實這個現象也是可以了解的,當我們調用 startActivityForResult 的時候,當然是希望拉起來的 Activity 生命周期結束退出後還能回到目前的 Activity,但是如果直接走 singleTask 的複用邏輯,就會把原 Activity 清掉,則無法傳回了,可以說這是一個 bug,也可以說 Android 為開發同學做了這一層兜底。

但是 singleTask 的 Activity 隻能在棧記憶體在一個執行個體的面試題是不是需要改改了。

繼續閱讀