由整合ARouter引發的一些思考

大頭呆發表於1970-01-01

引子

最近打算把專案的各個頁面按模組的不同做拆分,也就是簡單地想做下元件化的改造吧,那麼這樣一來不同模組的各個頁面就互不依賴了,自然不能直接通過startActivity來顯式跳轉了,自帶的隱式跳轉又略顯笨重,不夠靈活,於是乎就想到了引入路由框架,在github上找找,看到現在用的最多的就是ARouter了吧,看了下主頁的介紹,支援的功能還是挺多的,就它了!

因為今天想講的是頁面之間的資料互動,那先來看下ARouter關於這方面的使用方法:

// 構建標準的路由請求,startActivityForResult// navigation的第一個引數必須是Activity,第二個引數則是RequestCodeARouter.getInstance().build("/test/1")            .withLong("key1", 666L)            .withString("key3", "888")            .withObject("key4", new Test("Jack", "Rose"))            .navigation(this, 5);
複製程式碼

然後在對應的Activity中像解析startActivity傳遞的資料解析這些資料就好了:

// 在支援路由的頁面上新增註解(必選)// 這裡的路徑需要注意的是至少需要有兩級,/xx/xx@Route(path = "/test/activity")public class MainActivity extends Activity { 
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
Long key1 = bundle.getLong("key1");

}
}
}複製程式碼

看過原始碼就很簡單了,之所以是這麼做是因為ARouter只是用上面的withXXX幫我們把資料都儲存到了mBundle物件裡:

public Postcard withString(@Nullable String key, @Nullable String value) { 
mBundle.putString(key, value);
return this;

}public Bundle getExtras() {
return mBundle;

}複製程式碼

最終塞到了Intent物件裡:

// Build intent final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
....//省略 ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
複製程式碼

其實最終就是呼叫普通的startActivityForResult來做頁面跳轉和傳遞資料的。那怎麼返回資料給上一層頁面呢?當然也就是一樣用setResult(int resultCode, Intent data)的方式囉。

問題分析

問題是現在我專案裡用了兩三個Activity,卻有幾十個Fragment,大量模組間的頁面跳轉和資料傳遞都是由Fragment發起的,這樣就產生了一個問題,Fragment雖然也有startActivityForResultonActivityResult,但是根據上面對ARouter的原始碼簡單分析來看,我們壓根呼叫的都是它所依附的Activity的這兩個方法。github上的issues49是這麼解決的:

  @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
super.onActivityResult(requestCode, resultCode, data);
List<
Fragment>
allFragments = getSupportFragmentManager().getFragments();
if (allFragments != null) {
for (Fragment fragment : allFragments) {
fragment.onActivityResult(requestCode, resultCode, data);

}
}
}複製程式碼

手動把資料從Activity的onActivityResult傳遞到fragment裡,這樣簡單粗暴,所有attach到這個Acttivty的Fragment都會收到資料,當然再在對應的Fragment裡判斷requestCoderesultCode,這樣就沒問題了嗎?

原始碼分析

要解決這個問題,我們來分析下FragmentstartActivityForResultonActivityResult

startActivityForResult

   public void startActivityForResult(@SuppressLint("UnknownNullness") 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方法:

public void startActivityFromFragment(Fragment fragment, Intent intent,        int requestCode, @Nullable Bundle options) { 
....//省略 //檢查requestCode大小,不能超過0xffff checkForValidRequestCode(requestCode);
//分配給這個Fragment唯一的requestIndex,根據這個requestIndex可以獲取到對應Fragment的唯一標識mWho int requestIndex = allocateRequestIndex(fragment);
//之後就呼叫activity的startActivityForResult ActivityCompat.startActivityForResult( this, intent, ((requestIndex + 1) <
<
16) + (requestCode &
0xffff), options);

}複製程式碼

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

 // Allocates the next available startActivityForResult request index.    private int allocateRequestIndex(@NonNull Fragment fragment) { 
//找到一個尚未分配的requestIndex while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >
= 0) {
mNextCandidateRequestIndex = (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;

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

}複製程式碼

mWho是fragment一個變數,用來唯一標識一個Framgment。

 @NonNull    String mWho = UUID.randomUUID().toString();
複製程式碼

所以通過呼叫FragmentstartActivityForResult,我們會生成一個requestIndex,來和fragment的mWho建立對映關係,至此Fragment物件的任務就完成了,然後呼叫的就是Ativity的startActivityForResult了,不過它的requestCode也不是Fragment的requestCode,而是((requestIndex + 1) <
<
16) + (requestCode &
0xffff)

onActivityResult

因為最終呼叫的是發起跳轉的Fragment所attach的FragmentActivitystartActivityForResult,只是requestCode做了特殊處理了而已,Fragment並不需要參與跳轉,所以最先被回撥的也就是這個FragmentActivityonActivityResult

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { 
mFragments.noteStateNotSaved();
//解析得到requestIndex 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,也就是發起startActivityForResult的那個`fragment` Fragment targetFragment = mFragments.findFragmentByWho(who);
if (targetFragment == null) {
Log.w(TAG, "Activity result no fragment exists for who: " + who);

} else {
////解析得到最初fragment的requestCode,最後呼叫Fragment的onActivityResult targetFragment.onActivityResult(requestCode &
0xffff, resultCode, data);

} return;

} ... super.onActivityResult(requestCode, resultCode, data);

}複製程式碼

下面總結下兩種情況表現:

Fragment.onActivityResult FragmentActivity.onActivityResult
Fragment.startActivityForResult 正常接收 異常接收,requestCode不對
FragmentActivity.startActivityForResult 不能接收 正常接收

所以上面的相容方法應該改成:

  @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
super.onActivityResult(requestCode, resultCode, data);
List<
Fragment>
allFragments = getSupportFragmentManager().getFragments();
if (allFragments != null) {
for (Fragment fragment : allFragments) {
fragment.onActivityResult(requestCode&
0xffff, resultCode, data);

}
}
}複製程式碼

那最後我採取這種方案了嗎?

由整合ARouter引發的一些思考

思考

通過上面的一系列的分析,我其實得到的最有用的資訊是,FragmentActivity原來還有這麼一個方法:

public void startActivityFromFragment( Fragment fragment, Intent intent, int requestCode) {複製程式碼

注意這是個public方法,意味著不需要反射就可以呼叫了,所以我們就能很好地利用它了。

考慮到上面的相容方法太粗暴了,不夠優雅,而且路由本來就是用來解耦程式碼的,這樣處理反而產生了耦合。我那個小專案也不需要ARouter那些攔截器啊,全域性降級啊這些高階用法,所以我把ARouter程式碼下下來,刪刪減減,並新增了navigation(Fragment mFragment, int requestCode)方法:

        if (requestCode >
= 0) {
// Need start for result if (currentContext is FragmentActivity &
&
fragment != null) {
currentContext.startActivityFromFragment(fragment, intent, requestCode)
} else if (currentContext is Activity) {
ActivityCompat.startActivityForResult(currentContext, intent, requestCode, null)
} else {
Logs.defaults.e("Must use [navigation(activity, ...)] to support [startActivityForResult]")
}
} else {
ActivityCompat.startActivity(currentContext, intent, null)
}複製程式碼

應用

可以利用上述方法,拋棄繁瑣模板化的startActivityForResultonActivityResult和各種code,新增一個空白的Fragment,並採用回撥的方式處理返回結果:

object MyRouter { 
private var requestCode = AtomicInteger(1) fun navigation(fragmentActivity: FragmentActivity, intent: Intent, callback: (Int, Intent?) ->
Unit) {
val code = requestCode.getAndIncrement() val emptyFragment = EmptyFragment() emptyFragment.callback=callback emptyFragment.requestCode= code fragmentActivity.supportFragmentManager.beginTransaction().add(emptyFragment, "$code").commit() fragmentActivity.startActivityFromFragment(emptyFragment, intent, code)
} fun navigation(fragment: Fragment, intent: Intent, callback: (Int, Intent?) ->
Unit) {
val code = requestCode.getAndIncrement() val emptyFragment = EmptyFragment() emptyFragment.callback=callback emptyFragment.requestCode= code fragment.activity?.startActivityFromFragment(emptyFragment, intent, code)
}
}class EmptyFragment: Fragment() {
@IntRange(to = 0xFFFF) var requestCode: Int = -1 var callback: ((Int, Intent?) ->
Unit)? = null override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) if (this.requestCode == requestCode) {
callback?.invoke(resultCode, data)
} activity?.supportFragmentManager?.beginTransaction()?.remove(this@EmptyFragment)?.commit()
}
}複製程式碼

這樣我們跳轉和拿到返回資料的方式也就變得比較簡潔和優雅了:

    fun toMain2Activity() { 
val intent = Intent(this@MainActivity, Main2Activity::class.java) MyRouter.navigation(this, intent) {
resultCode, data ->
Log.d("result", "$resultCode ${data?.getStringExtra("key1")
}
"
)
}
}複製程式碼

順手也把這種方式的跳轉整合到了我的縮減版ARouter中了,程式碼已傳到github。

來源:https://juejin.im/post/5c31d4bff265da61307506a6

相關文章