Android Fragment 生命週期及其正確使用(建議使用自定義View替換Fragment)

使用Fragment 官方例子中顯示:html

例如:一個學生Fragment,須要傳入studentId,進行http請求顯示,那麼setArguments後防止殺掉Fragment後,參數爲0,顯示不了數據。android

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:( Fragment就至關於一個有生命週期的View,它的生命週期被所在的Activity的生命週期管理 )app

生命週期回調說明:異步

onAttach(Activity)
當Fragment與Activity發生關聯時調用。
onCreateView(LayoutInflater, ViewGroup,Bundle)
建立該Fragment的視圖
onActivityCreated(Bundle)
當Activity的onCreate方法返回時調用
onDestoryView()
與onCreateView想對應,當該Fragment的視圖被移除時調用
onDetach()
與onAttach相對應,當Fragment與Activity關聯被取消時調用ide

與Activity 依賴關係:動畫

與ViewPager 的關係:ui

(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() 方法空指針問題:

在Fragment基類裏設置一個Activity mActivity的全局變量,在 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方法,排序
注意:若是你想給下一個Fragment設置進棧動畫和出棧動畫,.setCustomAnimations(enter, exit)只能設置進棧動畫,第二個參數並非設置出棧動畫;
請使用.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正常保存下來。

二、進入新的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()

 

參考:

Fragment全解析系列(一):那些年踩過的坑

相關文章
相關標籤/搜索