Fragment的具體實現及應用

目錄

01.什麼是內存重啓

02.Fragment常用方法

03.onHiddenChanged回調時機

04.傳遞和接收參數

05.FragmentManager棧視圖

06.Fragment之懶加載使用

07.首頁Fragment使用

08.思考Fragment能否不依賴Activity嗎


本文是對Fragment的基礎介紹的具體實現和應用的補充,參考了GitHub上的一些Demo和博客分析,用於Android學習總結,若有不足之處,望指正~

01.什麼是內存重啓

  • 安卓app有一種特殊情況,就是 app運行在後臺的時候,系統資源緊張的時候導致把app的資源全部回收(殺死app的進程),這時把app再從後臺返回到前臺時,app會重啓。這種情況下文簡稱爲:“內存重啓”。(屏幕旋轉等配置變化也會造成當前Activity重啓,本質與“內存重啓”類似)
  • 在系統要把app回收之前,系統會把Activity的狀態保存下來,Activity的FragmentManager負責把Activity中的Fragment保存起來。在“內存重啓”後,Activity的恢復是從棧頂逐步恢復,Fragment會在宿主Activity的onCreate方法調用後緊接着恢復(從onAttach生命週期開始)。

02.Fragment常用方法

  • Fragment 的動態添加、刪除等操作都需要藉助於 FragmentTransaction 類來完成,比如上面提到的 commit() 操作,下面是幾種常用的方法:
    • add() 系列:添加 Fragment 到 Activity 界面中;
    • remove():移除 Activity 中的指定 Fragment;
    • replace() 系列:通過內部調用 remove() 和 add() 完成 Fragment 的修改;
    • hide() 和 show():隱藏和顯示 Activity 中的 Fragment;
    • addToBackStack():添加當前事務到回退棧中,即當按下返回鍵時,界面迴歸到當前事物狀態;
    • commit():提交事務,所有通過上述方法對 Fragment 的改動都必須通過調用 commit() 方法完成提交
  • replace()和hide()區別
    • replace()和hide()都可以動態的在Activity中顯示多個Fragment,並且可以來回靈活的切換,但是它們有很大的區別,replace() 方法不會保留 Fragment 的狀態,也就是說諸如 EditText 內容輸入等用戶操作在 remove() 時會消失;但是hide()卻不會,能完整的保留用戶的處理信息。
  • addToBackStack()退棧
    • 當用戶按下返回鍵時,如果回退棧中保存有之前的事務,會先執行事務回退,然後再執行Activity的finish()方法 。
  • add(), show(), hide(), replace()區別
    • 它們之間區別
      • show(),hide()最終是讓Fragment的View setVisibility(true還是false),不會調用生命週期;
      • replace()的話會銷燬視圖,即調用onDestoryView、onCreateView等一系列生命週期;
      • add()和 replace()不要在同一個階級的FragmentManager裏混搭使用。
    • 使用場景
      • 如果你有一個很高的概率會再次使用當前的Fragment,建議使用show(),hide(),可以提高性能。
      • 在我使用Fragment過程中,大部分情況下都是用show(),hide(),而不是replace
      • 注意:如果你的app有大量圖片,這時更好的方式可能是replace,配合你的圖片框架在Fragment視圖銷燬時,回收其圖片所佔的內存。

03.onHiddenChanged回調時機

  • 當使用add()+show(),hide()跳轉新的Fragment時,舊的Fragment回調onHiddenChanged(),不會回調onStop()等生命週期方法,而新的Fragment在創建時是不會回調onHiddenChanged(),這點要切記。
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if(activity!=null){
            if(hidden){
                //當該頁面隱藏時
            }else {
                //當頁面展現時
            }
        }
    }
    

04.傳遞和接收參數

4.1 最常用的方法

  • 對Fragment傳遞數據,建議使用setArguments(Bundle args),而後在onCreate中使用getArguments()取出
    • 在 “內存重啓”前,系統會幫你保存數據,不會造成數據的丟失。和Activity的Intent恢復機制類似。
    • 使用newInstance(參數) 創建Fragment對象,優點是調用者只需要關係傳遞的哪些數據,而無需關心傳遞數據的Key是什麼。
    public static WyNewsFragment newInstance(String param) {
    	WyNewsFragment fragment = new WyNewsFragment();
    	Bundle args = new Bundle();
    	args.putString(TYPE, param);
    	fragment.setArguments(args);
    	return fragment;
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	if (getArguments() != null) {
    		mType = getArguments().getString(TYPE);
    	}
    }
    

4.2 Fragment與Activity之間傳值

  • Fragment與Activity之間是如何傳值的?
    • 1.Activity向Fragment傳值:
      • 步驟:
      • 要傳的值,放到bundle對象裏;
      • 在Activity中創建該Fragment的對象fragment,通過調用
      • fragment.setArguments()傳遞到fragment中;
      • 在該Fragment中通過調用getArguments()得到bundle對象,就能得到裏面的值。
    • 2.Fragment向Activity傳值:
      • 第一種:
        • 在Activity中調用getFragmentManager()得到fragmentManager,,調用findFragmentByTag(tag)或者通過findFragmentById(id)
        • FragmentManager fragmentManager = getFragmentManager();
        • Fragment fragment = fragmentManager.findFragmentByTag(tag);
      • 第二種:
        • 通過回調的方式,定義一個接口(可以在Fragment類中定義),接口中有一個空的方法,在fragment中需要的時候調用接口的方法,值可以作爲參數放在這個方法中,然後讓Activity實現這個接口,必然會重寫這個方法,這樣值就傳到了Activity中。

4.3 Fragment與Fragment之間傳值

  • Fragment與Fragment之間是如何傳值的?
    • 第一種:
      • 通過findFragmentByTag得到另一個的Fragment的對象,這樣就可以調用另一個的方法了。
    • 第二種:
      • 通過接口回調的方式。
    • 第三種:
      • 通過setArguments,getArguments的方式。

4.4 爲何不構造傳值

  • 爲什麼fragment傳遞數據不用構造方法傳遞?
    • activity給fragment傳遞數據一般不通過fragment的構造方法來傳遞,會通過setArguments來傳遞,因爲當橫豎屏會調用fragment的空參構造函數,數據丟失。

05.FragmentManager棧視圖

  • 簡要的關係圖
    • 每個Fragment以及宿主Activity(繼承自FragmentActivity)都會在創建時,初始化一個FragmentManager對象,處理好Fragment嵌套問題的關鍵,就是理清這些不同階級的棧視圖。
  • 獲取FragmentManager對象
    • 對於宿主Activity,getSupportFragmentManager()獲取的FragmentActivity的FragmentManager對象;
    • 對於Fragment,getFragmentManager()是獲取的是父Fragment(如果沒有,則是FragmentActivity)的FragmentManager對象,而getChildFragmentManager()是獲取自己的FragmentManager對象。

06.Fragment之懶加載使用

  • Fragment之懶加載使用
    • 懶加載,其實也就是延遲加載,就是等到該頁面的UI展示給用戶時,再加載該頁面的數據(從網絡、數據庫等),而不是依靠ViewPager預加載機制提前加載兩三個,甚至更多頁面的數據。這樣可以提高所屬Activity的初始化速度,也可以爲用戶節省流量.而這種懶加載的方式也已經/正在被諸多APP所採用。
    • 具體可以參考這篇博客,Android 懶加載優化:https://www.jianshu.com/p/cf1f4104de78
  • 使用FragmentPagerAdapter+ViewPager時
    • 使用FragmentPagerAdapter+ViewPager時,切換回上一個Fragment頁面時(已經初始化完畢),不會回調任何生命週期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)會被回調,所以如果你想進行一些懶加載,需要在這裏處理。
  • 在給ViewPager綁定FragmentPagerAdapter時
    • 在給ViewPager綁定FragmentPagerAdapter時,new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保證正確,如果ViewPager是Activity內的控件,則傳遞getSupportFragmentManager(),如果是Fragment的控件中,則應該傳遞getChildFragmentManager()。只要記住ViewPager內的Fragments是當前組件的子Fragment這個原則即可。

07.首頁Fragment使用

  • 首頁tab對應4個fragment,用法如下所示
    private void showFragment(int index) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        hideFragment(ft);
        position = index;
        switch (index) {
            case FRAGMENT_HOME:
                /**
                 * 如果Fragment爲空,就新建一個實例
                 * 如果不爲空,就將它從棧中顯示出來
                 */
                if (homeFragment == null) {
                    homeFragment = BaseFragmentFactory.getInstance().getHomeFragment();
                    ft.add(R.id.fl_main, homeFragment, HomeFragment.class.getName());
                } else {
                    ft.show(homeFragment);
                }
                break;
            case FRAGMENT_MOVIE:
                if (mMovieFragment == null) {
                    mMovieFragment = BaseFragmentFactory.getInstance().getMovieFragment();
                    ft.add(R.id.fl_main, mMovieFragment, MovieFragment.class.getName());
                } else {
                    ft.show(mMovieFragment);
                }
                break;
            case FRAGMENT_VIDEO:
                if (videoFragment == null) {
                    videoFragment = BaseFragmentFactory.getInstance().getVideoFragment();
                    ft.add(R.id.fl_main, videoFragment, VideoFragment.class.getName());
                } else {
                    ft.show(videoFragment);
                }
                break;
            case FRAGMENT_ME:
                if (meFragment == null) {
                    meFragment = BaseFragmentFactory.getInstance().getMeFragment();
                    ft.add(R.id.fl_main, meFragment, MeFragment.class.getName());
                } else {
                    ft.show(meFragment);
                }
                break;
            case FRAGMENT_NEWS:
                if (newsFragment == null) {
                    newsFragment = BaseFragmentFactory.getInstance().getNewsFragment();
                    ft.add(R.id.fl_main, newsFragment, MeFragment.class.getName());
                } else {
                    ft.show(newsFragment);
                }
                break;
            default:
                break;
        }
        ft.commit();
    }
    
    
    private void hideFragment(FragmentTransaction ft) {
        // 如果不爲空,就先隱藏起來
        if (homeFragment != null) {
            setHide(ft,homeFragment);
        }
        if (newsFragment != null) {
            setHide(ft,newsFragment);
        }
        if (mMovieFragment != null) {
            setHide(ft,mMovieFragment);
        }
        if (videoFragment != null) {
            setHide(ft,videoFragment);
        }
        if (meFragment != null) {
            setHide(ft,meFragment);
        }
    }
    
    private void setHide(FragmentTransaction ft, Fragment fragment) {
        if(fragment.isAdded()){
            ft.hide(fragment);
        }
    }
    

08.思考Fragment能否不依賴Activity嗎

  • Fragment能否不依賴於Activity存在?
    • Fragment不能獨立存在,它必須嵌入到activity中,而且Fragment的生命週期直接受所在的activity的影響。
      • transaction只是記錄了從一個狀態到另一個狀態的變化過程,即比如從FragmentA替換到FragmentB的過程,當通過函數transaction.addToBackStack(null)將這個事務添加到回退棧,則會記錄這個事務的狀態變化過程,如從FragmentA —>FragmentB,當用戶點擊手機回退鍵時,因爲transaction的狀態變化過程被保存,則可以將事務的狀態變化過程還原,即將FragmentB —> FragmentA.
      // Create new fragment and transaction
      Fragment newFragment = new ExampleFragment();
      FragmentTransaction transaction = getFragmentManager().beginTransaction();
      
      // Replace whatever is in the fragment_container view with this fragment,
      // and add the transaction to the back stack
      transaction.replace(R.id.fragment_container, newFragment);
      transaction.addToBackStack(null);
      
      // Commit the transaction
      transaction.commit();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章