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

发表评论

您的电子邮箱地址不会被公开。