Android Fragment最佳實踐

該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!android


前言

上一篇文章中詳細分析了Fragment相關知識,那麼做爲「小Activity」,Fragment能作什麼呢,如何使用Fragment獲得最佳實踐呢。Fragment的設計最初也許是爲了大屏幕平板設備的需求,不過如今Fragment已經普遍運用到咱們普通的手機設備上。下圖是咱們幾乎在主流App中都能發現的一個功能。git

熟悉Android的朋友必定都會知道,很簡單嘛,使用TabHost就OK了!可是卻不知,TabHost並不是是那麼的簡單,它的可擴展性很是的差,不能隨意地定製Tab項顯示的內容,並且運行還要依賴於ActivityGroup。ActivityGroup本來主要是用於爲每個TabHost的子項管理一個單獨的Activity,但目前已經被廢棄了。爲何呢?固然就是由於Fragment的出現了!github

好了,,下面我就來實現上圖的效果,不過在開始以前,首先你必須已經瞭解Fragment的用法了,若是你對Fragment還比較陌生的話,建議先去閱讀我前面的一篇文章Android開發之漫漫長途 XII——Fragment詳解segmentfault

先建立宿主Activity

新建BestFragmentActivity性能優化

public class BestFragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_best_fragment);
        
    //下面是LuseenBottomNavigation的使用
    BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);

    BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
            ("首頁", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
    BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
            ("分類", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp);

    BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
            ("任務", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
    BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
            ("購物車", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp);

    BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
            ("個人", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp);

    bottomNavigationView.addTab(bottomNavigationItem);
    bottomNavigationView.addTab(bottomNavigationItem1);
    bottomNavigationView.addTab(bottomNavigationItem2);
    bottomNavigationView.addTab(bottomNavigationItem3);
    bottomNavigationView.addTab(bottomNavigationItem4);
    }

}

對應的佈局文件activity_best_fragmentapp

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_content"
    android:fitsSystemWindows="true"
    >

    <!--Fragment以後就動態的放在該佈局文件下-->
    <FrameLayout
        android:id="@+id/frame_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        android:layout_above="@+id/bottomNavigation"
        />

    <!--關於底層佈局我這裏使用了Github上的開源項目-->
    <com.luseen.luseenbottomnavigation.BottomNavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:layout_alignParentBottom="true"
        app:bnv_colored_background="false"
        app:bnv_with_text="true"
        app:bnv_shadow="false"
        app:bnv_tablet="false"
        app:bnv_viewpager_slide="true"
        app:bnv_active_color="@color/colorPrimary"
        app:bnv_active_text_size="@dimen/bottom_navigation_text_size_active"
        app:bnv_inactive_text_size="@dimen/bottom_navigation_text_size_inactive"/>

</RelativeLayout>

關於底層佈局我這裏使用了Github上的開源項目LuseenBottomNavigation,該項目地址是https://github.com/armcha/LuseenBottomNavigation讀者可自行查看ide

接着建立Fragment

目前Fragment做爲演示使用,能夠看到佈局內容都很是簡單,我這裏只給出其中一個Fragment的建立過程和源碼,項目完整源碼可見文末的源碼地址。佈局

咱們就拿第一個GoodsFragment舉例把性能

public class GoodsFragment extends Fragment {
    private static String TAG= GoodsFragment.class.getSimpleName();
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d(TAG,"onAttach");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG,"onCreateView");
        View view = inflater.inflate(R.layout.fragment_goods, null);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.d(TAG,"onViewCreated");
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG,"onActivityCreated");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"onCreate");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG,"onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG,"onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG,"onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG,"onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG,"onDetach");
    }

}

源碼很是的簡單,在onCreateView中加載佈局文件,該佈局文件也很是簡單,僅僅定義了一個幀佈局,在幀佈局中包含了一個TextView優化

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Goods"
        android:textStyle="bold"
        android:textSize="30sp"
        android:layout_gravity="center"/>

</FrameLayout>

按照上面的流程咱們創建了所需的Fragment,接着該更改BestFragmentActivity的代碼,更改後的源碼以下

public class BestFragmentActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_best_fragment);
        //底部導航佈局
        BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);

        BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
                ("首頁", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
        BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
                ("分類", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp);

        BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
                ("任務", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
        BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
                ("購物車", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp);

        BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
                ("個人", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp);

        bottomNavigationView.addTab(bottomNavigationItem);
        bottomNavigationView.addTab(bottomNavigationItem1);
        bottomNavigationView.addTab(bottomNavigationItem2);
        bottomNavigationView.addTab(bottomNavigationItem3);
        bottomNavigationView.addTab(bottomNavigationItem4);

        //爲底部導航佈局設置點擊事件
        bottomNavigationView.setOnBottomNavigationItemClickListener(new OnBottomNavigationItemClickListener() {
            @Override
            public void onNavigationItemClick(int i) {
                switch (i){
                    case 0:
                        switchToHome();
                        break;
                    case 1:
                        switchToCategory();
                        break;
                    case 2:
                        switchToTask();
                        break;
                    case 3:
                        switchToGoodCar();
                        break;
                    case 4:
                        switchToAbout();
                        break;
                }
            }
        });
        
        //初始加載首頁,即GoodsFragment
        switchToHome();
    }
    

    private void switchToAbout() {
        getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new AboutFragment(),AboutFragment.class.getName()).commit();
    }
    private void switchToCategory() {
        getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new CategoryFragment(),CategoryFragment.class.getName()).commit();
    }

    private void switchToTask() {
        getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new TaskFragment(),TaskFragment.class.getName()).commit();
    }

    private void switchToGoodCar() {
        getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodCarFragment(),GoodCarFragment.class.getName()).commit();
    }

    private void switchToHome() {
        getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodsFragment(),GoodsFragment.class.getName()).commit();
    }
}

上面的代碼能夠根據上一篇文章比較容易的寫出來,並且正常運行,但是在實際開發過程當中咱們不得不考慮代碼的性能問題。其實上面的代碼存在性能問題,尤爲是在底部導航這種場景中,Fragment之間的來回切換,這裏使用的replace方法。關於這個方法帶來的問題以及如何進行優化,將在下一節詳細說明。

Fragment 性能優化問題

FragmentTransaction

談到Fragment的性能優化問題,就不得不對FragmentTransaction進行深刻的研究以及探討,上面使用了getSupportFragmentManager().beginTransaction()獲得了FragmentTransaction對象,並依次調用其replace方法和commit方法。

  • replace(int containerViewId, Fragment fragment)、replace(int containerViewId, Fragment fragment, String tag)

該方法的做用是,相似於先remove掉視圖容器全部的Fragment,再add方法參數中的fragment,併爲該Fragment設置標籤tag。

getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new AboutFragment(),AboutFragment.class.getName()).commit();
    getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new CategoryFragment(),CategoryFragment.class.getName()).commit();
    getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new TaskFragment(),TaskFragment.class.getName()).commit();
    getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodsFragment(),GoodsFragment.class.getName()).commit();

如上面所示代碼塊中,咱們先進行了3次添加操做,以後的replace操做會移出前面添加的Fragment,再添加方法參數中指定的Frament。

  • add(int containerViewId, Fragment fragment, String tag)、 remove(Fragment fragment)

FragmentTransaction的Add()操做是維持着一個隊列的,在這個隊列中,根據ADD進去的前後順序造成了一個鏈表,咱們上面的操做在這個列表中的形式變化以下圖所示:

  • remove(Fragment fragment) : 移除一個已經存在的Fragment.
  • show(Fragment fragment): 顯示一個之前被隱藏過的Fragment
  • hide(Fragment fragment) : 隱藏一個存在的Fragment

    注:①Fragment被hide/show,僅僅是隱藏/顯示Fragment的視圖,不會有任何生命週期方法的調用。

    ②在Fragment中重寫onHiddenChanged方法能夠對Fragment的hide和show狀態進行監聽。

還有一些其餘的方法這裏就不一一列舉了,有了上面所列出的方法,咱們就能對Fragment有個很不錯的優化了。

Fragment性能問題分析與解決

Fragment性能問題分析

咱們上面是使用replace來切換頁面,那麼在每次切換的時候,Fragment都會從新實例化,從新加載一邊數據,這樣很是消耗性能和用戶的數據流量。這是由於replace操做,每次都會把container中的現有的fragment實例清空,而後再把指定的fragment添加進去,就就形成了在切換到之前的fragment時,就會從新實例會fragment。

Fragment性能問題解決

知道了問題的根源所在,那麼解決的辦法也呼之欲出了。咱們不能使用replace來進行頁面的切換,那麼可以使用的方法貌似只有add了,咱們能夠在加載的時候判斷Fragment是否是已經被添加到隊列中,若是已添加,咱們就顯示(show)該Fragment,隱藏(hide)其餘,若是沒有添加過呢,就添加。這樣就能作到多個Fragment切換不從新實例化。具體到代碼中就是這樣的

public class BestFragmentActivity extends AppCompatActivity{
    
    //當前的Fragment
    private Fragment mCurFragment = new Fragment();
    //初始化其餘的Fragment
    private GoodsFragment mGoodsFragment = new GoodsFragment();
    private GoodCarFragment mGoodCarFragment = new GoodCarFragment();
    private TaskFragment mTaskFragment = new TaskFragment();
    private AboutFragment mAboutFragment  = new AboutFragment();
    private CategoryFragment mCategoryFragment  = new CategoryFragment();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_best_fragment);
        BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);

        BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
                ("首頁", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
        BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
                ("分類", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp);

        BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
                ("任務", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
        BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
                ("購物車", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp);

        BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
                ("個人", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp);

        bottomNavigationView.addTab(bottomNavigationItem);
        bottomNavigationView.addTab(bottomNavigationItem1);
        bottomNavigationView.addTab(bottomNavigationItem2);
        bottomNavigationView.addTab(bottomNavigationItem3);
        bottomNavigationView.addTab(bottomNavigationItem4);

        bottomNavigationView.setOnBottomNavigationItemClickListener(new OnBottomNavigationItemClickListener() {
            @Override
            public void onNavigationItemClick(int i) {
                switch (i){
                    case 0:
                        switchToHome();
                        break;
                    case 1:
                        switchToCategory();
                        break;
                    case 2:
                        switchToTask();
                        break;
                    case 3:
                        switchToGoodCar();
                        break;
                    case 4:
                        switchToAbout();
                        break;
                }
            }
        });
        switchToHome();
    }

   private void switchFragment(Fragment targetFragment){
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();
        if (!targetFragment.isAdded()) {//若是要顯示的targetFragment沒有添加過
            transaction
                    .hide(mCurFragment)//隱藏當前Fragment
                    .add(R.id.frame_content, targetFragment,targetFragment.getClass().getName())//添加targetFragment
                    .commit();
        } else {//若是要顯示的targetFragment已經添加過
            transaction//隱藏當前Fragment
                    .hide(mCurFragment)
                    .show(targetFragment)//顯示targetFragment
                    .commit();
        }
        //更新當前Fragment爲targetFragment
        mCurFragment = targetFragment;

    }


    private void switchToAbout() {
        switchFragment(mAboutFragment);
    }
    private void switchToCategory() {
        switchFragment(mCategoryFragment);
    }
    private void switchToTask() {
        switchFragment(mTaskFragment);
    }
    private void switchToGoodCar() {
        switchFragment(mGoodCarFragment);
    }
    private void switchToHome() {
        switchFragment(mGoodsFragment);
    }

}

這樣就達到了咱們的目的,咱們在來回切換的操做中,Fragment只實例一次,少了銷燬又從新建立等帶來的性能消耗,另咱們想要在Fragment中更新數據時,咱們能夠在自定義Fragment中重寫其onHiddenChanged方法

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden){
       //Fragment隱藏時調用
    }else {
        //Fragment顯示時調用
    }

}

源碼地址:源碼傳送門

本篇總結

咱們在本篇博客中比較詳細的給出了一個Fragment的最佳實踐,咱們在許多主流App中都能看到這種頂部、底部導航的效果,而且在此基礎上咱們探討了使用Fragment不當的存在性能問題及優化。


下篇預告

下篇打算往Fragment中加點東西,ListView


此致,敬禮

相關文章
相關標籤/搜索