本文首發於公眾號“AntDream”,歡迎微信搜尋“AntDream”或掃描文章底部二維碼關注,和我一起每天進步一點點
Fragment中呼叫startActivityForResult要注意幾種情況
- 用getActivity方法發起呼叫,只有父Activity的onActivityResult會呼叫,Fragment中的onActivityResult不會被呼叫
- 直接發起startActivityForResult呼叫,當前的Fragment的onActivityResult,和父Activity的onActivityResult都會呼叫
- 用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
,所以會呼叫到父FragmentActivity
的startActivityFromFragment
方法
//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的requestCode
和who
通過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
方法中呼叫ActivityCompat
的startActivityForResult
方法發起啟動Activity的時候又把requestIndex
和requestCode
關聯起來了
這樣後面回撥onActivityResult
方法時就可以根據requestCode獲取對應的Fragment,以便呼叫Fragment的onActivityResult
方法
最後看一下ActivityCompat
的startActivityForResult
方法
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
是很常見的一種應用方式,但是稍不注意就會掉到坑裡,比如因為Activity
的onActivityResult
方法沒有呼叫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種方式!
歡迎關注我的微信公眾號,和我一起每天進步一點點!
複製程式碼