天天看點

完美解決Fragment重疊的問題完美解決Fragment重疊的問題

完美解決Fragment重疊的問題

一 原因

app所配置設定的記憶體不足時,非可見Activity都有可能被回收,FragmentActivity回收表現在:當FragmentActivity進入背景有可能被系統回收時,系統會回調Activity的

/**
 * Save all appropriate fragment state.
 */
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    if (mPendingFragmentActivityResults.size() > 0) {
        outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);

        int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
        String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
        for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
            requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
            fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
        }
        outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
        outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
    }
}      
完美解決Fragment重疊的問題完美解決Fragment重疊的問題

生命周期方法。很明顯FragmentActivity儲存了之前所有的Fragment狀态并且序列化(注意:這裡隻儲存了所有fragment的狀态,并不是儲存目前執行個體,目前的fragment執行個體會随着系統的回收而一起回收)

當系統檢測到該FragmentActivity需要回收時,再次進入該Activity就會重新執行個體化FragmentActivity,并帶入上次onSaveInstance儲存的所有狀态,其中就包括所有的Fragment,而以這種情況執行個體化的FragmentActivity在onCreate的時候會自動處理儲存下來的fragments,請看以下FragmentActivity的源碼

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.attachHost(null /*parent*/);

    super.onCreate(savedInstanceState);

    NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
    if (nc != null) {
        mFragments.restoreLoaderNonConfig(nc.loaders);
    }
    if (savedInstanceState != null) {
        Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
        mFragments.restoreAllState(p, nc != null ? nc.fragments : null);

        // Check if there are any pending onActivityResult calls to descendent Fragments.
        if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
            mNextCandidateRequestIndex =
                    savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
            int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
            String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
            if (requestCodes == null || fragmentWhos == null ||
                        requestCodes.length != fragmentWhos.length) {
                Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
            } else {
                mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
                for (int i = 0; i < requestCodes.length; i++) {
                    mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
                }
            }
        }
    }

    if (mPendingFragmentActivityResults == null) {
        mPendingFragmentActivityResults = new SparseArrayCompat<>();
        mNextCandidateRequestIndex = 0;
    }

    mFragments.dispatchCreate();
}      

注意:不同版本的源碼大同小異。 但是,一般情況下我們在建立FragmentActivity的時候會手動建立Fragment執行個體并add到Activity上,那麼問題就處在這裡,這時候系統自己生成了一套Fragmemts自己又建立了一套Fragments。而我們在代碼中隻操作了自己建立的那些Fragments,進而導緻界面出現重疊的情況。

二 解決問題

既然系統根據之前的fragments狀态恢複到新的Activity執行個體中,那麼本着不産生新問題以及不破壞Activity生命周期的原則(有人說注釋掉onSaveInstance方法),且看我如何完美解決此問題:

正常建立Activity時,我們将fragments儲存起來,以便代碼友善使用

private BaseFragment[] fragments;      

也許你的是ArrayList,SparseList都可以(對于fragments量小,基本隻有索引查找時建議使用數組形式,減少開銷)

以下是我如何動态建立fragment以及如何hide和show,其中enum類型的Item你們可以了解成一個int型的index即可,我自己封裝了一些業務需要,

public void showFragment(Item item) {
    if (item == null || activity.getCurrentItem() == item) {
        return;
    }
    hideAll();
    FragmentTransaction transaction = manager.beginTransaction();
    if (fragments[item.getIndex()] == null) {
        switch (item) {
            case HOME://首頁
                fragments[item.getIndex()] = new HomeFragment();
                break;
            case USER_CENTER://使用者中心
                fragments[item.getIndex()] = new UserCenterFragment();
                break;
            default:
                break;
        }
        transaction.add(R.id.container, fragments[item.getIndex()], String.valueOf(item.getIndex()));
    } else {
        transaction.show(fragments[item.getIndex()]);
    }
    transaction.commitAllowingStateLoss();
}      

這是正常情況下的建立,那麼針對系統回收的情況我們該怎麼處理呢?簡單!!!!!! 我們隻需要将系統建立的Fragments存儲到fragments數組中即可,當代碼走到showFragment()方法的時候,就不會再次建立Fragment執行個體了。

直接上代碼:(請注意,這個方法是我自己寫的,不是系統方法)最終這個方法應該在Activity的onCreate方法中調用

public void onRestoreInstanceState(Bundle savedInstanceState) {
    if (savedInstanceState != null) {
        Item currentItem = null;
        int index = savedInstanceState.getInt(MainActivity.ITEM_INDEX_KEY, Item.HOME.getIndex());
        for (int i = 0; i < fragments.length; i++) {
            int tmpIndex = Item.values()[i].getIndex();
            fragments[i] = (BaseFragment) manager.findFragmentByTag(String.valueOf(tmpIndex));
            if (index == tmpIndex) {
                currentItem = Item.values()[i];
            }
        }
        if (currentItem != null) {
            activity.setCurrentItem(currentItem);
        } else {
            activity.setCurrentItem(Item.HOME);
        }
    }
}      

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.act_main);
    //注意此代碼必須在super.onCreate之後,因為必須保證父類FragmentActivity的onCreate優先執行,
    if (savedInstanceState != null) {
        containerModel.onRestoreInstanceState(savedInstanceState);
    } else {
        navigationView.setCurrentItem(Item.HOME);
    }
}      

好了 問題解決!!

附:我在onRestoreInstanceState中儲存了一個目前Fragment的索引,也是為了解決,當重新進來時每次都展示第一個fragment的問題,這樣使用者就察覺不到背景系統回收每次都回到首頁的問題。索引的儲存請看:

@Override
protected void onSaveInstanceState(Bundle outState) {
    Item item = getCurrentItem();
    if (item != null) {
        outState.putInt(ITEM_INDEX_KEY, item.getIndex());
    }
    super.onSaveInstanceState(outState);
}