Fragment不得不说的一些事

/ 0评 / 0

Activity重创建对Fragment的影响

首先看一段我们在代码中经常出现的片段

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_life_cycle);
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    Fragment fragment = new FragmentOne();
    transaction.add(R.id.container, fragment);
    transaction.commit();
}

乍一看上去还是非常ok的,创建一个Fragment然后添加到布局,可是当我们打开开发者选项中的不保留应用然后再回到应用就会发现我们常见的Fragment重叠的问题。

原因:当我们的Activity被系统回收然后再次重建的时候FragmentManager会帮我们重新恢复上次的Fragment的状态,所以我们不应该直接创建Fragment然后添加,而是应该判断Fragment是否已经存在!

正确做法:在添加Fragment之前判断是否存在,如果不存在才添加

private static final String FRAGMENT_TAG1 = TAG1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_life_cycle);
    Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG1);
    if (fragment == null) {
        fragment = BlankFragment.newInstance(String.valueOf(System.currentTimeMillis()));
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.add(R.id.home_root, fragment, FRAGMENT_TAG1);
        transaction.commit();
    }
}

FragmentManager为我们保存了那些有关Fragment的信息呢?

我们查看一下FragmentState类即可,包括Fragment的可见性、实例等等

final class FragmentState implements Parcelable {
    final String mClassName;
    final int mIndex;
    final boolean mFromLayout;
    final int mFragmentId;
    final int mContainerId;
    final String mTag;
    final boolean mRetainInstance;
    final boolean mDetached;
    final Bundle mArguments;
    final boolean mHidden;

    Bundle mSavedFragmentState;

    Fragment mInstance;
}

PS:我们使用Activity+Fragment是否常用的一个操作就是使用一个下标标记当前显示的是哪一个Fragment,所以对于这个变量我们也需要自己手动保存,如下

public class MainActivity extends AppCompatActivity {

    private static final String TAG_INDEX = index_tag;

    private int mIndex = 0;

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

        if (savedInstanceState != null) {
            mIndex = savedInstanceState.getInt(TAG_INDEX, 0);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(TAG_INDEX, mIndex);
    }
}

在上面的代码中没有使用onRestoreInstanceState的原因是,当Activity被系统回收再重建的调用顺序是onSaveInstanceState->onCreate->onRestoreInstanceState

Fragment与ViewPager的那些事

同样的,我们再看一段代码

public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;

    private List<Fragment> mFragments = new ArrayList<>();

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

        mViewPager = findViewById(R.id.home_view_pager);

        for (int i = 0; i < 5; i++) {
            mFragments.add(BlankFragment.newInstance(fragment =  + i));
        }

        mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int i) {
                Log.i(TAG, getItem:  + i);
                return mFragments.get(i);
            }

            @Override
            public int getCount() {
                return mFragments.size();
            }
        });
    }
}

首先定义好数据集,然后在Adapter中返回给ViewPager,这样会有问题么?同样打开开发者选项中的不保留应用然后返回应用,如果这个时候我们在getItem中添加了日志则会发现,当我们选中已经被加载过的Fragment,这个时候getItem并不会被调用!!!

原因:其中的原理其实和最开始我们提出的一样,FragmentManager为我们保存了已经加载的Fragment以及各种状态,所以我们使用List保存Fragment列表的做法是错的,因为当Activity重建的时候,我们则多创建了几个永远不会被加载到ViewPager的Fragment,并且如果我们的业务依赖了这些Fragment,比如在Activity中调用Fragment的方法,则没有任何效果并可能导致Crash。我们查看一下FragmentPagerAdapter的源码中的instantiateItem方法

public abstract class FragmentPagerAdapter extends PagerAdapter {

    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, Attaching item # + itemId + : f= + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, Adding item # + itemId + : f= + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    private static String makeFragmentName(int viewId, long id) {
        return android:switcher: + viewId + : + id;
    }
}

我们可以很容易发现,FragmentPagerAdapter使用了findFragmentByTag方法去获取Fragment是否已经被加载过了,如果没有才会调用getItem。至于tag则是由makeFragmentName方法创建。PS:既然我们知道了Fragment的tag,那么我们能自己拼接tag然后获取Fragment么?答案是可以的!!!

Fragment fragment = getSupportFragmentManager().findFragmentByTag(makeFragmentName(mViewPager.getId(), 0));

if (fragment instanceof BlankFragment) {
    ((BlankFragment) fragment).showString(Activity调用Fragment的方法);
}

private String makeFragmentName(int viewId, long id) {
    return android:switcher: + viewId + : + id;
}

不过要注意的是,如果Fragment没有加载,可能获取到的为null。

参考链接https://www.jianshu.com/p/3d27ddc952fe

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注