Android 多個Fragment巢狀導致的三大BUG

weixin_34067049發表於2016-12-02

Android有碎片化的問題,當然本文說的碎片化不是指的系統版本碎片化的問題,而是Fragment元件碎片化的問題。

很久之前,在Android 3.1系統釋出的時候,Google推出了使用Fragment來更加容易地開發平板和手機應用,雖然Activity還是頁面結構的主體,但是卻可以在其基礎上使用多個Fragment來構建頁面,這些Fragment都是有各自的生命週期的。

最常見的是列表和詳情頁面使用Fragment,如果在手機裝置上,這個兩個一般都是在獨立的Activity頁面中,但是在平板上這兩個Fragment往往都是巢狀在一個Activity中。

當然,在開發過程中,正常情況下都是沒有問題的。

1、特殊的Fragment

如果想要使用Fragment來開發應用並且適配低版本系統,必須要使用Google提供的Support Library(V4).

Support Library這個相容庫設計地有些莫名其妙,它為了提供向後相容的特性,替換了整個Fragment框架。比如,執行在3.1之後的系統系統上,使用的Fragment還是Support Library所提供的,而不是基於系統自身的(這一點在講到後面的時候非常重要)。

2、Fragment的巢狀

使用Fragment時最繁瑣複雜的就是多個Fragment之間的相互通訊,必須通過Activity作為中間者傳遞。巢狀的Fragment一開始是不支援的,因為會導致了各式各樣的bug。直到API 17,也就是Jelly Bean 4.2,終於開始支援巢狀的Fragments,並且這個功能也被新增到了Support Library裡面。使用Fragment來搭建頁面的夢想實現的一天終於到來了,這種方式有一個巨大的好處,就是解放Activity,使用多個Fragment元件來承載UI和邏輯。

夢想很美好,現實很殘酷!

3、Fragment巢狀BUG之一:突變的動畫效果

問題: 互動體驗做到極致的APP,都會使UI具有平滑順暢的動畫效果。FragmentManager是允許通過設定轉場過渡動畫的。但是,退出動畫會導致巢狀的Fragment在動畫剛剛開始時就瞬間消失。

原因: Fragments有一個巢狀的生命週期,導致巢狀的Fragment會在其宿主Fragment前執行相應的生命週期,比如onStop。由於宿主Fragment的FragmentManager無法識別巢狀的Fragment,在動畫開始執行的時候,巢狀的Fragment的檢視樹會直接跳過動畫階段,但是宿主Fragment的動畫卻還在執行。所以宿主Fragment和巢狀Fragment動畫的步調是完全不一致的。

解決: 參考Stack Overflow上的一個解決方案:http://stackoverflow.com/questions/14900738/nested-fragments-disappear-during-transition-animation 原理是快取宿主Fragment的當前可見狀態,但是這個會導致頁面重繪,可能衍生出其它的問題。

4、Fragment巢狀BUG之一:被繼承的setRetainInstance

Fragments可以設定成保持狀態。比如,當螢幕旋轉導致Activity銷燬和重啟時,可以不用重新建立Fragment。

問題: 巢狀的Fragment會繼承宿主Fragment的retain instance狀態。

原因: 不明

解決: 尚無解決方案。

這個看起來是個很小的點,但是卻可能產生很大的問題。雖然個人傾向於讓所有fragments重新建立來保證其狀態不出錯(尤其是有複雜View的場景下),但是如果遇到不存在或簡單View的場景是,比如網路請求或者多個元件呼叫,可能會設定一個回撥監聽器,而這個監聽器是不需要重複建立的。上面所說的這種Fragment如果被巢狀在一個需要重新建立的Fragment裡面,由於setRetainInstance 的繼承性,會導致這個Fragment也跟著被重新建立。我的解決方式是使用靜態例項和弱引用來持有這個Fragment,保證其不需要重新建立,有點坑。。。

5、Fragment巢狀BUG之一:錯亂的onActivityResult傳遞

這是最讓人頭疼的問題了,而且我們會經常遇到,比如在巢狀的Fragment裡面啟動Activity。

問題: onActivityResult回撥不會走到巢狀的Fragment裡面。

原因: Support Library(V4)會修改了requestCode,使其中包含了一個Fragment 16位的索引值。這個索引值是與FragmentManager相關聯的,Activity會根據這個索引值在自身的FragmentManager裡面搜尋Fragment來分發onActivityResult,但是隻能搜尋到宿主Fragment,而宿主Fragment卻不會向其內部巢狀的Fragment分發。這樣就導致巢狀的Fragment永遠收不到onActivityResult回撥。

解決: 宿主Fragment向其內部巢狀的Fragment傳送onActivityResult回撥。

補充: 使用系統自帶的Fragment不會出現這種問題,谷歌還是很牛逼的哈!


測試原始碼倉庫:https://github.com/BurntBrunch/android-fragment-bugs

結束,謝謝觀賞!

相關文章