Fragment中呼叫startActivityForResult的那些坑

xxq2dream發表於2018-12-19

本文首發於公眾號“AntDream”,歡迎微信搜尋“AntDream”或掃描文章底部二維碼關注,和我一起每天進步一點點

Fragment中呼叫startActivityForResult要注意幾種情況

  1. 用getActivity方法發起呼叫,只有父Activity的onActivityResult會呼叫,Fragment中的onActivityResult不會被呼叫
  2. 直接發起startActivityForResult呼叫,當前的Fragment的onActivityResult,和父Activity的onActivityResult都會呼叫
  3. 用getParentFragment發起呼叫,則只有父Activity和父Fragment的onActivityResult會被呼叫,當前的Fragment的onActivityResult不會被呼叫。

這裡2和3的前提是如果父activity中重寫了onActivityResult,父Activity的onActivityResult中必須新增super.onActivityResult()

總結起來就是:從哪裡發起呼叫,最終就會走到哪裡。

原始碼分析

Fragment中直接呼叫startActivityForResult

(1)發起startActivityForResult呼叫

這種情況會直接呼叫到Fragment的startActivityForResult方法

//Fragment.class
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
    if (mHost == null) {
        throw new IllegalStateException("Fragment " + this + " not attached to Activity");
    }
    mHost.onStartActivityFromFragment(this /*fragment*/, intent, requestCode, options);
}
複製程式碼

上面的mHost對應的就是Fragment的父FragmentActivity,所以會呼叫到父FragmentActivitystartActivityFromFragment方法

//FragmentActivity.class
public void startActivityFromFragment(Fragment fragment, Intent intent,
        int requestCode, @Nullable Bundle options) {
    mStartedActivityFromFragment = true;
    try {
    	//一般requestCode都不會為-1,所以不會走if裡面
        if (requestCode == -1) {
            ActivityCompat.startActivityForResult(this, intent, -1, options);
            return;
        }
        //這裡檢查requestCode是否越界了,不能超過2^16
        checkForValidRequestCode(requestCode);
        //根據這個requestIndex可以獲取到對應Fragment的唯一標識mWho
        int requestIndex = allocateRequestIndex(fragment);
        //發起startActivityForResult呼叫,這裡requestIndex和requestCode關聯起來
        ActivityCompat.startActivityForResult(
                this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
    } finally {
        mStartedActivityFromFragment = false;
    }
}
複製程式碼

每一個Fragment在內部都有一個唯一的標識欄位who,在FragmentActivity中把所有呼叫startActivityFromFragment方法的fragment的requestCodewho通過key-value的方式儲存在mPendingFragmentActivityResults變數中

private int allocateRequestIndex(Fragment fragment) {
    ...

    int requestIndex = mNextCandidateRequestIndex;
    
    //將requestIndex和fragment的mWho儲存起來
    mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
    mNextCandidateRequestIndex =
            (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;

    return requestIndex;
}
複製程式碼

這裡allocateRequestIndex方法就把requestIndex和Fragment的mWho變數關聯起來了

在上面的startActivityFromFragment方法中呼叫ActivityCompatstartActivityForResult方法發起啟動Activity的時候又把requestIndexrequestCode關聯起來了

這樣後面回撥onActivityResult方法時就可以根據requestCode獲取對應的Fragment,以便呼叫Fragment的onActivityResult方法

最後看一下ActivityCompatstartActivityForResult方法

public static void startActivityForResult(@NonNull Activity activity, @NonNull Intent intent,
        int requestCode, @Nullable Bundle options) {
    if (Build.VERSION.SDK_INT >= 16) {
        activity.startActivityForResult(intent, requestCode, options);
    } else {
        activity.startActivityForResult(intent, requestCode);
    }
}
複製程式碼
(2)onActivityResult方法回撥

通過斷點除錯的方法,我們會發現最先被回撥的就是父Activity的onActivityResult,也就是我們的FragmentActivity的onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int requestIndex = requestCode>>16;
    //requestIndex = 0就表示沒有Fragment發起過startActivityForResult呼叫
    if (requestIndex != 0) {
        requestIndex--;
		
		//根據requestIndex獲取Fragment的who變數
        String who = mPendingFragmentActivityResults.get(requestIndex);
        mPendingFragmentActivityResults.remove(requestIndex);
        if (who == null) {
            Log.w(TAG, "Activity result delivered for unknown Fragment.");
            return;
        }
        
        //然後根據who變數獲取目標Fragment
        Fragment targetFragment = mFragments.findFragmentByWho(who);
        if (targetFragment == null) {
            Log.w(TAG, "Activity result no fragment exists for who: " + who);
        } else {
        	//最後呼叫Fragment的onActivityResult
            targetFragment.onActivityResult(requestCode & 0xffff, resultCode, data);
        }
        return;
    }

    ...
    super.onActivityResult(requestCode, resultCode, data);
}
複製程式碼

從上面的方法中可以看出FragmentActivity中的onActivityResult方法中對於Fragment的startActivityForResult呼叫已經做了處理。

這裡就有一個問題需要注意了,我們一般都會覆寫父Activity中的onActivityResult方法,這個時候我們必須在onActivityResult方法加上super.onActivityResult(),否則Fragment中的onActivityResult方法就沒有辦法回撥到了。

這就是文章開頭中提到的2、3兩點需要注意的原因

getParentFragment發起呼叫

這種情況一般發生在巢狀多層Fragment的時候

getParentFragment發起呼叫的過程和上面的類似,只不過發起呼叫的是當前Fragment的父Fragment,所以最後回撥的也是父Activity的onActivityResult方法和父Fragment的onActivityResult方法。

所以如果想在子Fragment中監聽到onActivityResult方法的回撥,就不要用這種方式

getActivity方法發起呼叫

這個就更簡單了,直接呼叫的是父Activity的onActivityResult方法

//FragmentActivity.class
@Override
    public void startActivityForResult(Intent intent, int requestCode) {
        // If this was started from a Fragment we've already checked the upper 16 bits were not in
        // use, and then repurposed them for the Fragment's index.
        if (!mStartedActivityFromFragment) {
            if (requestCode != -1) {
                checkForValidRequestCode(requestCode);
            }
        }
        super.startActivityForResult(intent, requestCode);
    }

複製程式碼

所以從原始碼也可以看出,這種方式最後不會回撥Fragment的onActivityResult方法

總結

在Fragment中呼叫startActivityForResult以及監聽onActivityResult是很常見的一種應用方式,但是稍不注意就會掉到坑裡,比如因為ActivityonActivityResult方法沒有呼叫super.onActivityResult()方法而導致Fragment中死活接收不到onActivityResult的回撥。

最後總結一下幾種場景的應用步驟:

(1)一個Activity巢狀一層Fragment,Fragment中需要監聽onActivityResult返回結果
  • 直接在Fragment中呼叫startActivityForResult方法
  • 如果父Activity中覆寫了onActivityResult,則需要確保呼叫了super.onActivityResult()方法
  • Fragment中實現onActivityResult方法即可監聽回撥結果
(2)一個Activity巢狀多層Fragment,Fragment中需要監聽onActivityResult返回結果
  • 這種情況和上面的是一樣的,從上面的原始碼中我們可以看到,在哪個Fragment發起的startActivityForResult呼叫,只要父Activity的onActivityResult方法呼叫了super.onActivityResult()方法,Fragment中的onActivityResult方法就會回撥

其實,說白了就是在Fragment中直接呼叫startActivityForResult方法就行,不要用getActivity().startActivityForResult(),也不要用getParentFragment().startActivityForResult(),除非你知道為什麼非要用這2種方式!


                       歡迎關注我的微信公眾號,和我一起每天進步一點點!
複製程式碼

AntDream

相關文章