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


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

在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正常保存下來。

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()

 

參考:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM