使用Fragment 官方例子中顯示:
例如:一個學生Fragment,需要傳入studentId,進行http請求顯示,那么setArguments后防止殺掉Fragment后,參數為0,顯示不了數據。
1 public static StudentFragment newInstance(int studentId){ 2 StudentFragment fragment = new StudentFragment(); 3 Bundle bundle = new Bundle(); 4 bundle.putInt("student_id", studentId); 5 fragment.setArguments(bundle); 6 return fragment; 7 }
setArguments:
1 private int mStudentId = 0; 2 3 @Override 4 public void setArguments(Bundle args) { 5 super.setArguments(args); 6 7 mStudentId = args.getInt("student_id", 0); 8 }
那么app在殺死后,回到這個Fragment 時,會在onCreateView時,進行獲取參數,獲取正確的頁面數據,不然有可能會因為null,而崩毀。
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mStudentId = getArguments().getInt("student_id", 0); return super.onCreateView(inflater, container, savedInstanceState); }
Fragment:( Fragment就相當於一個有生命周期的View,它的生命周期被所在的Activity的生命周期管理 )
生命周期回調說明:
onAttach(Activity)
當Fragment與Activity發生關聯時調用。
onCreateView(LayoutInflater, ViewGroup,Bundle)
創建該Fragment的視圖
onActivityCreated(Bundle)
當Activity的onCreate方法返回時調用
onDestoryView()
與onCreateView想對應,當該Fragment的視圖被移除時調用
onDetach()
與onAttach相對應,當Fragment與Activity關聯被取消時調用
與Activity 依賴關系:
與ViewPager 的關系:
(1)setOffscreenPageLimit
1 // 預加載頁面的數量 2 viewPager.setOffscreenPageLimit(2);
知道ViewPager會有預加載的特性,我以為Fragment_1會從 1 onAttach() 開始直到 6 onResume()后,Fragment_2才會開始從 1onAttach()開始,到了3 onCreateView()會停止。然而,並不是預想的這樣。
首先,Fragment_1 經歷 1_onAttach() 和 2_onCreate() 后, Fragment_2也開始走了 1_onAttach()和 2_onCreate()方法。
接着,Fragment_1依次經歷 3_onCreateView(), 4_onCreateActivity(),5 _onStart ,6_onResume()。此時,Fragment_1獲得焦點,已經展示在手機屏幕。Fragment_2也接着從 3_onCreate()開始直到也執行到 6_onResume()方法。疑問就在這,我個人感覺既然Fragment_2沒有在屏幕顯示,就不會執行到 6_onResume()方法,然而Log信息卻顯示執行到了 6_onResume()。這里記錄一下。
多了Fragment_3的Log信息,而且走的方法和Frgment_1和2是一樣的。后面的情況的Log信息便不再貼出來了,本質是一樣的,只是多預加載了一個Fragment。但此時ViewPager依然只是保留3個Fragment的信息。當滑到Fragment_4的時候,Fragment_1走了7_onPause(),8_onStop(),9_onDestroyView()。Fragment_2,3,4則處於6_onResume()。
這個方法對Fragment生命周期方法的調用順序上並沒有什么影響,只是預加載的Fragment的數量又設置的limit參數決定。
(2)setUserVisibleHint
setUserVisibleHint()這個方法會在預加載Fragment時,會在Fragment的1_onAttach()前調用。此時,在setUserVisibleHint()里面調用也不用害怕空指針的問題,因為有3個前條件。當通過滑動ViewPger時,根據6.1知道,每次滑動都會調用setUserVisibleHint()這個方法,進行預加載后,當滑到Fragment_2,3,4時,就會調用里面的load()方法。
在Fragment的4_onCreateActivity()中調用load。我個人習慣把網絡請求放在這個生命周期。在Fragment_1和點擊Tab時,引起問題的原因就是setUserVisibleHint()先於1_onAttach()調用,不能滿足前提條件中的isCreate,所以load方法不會被調用。而4_onCreateActivity()中會再次調用load()方法,此時還滿足3個前提條件。這樣,遺留的問題也解決了。ViewPager中Fragment延遲加載這個需求也可以實現了。如果此時Fragment中有一個輪播圖的話,也可以通過getUserVisibleHint()這個方法來選擇關閉輪播圖線程的時機。
Fragment Api:
Fragment常用的三個類:
android.app.Fragment 主要用於定義Fragment
android.app.FragmentManager 主要用於在Activity中操作Fragment
android.app.FragmentTransaction 保證一些列Fragment操作的原子性
主要的操作都是FragmentTransaction的方法:
1 // v4包中,getSupportFragmentManager 2 FragmentManager fm = getFragmentManager(); 3 // 開啟一個事務 (主要的操作都是FragmentTransaction的方法) 4 FragmentTransaction transaction = fm.benginTransatcion(); 5 6 // 往Activity中添加一個Fragment 7 transaction.add(Fragment fragment); 8 // 從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧(回退棧后面會詳細說),這個Fragment實例將會被銷毀。 9 transaction.remove(Fragment fragment); 10 // 使用另一個Fragment替換當前的,實際上就是remove()然后add()的合體 11 transaction.replace(R.id.XXX, Fragment fragment); 12 // 隱藏當前的Fragment,僅僅是設為不可見,並不會銷毀 13 transaction.hide(Fragment fragment); 14 // 顯示之前隱藏的Fragment 15 transaction.show(Fragment fragment); 16 //提交一個事務 17 transatcion.commit();
// 會將view從UI中移除,和remove()不同,此時fragment的狀態依然由FragmentManager維護。
detach()
// 重建view視圖,附加到UI上並顯示。
attach();
判斷什么時候該使用什么方法:
(1)希望保留用戶操作的面板,可以使用hide和show。
(2)不希望保留用戶操作,可以使用remove(),然后add();或者直接使用replace(),效果相同。
(3)remove會銷毀整個Fragment實例,而detach則只是銷毀其視圖結構,實例並不會被銷毀。
(4)當前Activity一直存在,那么在不希望保留用戶操作的時候,可以優先使用detach。
參考地址:Android Fragment 真正的完全解析(上) Android Fragment 真正的完全解析(下)
對於用法:
(1)getActivity() 方法空指針問題:
onAttach(Activity activity)
里賦值,使用mActivity代替
getActivity()
,保證Fragment即使在
onDetach
后,仍持有Activity的引用(有引起內存泄露的風險,但是異步任務沒停止的情況下,本身就可能已內存泄漏,相比Crash,這種做法“安全”些),即:
protected Activity mActivity; /** * API低於 23 的版本中不會去調用后者,只會去調用onAttach(Activity) */ @Override public void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = activity; } /** * 如果用了support 23的庫,上面的方法會提示過時,且不會被調用,可以用下面的方法代替 */ @Override public void onAttach(Context context) { super.onAttach(context); this.mActivity = (Activity) context; }
如不需要使用getActivity():
1 /* 2 * onAttach(Context) is not called on pre API 23 versions of Android and onAttach(Activity) is deprecated 3 * Use onAttachToContext instead 4 */ 5 @TargetApi(23) 6 @Override 7 public void onAttach(Context context) { 8 super.onAttach(context); 9 onAttachToContext(context); 10 } 11 12 /* 13 * Deprecated on API 23 14 * Use onAttachToContext instead 15 */ 16 @SuppressWarnings("deprecation") 17 @Override 18 public void onAttach(Activity activity) { 19 super.onAttach(activity); 20 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 21 onAttachToContext(activity); 22 } 23 } 24 25 /* 26 * Called when the fragment attaches to the context 27 */ 28 protected void onAttachToContext(Context context) { 29 //do something 30 }
(2)未必靠譜的出棧方法remove()
如果你想讓某一個Fragment出棧,使用remove()
在加入回退棧時並不靠譜。
如果你在add的同時將Fragment加入回退棧:addToBackStack(name)的情況下,它並不能真正將Fragment從棧內移除,如果你在2秒后(確保Fragment事務已經完成)打印getSupportFragmentManager().getFragments()
,會發現該Fragment依然存在,並且依然可以返回到被remove的Fragment,而且是空白頁面。
如果你沒有將Fragment加入回退棧,remove方法可以正常出棧。
如果你加入了回退棧,popBackStack()
系列方法才能真正出棧,這也就引入下一個深坑,popBackStack(String tag,int flags)
等系列方法的BUG。
(3)Fragment轉場動畫
如果你的Fragment沒有轉場動畫,或者使用setCustomAnimations(enter, exit)
的話。
1 getFragmentManager().beginTransaction().setCustomAnimations(enter, exit); 3 // 如果通過tag/id同時出棧多個Fragment的情況時, 4 // 請謹慎使用.setCustomAnimations(enter, exit, popEnter, popExit) 5 // 在support-25.4.0之前出棧多Fragment時,伴隨出棧動畫,會在某些情況下發生異常 6 // 你需要搭配Fragment的onCreateAnimation()臨時取消出棧動畫,或者延遲一個動畫時間再執行一次上面提到的Hack方法,排序
請使用.setCustomAnimations(enter, exit, popEnter, popExit),這個方法的第1個參數對應進棧動畫,第4個參數對應出棧動畫,所以是.setCustomAnimations(進棧動畫, exit, popEnter, 出棧動畫)
如果想讓出棧動畫運作正常的話,需要使用Fragment的onCreateAnimation
中控制動畫:
1 @Override 2 public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { 3 // 此處設置動畫 4 }
pop多個Fragment時轉場動畫 帶來的問題:
在使用 pop(tag/id)
出棧多個Fragment的這種情況下,將轉場動畫臨時取消或者延遲一個動畫的時間再去執行其他事務;
原因在於這種情景下,可能會導致棧內順序錯亂(上文有提到),同時如果發生“內存重啟”后,因為Fragment轉場動畫沒結束時再執行其他方法,會導致Fragment狀態不會被FragmentManager正常保存下來。
2、進入新的Fragment並立刻關閉當前Fragment 時的一些問題
(1)如果你想從當前Fragment進入一個新的Fragment,並且同時要關閉當前Fragment。由於數據結構是棧,所以正確做法是先pop
,再add
,但是轉場動畫會有覆蓋的不正常現象,你需要特殊處理,不然會閃屏!
如果你遇到Fragment的mNextAnim空指針的異常(通常是在你的Fragment被重啟的情況下),那么你首先需要檢查是否操作的Fragment是否為null;其次在你的Fragment轉場動畫還沒結束時,你是否就執行了其他事務等方法;解決思路就是延遲一個動畫時間再執行事務,或者臨時將該Fragment設為無動畫
對於一些操作,Fragment發生的生命周期變化:
切換到該Fragment:onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume()
屏幕滅掉或者回到桌面(Home): onPause() -> onSaveInstanceState() -> onStop()
屏幕解鎖或者重新回到應用: onStart() -> onResume()
切換到其他Fragment: onPause() -> onStop() -> onDestroyView()
切換回本身的Fragment: onCreateView() -> onActivityCreated() -> onStart() -> onResume()
退出應用:onPause() -> onStop() -> onDestroyView() -> onDestroy() -> onDetach()
參考: