編輯:Android開發實例
Fragment的主要意義就是提供與Activity綁定的生命周期回調。
Fragment不一定要向Activity的視圖層級中添加View. 當某個模塊需要獲得Activity的生命周期回調的時候,就可以考慮通過Fragment來實現.
例如: DialogFragment, 調用show方法來顯示一個Dialog(這個一個子Window,並不在Activity的視圖層級中),當旋屏時,DialogFragment利用onDestroyView回調來dismiss Dialog,然後Activity重建之後,DialogFragment利用onStart回調再顯示Dialog。
當然,我們也可以創建一個完全沒有UI的Fragment,比如BackgroundWorkerFragment,在onResume的時候執行一個Task,在onPause的時候暫停一個Task。
Fragment 生命周期
先來回顧一下基礎知識,Fragment的生命周期圖如下:
說明:總的來說,Fragment和Activity的生命周期類似。需要注意的是,它相比於Activity,多了onAttach(), onDetch(), onCreateView()和onDestroyView()這幾個回調函數;但是,卻少了onRestart()。
Fragment的生命周期非常復雜,分為以下幾種情況:
FragmentTransaction
Can not perform this action after onSaveInstanceState
在使用Fragment的過程中,常常會遇到在Activity的onSaveInstanceState方法調用之後,操作commit或者popBackStack而導致的crash.
因為在onSaveInstanceState方法之後的操作狀態可能會丟失,因此Android framework默認會拋出一個異常.
對於commit方法來說,單純避免這個異常很簡單,使用commitAllowingStateLoss方法即可.但是popBackStack以及popBackStackImmediate也都會檢查state(checkStateLoss),特別需要注意的是Activity的onBackPressed方法
public void onBackPressed() { if (!mFragments.popBackStackImmediate()) {//注意 supportFinishAfterTransition(); } }
如果onBackPressed在onSavedInstanceState之後調用,那麼就會crash.
onBackPressed的調用時機:
* targetSdkVersion <= 5,在onKeyDown中調用
* targetSdkVersion > 5,在onKeyUp中調用
onSavedInstanceState的調用時機(如果調用的話):
* 一定在onStop之前
* 可能在onPause之前,也可能在onPause與onStop之間
需要注意的是: onSavedInstanceState方法不一定會調用,只有在Activity因為某些原因而被Framework銷毀,並且之後還需要重新創建的情況,才需要調用(例如:旋屏,或者內存不足而回收返回棧中的某些Activity)
舉例:
* Activity A在前台時,屏幕逐漸變暗直至鎖屏,那麼A的onSavedInstanceState會被調用
* Activity A start Activity B,Activity A的onSavedInstanceState會被調用
* Activity A因為返回鍵或者finish調用而返回到上一個界面,那麼A的onSavedInstanceState不會被調用
因此,當onBackPressed在onSavedInstanceState方法之後調用,就一定會crash.解決方法主要有兩種:
重寫Activity的onSavedInstanceState()方法,並且注釋掉super調用.
這種方法能避免crash,但是它會導致整個Activity的狀態丟失.以DialogFragment為例,正常情況下,顯示的DialogFragment在旋屏Activity重新創建之後,不需要我們處理,Dialog會自動顯示出來(參見DialogFragment.onStart()),但是注釋掉Activity的onSavedInstanceState()方法之後,Fragment狀態丟失,Activity重新創建之後,Dialog也就不會再顯示出來了.
更好且通用的做法:在調用commit,popBackStack以及onBackPressed方法之前,判斷onSavedInstanceState()方法是否已經執行,並且onResume方法還沒有執行,如果不是,那麼直接操作,否則加入到pending隊列,等待onResumeFragments或者onPostResume之後再執行.
注意:不要在onResume中操作,因為這時候FragmentManager中的mStateSaved依然可能是true.(如果執行順序是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的可用區間:
1.Activity: [Integer.MIN_VALUE, Integer.MAX_VALUE]
(1)當requestCode取值在[Integer.MIN_VALUE, -1]區間中,效果和startActivity()一樣,不會收到onActivityResult()回調
(2)內置的Fragment可用requestCode的區間和Activity相同
2.support庫: Fragment,以及FragmentActivity:[-1, 65535]
(1)requestCode == -1,效果和startActivity()一樣,不會收到onActivityResult()回調
(2)requestCode 在 [Integer.MIN_VALUE, -2]或者[65536, Integer.MAX_VALUE]之間,會拋出異常(requestCode只能使用低16比特)
建議: requestCode的取值統一限制在[-1, 65535]之間
嵌套Fragment
首先要說的是盡量不要使用嵌套Fragment.
當在嵌套Fragment中使用startActivityForResult()時,會遇到的問題:
所有的Fragment都收不到onActivityResult()
某個level 1 的Fragment收到了onActivityResult()
總之那個發起startActivityForResult()的嵌套Fragment是一定不會收到onActivityResult()回調的.
原因如下:(可參考上面說的requestCode)
FragmentActivity.startActivityFromFragment()會改動requestCode,用高16比特存儲Fragment在FragmentManager中的index,而低16比特作為Fragment可用的requestCode.在FragmentActivity.onActivityResult()中,根據高16比特,從FragmentManager中找到對應的Fragment,然後將低16比特的值作為requestCode,調用Fragment.onActivityResult().
那麼requestCode中只能存儲一個index,即root FragmentManager中的Fragment index.因此就會出現上面所列出的情形:
解決方案:
Tips
開發的時候,可以打開Fragment相關的調試信息
FragmentManager.enableDebugLogging(BuildConfig.DEBUG); Activity的onResume被調用時,Fragment的onResume還未被調用. protected void onPostResume() { super.onPostResume(); mHandler.removeMessages(MSG_RESUME_PENDING); onResumeFragments(); mFragments.execPendingActions(); } protected void onResumeFragments() { mFragments.dispatchResume(); }
如果需要在Fragment的onResume都執行完後再執行某個操作,可以重寫onPostResume()方法,一定要調用 super.onPostResume()
1.IllegalStateException(Fragment not attached to Activity)的問題
這個異常通常的發生情況是:在Fragment中啟動一個異步任務,然後在回調中執行和resource相關的操作(getString(...)),或者startActivity(...)之類的操作.但是這個時候Fragment可能已經被detach了,所以它的mHost==null,因此在執行這些操作之前,需要先判斷一下isAdded().
注意: 這裡不要使用isDetached()來判斷,因為Fragment被detach之後,它的isDetached()方法依然可能返回false
2.如果Fragment A是因為被replace而detach的,那麼它的isDetached()將返回false
3.如果Fragment A對應的FragmentTransaction被加入到返回棧中,因為出棧而detach,那麼它的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); }
登錄應用程序的屏幕,詢問憑據登錄到一些特定的應用。可能需要登錄到Facebook,微博等本章介紹了,如何創建一個登錄界面,以及如何管理安全問題和錯誤嘗試。首先,必須定義兩
在上篇文章給大家介紹深入淺析Android Fragment(上篇),包括一些基本的用法和各種API,如果還想深入學習請繼續關注本篇文章。 本篇將介紹上篇提到的:
在android項目中訪問網絡圖片是非常普遍性的事情,如果我們每次請求都要訪問網絡來獲取圖片,會非常耗費流量,而且圖片占用內存空間也比較大,圖片過多且不釋放的話很
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個