天天看点

完美解决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);
}