Android應(yīng)用中使用Fragment組件的一些問(wèn)題及解決方案總結(jié)
Fragment的主要意義就是提供與Activity綁定的生命周期回調(diào)。
Fragment不一定要向Activity的視圖層級(jí)中添加View. 當(dāng)某個(gè)模塊需要獲得Activity的生命周期回調(diào)的時(shí)候,就可以考慮通過(guò)Fragment來(lái)實(shí)現(xiàn).
例如: DialogFragment, 調(diào)用show方法來(lái)顯示一個(gè)Dialog(這個(gè)一個(gè)子Window,并不在Activity的視圖層級(jí)中),當(dāng)旋屏?xí)r,DialogFragment利用onDestroyView回調(diào)來(lái)dismiss Dialog,然后Activity重建之后,DialogFragment利用onStart回調(diào)再顯示Dialog。
當(dāng)然,我們也可以創(chuàng)建一個(gè)完全沒(méi)有UI的Fragment,比如BackgroundWorkerFragment,在onResume的時(shí)候執(zhí)行一個(gè)Task,在onPause的時(shí)候暫停一個(gè)Task。
Fragment 生命周期
先來(lái)回顧一下基礎(chǔ)知識(shí),F(xiàn)ragment的生命周期圖如下:
說(shuō)明:總的來(lái)說(shuō),F(xiàn)ragment和Activity的生命周期類(lèi)似。需要注意的是,它相比于Activity,多了onAttach(), onDetch(), onCreateView()和onDestroyView()這幾個(gè)回調(diào)函數(shù);但是,卻少了onRestart()。
Fragment的生命周期非常復(fù)雜,分為以下幾種情況:
- 如果是通過(guò)XML中的<fragment/>標(biāo)簽實(shí)例化的,那么第一個(gè)收到的回調(diào)將是onInflate
- 如果setRetainInstance(true),那么當(dāng)Activity重建時(shí),Fragment的onDestroy以及Activity重建后Fragment的onCreate回調(diào)不會(huì)被調(diào)用.(無(wú)論是否將其添加到了返回棧)
- 如果當(dāng)前顯示的是Fragment A,然后執(zhí)行FragmentTransaction.replace(),那么Fragment A會(huì)執(zhí)行onPause()->onStop()->onDestroyView()->onDestroy()->onDetach(),如果執(zhí)行FragmentTransaction.replace().addToBackStack(),那么Fragment A會(huì)執(zhí)行onPause()->onStop()->onDestroyView()
- FragmentTransaction.hide(),將不會(huì)導(dǎo)致onPause(),而是會(huì)觸發(fā)onHiddenChanged()
- FragmentTransaction.detach(),會(huì)導(dǎo)致onPause()->onStop()->onDestroyView(),注意:onDestroy()和onDetach()不會(huì)調(diào)用
FragmentTransaction
- 對(duì)于Fragment的操作都是通過(guò)FragmentTransaction來(lái)進(jìn)行的,一個(gè)FragmentTransaction可以包含一個(gè)或者多個(gè)操作,通過(guò)commit或者commitAllowingStateLoss來(lái)提交.如果該FragmentTransaction被加入返回棧,那么出棧的時(shí)候,該Transaction中的所有操作都會(huì)被撤銷(xiāo)
- commit方法是異步的(handler post相應(yīng)的message到MainLooper關(guān)聯(lián)的Message queue),如果需要立刻執(zhí)行Transaction的操作,可以調(diào)用executePendingTransactions()
- FragmentTransaction的commit方法以及FragmentManager的popBackStack方法都是異步的,給調(diào)用者帶來(lái)了很多不便,雖然可以通過(guò)調(diào)用executePendingTransactions()方法來(lái)立即執(zhí)行,但是為什么默認(rèn)是異步的呢??(我覺(jué)得是因?yàn)?提交一個(gè)Transaction,會(huì)導(dǎo)致Fragment的生命周期方法的執(zhí)行,甚至是多個(gè)回調(diào)的執(zhí)行,如果Fragment在這些回調(diào)中又提交新的Transaction,那么可能會(huì)破壞當(dāng)前Transaction的狀態(tài),比方說(shuō)這是一個(gè)pop操作)
Can not perform this action after onSaveInstanceState
在使用Fragment的過(guò)程中,常常會(huì)遇到在Activity的onSaveInstanceState方法調(diào)用之后,操作commit或者popBackStack而導(dǎo)致的crash.
因?yàn)樵趏nSaveInstanceState方法之后的操作狀態(tài)可能會(huì)丟失,因此Android framework默認(rèn)會(huì)拋出一個(gè)異常.
對(duì)于commit方法來(lái)說(shuō),單純避免這個(gè)異常很簡(jiǎn)單,使用commitAllowingStateLoss方法即可.但是popBackStack以及popBackStackImmediate也都會(huì)檢查state(checkStateLoss),特別需要注意的是Activity的onBackPressed方法
public void onBackPressed() { if (!mFragments.popBackStackImmediate()) {//注意 supportFinishAfterTransition(); } }
如果onBackPressed在onSavedInstanceState之后調(diào)用,那么就會(huì)crash.
onBackPressed的調(diào)用時(shí)機(jī):
* targetSdkVersion <= 5,在onKeyDown中調(diào)用
* targetSdkVersion > 5,在onKeyUp中調(diào)用
onSavedInstanceState的調(diào)用時(shí)機(jī)(如果調(diào)用的話):
* 一定在onStop之前
* 可能在onPause之前,也可能在onPause與onStop之間
需要注意的是: onSavedInstanceState方法不一定會(huì)調(diào)用,只有在Activity因?yàn)槟承┰蚨籉ramework銷(xiāo)毀,并且之后還需要重新創(chuàng)建的情況,才需要調(diào)用(例如:旋屏,或者內(nèi)存不足而回收返回棧中的某些Activity)
舉例:
* Activity A在前臺(tái)時(shí),屏幕逐漸變暗直至鎖屏,那么A的onSavedInstanceState會(huì)被調(diào)用
* Activity A start Activity B,Activity A的onSavedInstanceState會(huì)被調(diào)用
* Activity A因?yàn)榉祷劓I或者finish調(diào)用而返回到上一個(gè)界面,那么A的onSavedInstanceState不會(huì)被調(diào)用
因此,當(dāng)onBackPressed在onSavedInstanceState方法之后調(diào)用,就一定會(huì)crash.解決方法主要有兩種:
重寫(xiě)Activity的onSavedInstanceState()方法,并且注釋掉super調(diào)用.
這種方法能避免crash,但是它會(huì)導(dǎo)致整個(gè)Activity的狀態(tài)丟失.以DialogFragment為例,正常情況下,顯示的DialogFragment在旋屏Activity重新創(chuàng)建之后,不需要我們處理,Dialog會(huì)自動(dòng)顯示出來(lái)(參見(jiàn)DialogFragment.onStart()),但是注釋掉Activity的onSavedInstanceState()方法之后,Fragment狀態(tài)丟失,Activity重新創(chuàng)建之后,Dialog也就不會(huì)再顯示出來(lái)了.
更好且通用的做法:在調(diào)用commit,popBackStack以及onBackPressed方法之前,判斷onSavedInstanceState()方法是否已經(jīng)執(zhí)行,并且onResume方法還沒(méi)有執(zhí)行,如果不是,那么直接操作,否則加入到pending隊(duì)列,等待onResumeFragments或者onPostResume之后再執(zhí)行.
注意:不要在onResume中操作,因?yàn)檫@時(shí)候FragmentManager中的mStateSaved依然可能是true.(如果執(zhí)行順序是onSavedInstanceState()->onPause()->onResume() 或者 onPause()->onSavedInstanceState()->onResume())
例如:
public void onDataReceived() { if(isStateSaved()) {//isStateSaved()由BaseActivity提供 addPendingFragmentOperation(new Runnable() { @Override public void run() { getSupportFragmentManager().popBackStackImmediate(); } }); } else { getSupportFragmentManager().popBackStackImmediate(); } } @Override protected void onPostResume() { super.onPostResume(); if(pendingFragmentOperation != null && !pendingFragmentOperation.isEmpty()) { for(Runnable operation : pendingFragmentOperation) { operation.run(); } pendingFragmentOperation.clear(); } }
startActivityForResult
requestCode的可用區(qū)間:
1.Activity: [Integer.MIN_VALUE, Integer.MAX_VALUE]
(1)當(dāng)requestCode取值在[Integer.MIN_VALUE, -1]區(qū)間中,效果和startActivity()一樣,不會(huì)收到onActivityResult()回調(diào)
(2)內(nèi)置的Fragment可用requestCode的區(qū)間和Activity相同
2.support庫(kù): Fragment,以及FragmentActivity:[-1, 65535]
(1)requestCode == -1,效果和startActivity()一樣,不會(huì)收到onActivityResult()回調(diào)
(2)requestCode 在 [Integer.MIN_VALUE, -2]或者[65536, Integer.MAX_VALUE]之間,會(huì)拋出異常(requestCode只能使用低16比特)
建議: requestCode的取值統(tǒng)一限制在[-1, 65535]之間
嵌套Fragment
首先要說(shuō)的是盡量不要使用嵌套Fragment.
當(dāng)在嵌套Fragment中使用startActivityForResult()時(shí),會(huì)遇到的問(wèn)題:
所有的Fragment都收不到onActivityResult()
某個(gè)level 1 的Fragment收到了onActivityResult()
總之那個(gè)發(fā)起startActivityForResult()的嵌套Fragment是一定不會(huì)收到onActivityResult()回調(diào)的.
原因如下:(可參考上面說(shuō)的requestCode)
FragmentActivity.startActivityFromFragment()會(huì)改動(dòng)requestCode,用高16比特存儲(chǔ)Fragment在FragmentManager中的index,而低16比特作為Fragment可用的requestCode.在FragmentActivity.onActivityResult()中,根據(jù)高16比特,從FragmentManager中找到對(duì)應(yīng)的Fragment,然后將低16比特的值作為requestCode,調(diào)用Fragment.onActivityResult().
那么requestCode中只能存儲(chǔ)一個(gè)index,即root FragmentManager中的Fragment index.因此就會(huì)出現(xiàn)上面所列出的情形:
- 當(dāng)嵌套Fragment在childFragmentManager中的index,大于rootFragmentManager中的所有index時(shí), rootFragmentManager將找不到與此index對(duì)應(yīng)的Fragment,所以沒(méi)有Fragment能收到onActivityResult()
- 當(dāng)嵌套Fragment在childFragmentManager中的index,小于等于rootFragmentManager中的所有index時(shí),那么隸屬于rootFragmentManager的一個(gè)Fragment將會(huì)收到onActivityResult()
- 總之即使能有Fragment能收到onActivityResult(),那也是頂層的某個(gè)Fragment,而不是發(fā)起請(qǐng)求的嵌套Fragment
解決方案:
- 不使用嵌套Fragment :)
- 依然利用requestCode,將其低16位拆分,其中的高8位用來(lái)存儲(chǔ)childFragmentManager中的index,低8位留給ChildFragment使用.(如果嵌套層級(jí)不深,那么此方案還是不錯(cuò)的,如果層級(jí)較深,那么留給Fragment的requestCode的可用值區(qū)間將非常局限)
- Android 4.2(Api 17)以后,可以使用內(nèi)置的Fragment,以及ChildFragmentManager,內(nèi)置Fragment不再需要借助requestCode的高16比特來(lái)記錄它的index.而是由Framework收到Fragment.startActivityForResult()時(shí),記錄該Fragment的標(biāo)識(shí)(android:fragment:${parentIndex}:${myIndex}),派發(fā)result時(shí),就根據(jù)這個(gè)標(biāo)識(shí)找到那個(gè)Fragment.因此就不會(huì)出現(xiàn)ChildFragment收不到onActivityResult()回調(diào)的問(wèn)題了.可以參考Activity.dispatchActivityResult()
Tips
開(kāi)發(fā)的時(shí)候,可以打開(kāi)Fragment相關(guān)的調(diào)試信息
FragmentManager.enableDebugLogging(BuildConfig.DEBUG); Activity的onResume被調(diào)用時(shí),Fragment的onResume還未被調(diào)用. protected void onPostResume() { super.onPostResume(); mHandler.removeMessages(MSG_RESUME_PENDING); onResumeFragments(); mFragments.execPendingActions(); } protected void onResumeFragments() { mFragments.dispatchResume(); }
如果需要在Fragment的onResume都執(zhí)行完后再執(zhí)行某個(gè)操作,可以重寫(xiě)onPostResume()方法,一定要調(diào)用 super.onPostResume()
1.IllegalStateException(Fragment not attached to Activity)的問(wèn)題
這個(gè)異常通常的發(fā)生情況是:在Fragment中啟動(dòng)一個(gè)異步任務(wù),然后在回調(diào)中執(zhí)行和resource相關(guān)的操作(getString(...)),或者startActivity(...)之類(lèi)的操作.但是這個(gè)時(shí)候Fragment可能已經(jīng)被detach了,所以它的mHost==null,因此在執(zhí)行這些操作之前,需要先判斷一下isAdded().
注意: 這里不要使用isDetached()來(lái)判斷,因?yàn)镕ragment被detach之后,它的isDetached()方法依然可能返回false
2.如果Fragment A是因?yàn)楸籸eplace而detach的,那么它的isDetached()將返回false
3.如果Fragment A對(duì)應(yīng)的FragmentTransaction被加入到返回棧中,因?yàn)槌鰲6鴇etach,那么它的isDetached()將返回true
final public Resources getResources() { if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } return mHost.getContext().getResources(); } public void startActivity(Intent intent, @Nullable Bundle options) { if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1, options); }
- Android應(yīng)用 坐標(biāo)系詳細(xì)介紹
- 非常實(shí)用的小功能 Android應(yīng)用版本的更新實(shí)例
- 第1個(gè)Android應(yīng)用程序 Android制作簡(jiǎn)單單頁(yè)導(dǎo)航
- Android應(yīng)用開(kāi)發(fā)中使用GridView網(wǎng)格布局的代碼示例
- Android應(yīng)用自動(dòng)更新功能實(shí)現(xiàn)的方法
- Android應(yīng)用開(kāi)發(fā)中WebView的常用方法筆記整理
- 詳解Android應(yīng)用中DialogFragment的基本用法
- Android應(yīng)用中設(shè)置alpha值來(lái)制作透明與漸變效果的實(shí)例
- Android應(yīng)用中實(shí)現(xiàn)手勢(shì)控制圖片縮放的完全攻略
- Docker 實(shí)現(xiàn)瀏覽器里開(kāi)發(fā)Android應(yīng)用的功能
相關(guān)文章
android 捕捉異常并上傳至服務(wù)器的簡(jiǎn)單實(shí)現(xiàn)
本篇文章主要介紹了android 捕捉異常并上傳至服務(wù)器的簡(jiǎn)單實(shí)現(xiàn),具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04flutter項(xiàng)目引入iconfont阿里巴巴圖標(biāo)
這篇文章主要為大家介紹了flutter項(xiàng)目引入iconfont阿里巴巴圖標(biāo)的過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Android?Studio實(shí)現(xiàn)簡(jiǎn)單繪圖板
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)簡(jiǎn)單繪圖板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Android自定義可點(diǎn)擊的ImageSpan并在TextView中內(nèi)置View
這篇文章主要為大家詳細(xì)介紹了Android自定義可點(diǎn)擊的ImageSpan并在TextView中內(nèi)置"View",具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android 數(shù)據(jù)存儲(chǔ)方式有哪幾種
android為數(shù)據(jù)存儲(chǔ)提供了五種方式,有SharedPreferences、文件存儲(chǔ)、SQLite數(shù)據(jù)庫(kù)、ContentProvider、網(wǎng)絡(luò)存儲(chǔ),對(duì)android數(shù)據(jù)存儲(chǔ)方式感興趣的朋友可以通過(guò)本文學(xué)習(xí)一下2015-11-11Android Handler內(nèi)存泄漏詳解及其解決方案
在android開(kāi)發(fā)過(guò)程中,我們可能會(huì)遇到過(guò)令人奔潰的OOM異常,這篇文章主要介紹了Android Handler內(nèi)存泄漏詳解及其解決方案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08