日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android Fragment嵌套导致的bug

發布時間:2025/4/16 Android 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Fragment嵌套导致的bug 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文鏈接

Android 多個Fragment嵌套導致的三大BUG

Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常見錯誤

  • http://www.cnblogs.com/mengdd/p/5548359.html
  • http://www.cnblogs.com/mengdd/p/5552721.html
  • http://www.cnblogs.com/mengdd/p/5582244.html
  • http://www.cnblogs.com/mengdd/p/5590634.html

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

嵌套Fragment的使用及常見錯誤

嵌套Fragments (Nested Fragments), 是在Fragment內部又添加Fragment.
使用時, 主要要依靠宿主Fragment的 getChildFragmentManager() 來獲取FragmentManger.

雖然看起來和在activity中添加fragment差不多, 但因為fragment生命周期及管理恢復模式不同, 其中有一些需要特別注意的地方.

本文內容還包括了從Fragment遷移到v4.Fragment代碼中需要改動的一些地方.

嵌套Fragments

嵌套Fragments Nested Fragments 是Android 4.2 API 17 引入的.
目的: 進一步增強動態復用.
如果要在Android 4.2之前使用, 可以用support library v4的版本, 后面會有詳細的遷移過程介紹.

嵌套Fragment的動態添加

在宿主fragment里調用getChildFragmentManager()
即可用它來向這個fragment內部添加fragments.

Fragment videoFragment = new VideoPlayerFragment(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); transaction.add(R.id.video_fragment, videoFragment).commit();

同樣, 對于內部的fragment來說, getParentFragment() 方法可以獲取到fragment的宿主fragment.

getChildFragmentManager() 和 getFragmentManager()

getChildFragmentManager()是fragment中的方法, 返回的是管理當前fragment內部子fragments的manager.
getFragmentManager()在activity和fragment中都有.
在activity中, 如果用的是v4 support庫, 方法應該用getSupportFragmentManager(), 返回的是管理activity中fragments的manager.
在fragment中, 還叫getFragmentManager(), 返回的是把自己加進來的那個manager.

也即, 如果fragment在activity中, fragment.getFragmentManager()得到的是activity中管理fragments的那個manager.
如果fragment是嵌套在另一個fragment中, fragment.getFragmentManager()得到的是它的parent的getChildFragmentManager().

總結就是: getFragmentManager()是本級別管理者, getChildFragmentManager()是下一級別管理者.
這實際上是一個樹形管理結構.

使用Support library

為什么要使用support library? 有兩種原因:

要在API level11之前使用fragment.
要在API Level 17之前使用getChildFragmentManager(), 即使用嵌套Fragment.
遷移到support library需要改動哪些地方?

把Fragment遷移到v4版本, 需要改動如下地方:

import android.app.Fragment; -> import android.support.v4.app.Fragment;
Activity -> FragmentActivity / AppCompatActivity
activity.getFragmentManager() -> getSupportFragmentManager()

Loader, LoaderManager, LoaderCursor也需要改成v4包的.
activity.getLoaderManager() -> getSupportLoaderManager()
Fragment中onTrimMemory()方法不見了
以前是這個方法

@Overridepublic void onTrimMemory(int level) {super.onTrimMemory(level);imageLoader.trimMemory(level);}

v4版本需要改成這個

@Overridepublic void onLowMemory() {super.onLowMemory();imageLoader.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);}

嵌套Fragment使用常見錯誤

錯誤情形1: 把嵌套Fragment放在布局里

把嵌套Fragment放在布局里 -> InflateException in Binary XML

看起來嵌套fragment的使用除了要用getChildFragmentManager()以外, 其他跟之前似乎沒什么區別.
如果嵌套的fragment不需要太多控制, 固定地占據了一塊地方, 你可能想當然地為了省事就把它放進了xml布局文件里, 寫個標簽.
運行一下初看起來似乎沒什么錯, run一下也能顯示出來, 但是千萬不要這樣做, 多玩兩下更復雜的你就知道了.

上面官網介紹時就有這么一句:

Note: You cannot inflate a layout into a fragment when that layout includes a .
Nested fragments are only supported when added to a fragment dynamically.
人家這么說肯定是有原因的哇, 下面我來告訴你我知道的問題:
如果Fragment被嵌套寫在了布局里, inflate到這個標簽的時候就相當于將它加進了FragmentManager里.
如果嵌套的parent fragment因為需要重建View而重新走了onCreateView()方法, 再次inflate, 此時就會拋出異常: InflateException in Binary XML

之前為什么可以呢? 非嵌套的情況, fragment直接加在activity里, 如果需要重新inflate, 必定是在onCreate()里, activity是重新建的, 所以沒有問題, 因為不存在fragmentManager中已經持有同一個fragment的問題.

舉一個例子:
在嵌套的情況下, 如果FragmentE布局里有FragmentA, 這時候我們需要疊加一個FragmentD.
用了replace(), 并且addToBackStack().
當D顯示的時候, E實際上View是被銷毀的, 然后back回來, 重建View, 即FragementE需要重新從onCreateView
()開始走生命周期, 走到inflate的時候又看到了fragmentA的標簽.
但是這時候A實際上還在FragmentManager里面, 所以就會拋出如下的異常:
android.view.InflateException: Binary XML file line # XX: Binary XML file line #XX: Error inflating class fragment
崩潰的位置就在parent fragment(FragmentE) inflate的時候.
打印具體的異常棧信息可以看到:

at com.example.ddmeng.helloactivityandfragment.fragment.FragmentE.onCreateView(FragmentE.java:35) at android.app.Fragment.performCreateView(Fragment.java:2220) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:973) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148) at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587) at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578) at android.support.v4.app.BaseFragmentActivityEclair.onBackPressedNotHandled(BaseFragmentActivityEclair.java:27) at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:189)Caused by: java.lang.IllegalArgumentException: Binary XML file line #16: Duplicate id 0x7f0c0059, tag null, or parent id 0xffffffff with another fragment for com.example.ddmeng.helloactivityandfragment.fragment.FragmentA at android.app.FragmentManagerImpl.onCreateView(FragmentManager.java:2205)

實驗例子代碼

Solution 1: 動態添加child fragment

解決上面的問題有各種方法, 最常規的做法是, 使用動態添加:

Fragment fragmentA = getChildFragmentManager().findFragmentByTag(NESTED_FRAGMENT_TAG); if (fragmentA == null) {Log.i(LOG_TAG, "add new FragmentA !!");fragmentA = new FragmentA();FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();fragmentTransaction.add(R.id.fragment_container, fragmentA, NESTED_FRAGMENT_TAG).commit(); } else {Log.i(LOG_TAG, "found existing FragmentA, no need to add it again !!"); }

Solution 2: 在異常之前remove child fragment

如果你的子fragment非要加在布局里不可, 而你的程序確實會有重建父fragment view的情形.
為了避免上面的異常, 你也可以這樣做(tricky and not recommended):

public void removeChildFragment(Fragment parentFragment) {FragmentManager fragmentManager = parentFragment.getChildFragmentManager();Fragment child = fragmentManager.findFragmentById(R.id.child);if (child != null) {fragmentManager.beginTransaction().remove(child).commitAllowingStateLoss();} }

在parentFragment的onCreateView()方法中inflate之前和onSaveInstanceState()方法中做save工作之前調用它.
這兩個地方是發生異常的地方, 只要在其之前remove就好.

錯誤情形2: 把fragment放在一個動態布局里

把fragment放在一個動態布局里 -> java.lang.IllegalArgumentException: No view found for id

發現這個錯誤是因為項目中的一個子Fragment是添加在RecyclerView里面的一塊的.
RecyclerView要等到Loader的數據取到了之后再populate每一塊的布局.
還是上面的流程, 啟動父fragment, load數據, 添加子fragment, 這都沒有問題.
但是一旦如果是上面的replace()加addToBackStack() , 并且再次返回, 就會出現異常.

因為當重建View的時候, fragmentManager其中是持有child fragment的, 但是找不到它的container, 于是就會拋出異常.
我也同樣做了一個小實驗, 在我的demo程序里:
HelloActivityAndFragment
Nested Fragment in Dynamic Container:
在Fragment F中, 先添加一個FrameLayout, 再把child fragment A加進去.
然后在Activity中, 用D replace F, 按back鍵返回, 就會有crash:

java.lang.IllegalArgumentException: No view found for id 0x7f0c0062 (com.example.ddmeng.helloactivityandfragment:id/frame_container) for fragment FragmentA{b37763 #0 id=0x7f0c0062 FragmentA}at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:965)at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1130)at android.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1953)at android.app.Fragment.performActivityCreated(Fragment.java:2234)at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:992)at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:1670)at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587)at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578)at android.app.Activity.onBackPressed(Activity.java:2503)

這是因為返回的時候FragmentManager找不到對應的container了.
所以應該避免這種做法, 盡量把fragment加進parent的根布局里, 而不是某個動態添加的布局.

其他
關于嵌套fragments的情況, 可能和ViewPager結合使用的情形比較多.
這個感覺說來話長了, 以為有很多系統幫忙做的事情, 改天有空再說吧.

這里有個大哥寫了個工具類Fragmentation.
他也有幾篇博文分析遇到的坑和原因(見上面repo的README給出的鏈接), 里面有一些back stack的問題, 還有動畫什么的, 大家有興趣可以看看.

總結

以上是生活随笔為你收集整理的Android Fragment嵌套导致的bug的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。