Android中管理多個Fragment的最佳實踐,完美解決保存狀態與重影問題


自從在Android 3.0引入Fragment以來,它被使用的頻率也隨之增多。Fragment帶來的好處不言而喻,解決了不同屏幕分辨率的動態和靈活UI設計。但是在Activity管理多個Fragment中,通常會遇到這些問題:

1、Fragment的狀態保存

2、Fragment的重影

當然,這些問題也一直出現我的開發過程中,雖然有時候通過各種手段也能解決一些問題,但是總是同時完美解決這兩個問題。近來因為項目需要,查閱了很多官方資料(Android官方資料也慢慢有中文資料了,我大Google果然是Don't be evil,扯遠了~~),終於徹底解決了這些問題。

設備:nexus 5

條件:

1、打開“不保留活動”(開發者選項里,主要用於模擬Activity被及時回收)

2、關閉“不保留活動”(正常狀態下)

結果:目前沒發現問題,由於設備有限,大家如果發現在其他設備上有問題,請在下方回帖!

 

首先我先來解釋下上面問題出現的原因:

1、有時候,我們需要在多個Fragment間切換,並且保存每個Fragment的狀態。官方的方法是使用replace()來替換Fragment,但是replace()的調用會導致Fragment的onCreteView()被調用,所以切換界面時會無法保存當前的狀態。因此一般采用add()、hide()與show()配合,來達到保存Fragment的狀態。以下為代碼片段:

private void setTabSelection(int position) {
        //記錄position
        this.position = position;
        //更改底部導航欄按鈕狀態
        changeButtonStatus(position);
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 先隱藏掉所有的Fragment,以防止有多個Fragment顯示在界面上的情況
        hideFragments(transaction);
        switch (position) {
            case TAB_HOME:
                btnHomePager.setSelected(true);
                btnShoppingCart.setSelected(false);
                btnMine.setSelected(false);
                if (homeFragment == null) {
                    homeFragment = new HomePagerFragment();
                    transaction.add(R.id.fragment_container, homeFragment);
                } else {
                    transaction.show(homeFragment);
                }
                break;
            case TAB_SHOP:
                btnHomePager.setSelected(false);
                btnShoppingCart.setSelected(true);
                btnMine.setSelected(false);
                if (shoppingFragment == null) {
                    shoppingFragment = new ShoppingCartFragment();
                    transaction.add(R.id.fragment_container, shoppingFragment);
                } else {
                    transaction.show(shoppingFragment);
                }
                break;
            case TAB_MINE:
                btnHomePager.setSelected(false);
                btnShoppingCart.setSelected(false);
                btnMine.setSelected(true);
                if (mineFragment == null) {
                    mineFragment = new MineFragment();
                    transaction.add(R.id.fragment_container, mineFragment);
                } else {
                    transaction.show(mineFragment);
                }
                break;
        }
        transaction.commitAllowingStateLoss();
    }

 

2、第二個問題的出現正是因為使用了Fragment的狀態保存,當系統內存不足,Fragment的宿主Activity回收的時候,Fragment的實例並沒有隨之被回收。Activity被系統回收時,會主動調用onSaveInstance()方法來保存視圖層(View Hierarchy),所以當Activity通過導航再次被重建時,之前被實例化過的Fragment依然會出現在Activity中,然而從上述代碼中可以明顯看出,再次重建了新的Fragment,綜上這些因素導致了多個Fragment重疊在一起。

 

我嘗試了很多種方法去解決這個問題,比如:

在onSaveInstance()里面去remove()所有非空的Fragment,然后在onRestoreInstanceState()中去再次按照問題一的方式創建Activity。當我處於打開“不保留活動”的時候,效果非常令人滿意,然而當我關閉“不保留活動”的時候,問題卻出現了。當轉跳到其他Activity、打開多任務窗口、使用Home回到主屏幕再返回時,發現根本沒有Fragment了,一篇空白。

於是跟蹤下去,我調查了onSaveInstanceState()與onRestoreInstanceState()這兩個方法。原本以為只有在系統因為內存回收Activity時才會調用的onSaveInstanceState(),居然在轉跳到其他Activity、打開多任務窗口、使用Home回到主屏幕這些操作中也被調用,然而onRestoreInstanceState()並沒有在再次回到Activity時被調用。而且我在onResume()發現之前的Fragment只是被移除,並不是空,所以就算你在onResume()中執行問題一中創建的Fragment的方法,同樣無濟於事。所以通過remove()宣告失敗。

接着通過調查資料發現Activity中的onSaveInstanceState()里面有一句super.onRestoreInstanceState(savedInstanceState),Google對於這句話的解釋是“Always call the superclass so it can save the view hierarchy state”,大概意思是“總是執行這句代碼來調用父類去保存視圖層的狀態”。其實到這里大家也就明白了,就是因為這句話導致了重影的出現,於是我刪除了這句話,然后onCreate()與onRestoreInstanceState()中同時使用問題一中的創建Fragment方法,然后再通過保存切換的狀態,發現結果非常完美。代碼如下:

    //記錄Fragment的位置
    private int position = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_index);

        setTabSelection(position);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        position = savedInstanceState.getInt("position");
        setTabSelection(position);
        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        //記錄當前的position
        outState.putInt("position", position);
    }

 

記錄於此,希望能幫助到一些正遇到這種問題的朋友!


免責聲明!

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



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