作者:怪盗kidou
链接:https://www.jianshu.com/p/31f013df7580
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
根本原因只有一个:Activity在重建的时候回恢复其包含的FragmentManager,FragmentManager又会恢复其管理的Fragment,同理Fragment也会恢复其包含的FragmentManager,层层递进,直至全部恢复
复用的好处:
所以复用还是相当有必要的,同时当我们知道了要复用的根本原因之后,如何复用Fragment也就变成 【如何查找已存在的Fragment】的问题了。
##二、如何获取已经存在的Fragment ##
目前已经知道的方法如下:
【不推荐】获取全部的已经添加到FragmentManager
FragmentManager.getFragments()
根据TAG查找Fragment
FragmentManager.findFragmentByTag(String tag)
根据ID查找Fragment
FragmentManager.findFragmentById(int id)
FragmentManager.getFragment(Bundle bundle,String key)
FragmentManager.putFragment(Bundle bundle, String key, Fragment fragment)
既然不推荐,那么总是有原因的,总结几点原因如下:
FragmentManager.getFragments()
会返回所有已经添加到 FragmentManager 中的 Fragment,这就可能导致这个列表中包含了非我们自己所定义的Fragment,你可能会有疑问界面上不就显示我自己定义的Fragment么?
首先我们应该清楚的认识到 Fragment 不单单是界面的载体,它也可以用来实现别的功能,比如 生命周期 的监听。比如图片加载库 Glide 以及 Android 最新的 Android 架构组件
中的 ViewModel
都采用了这种方式。
所以如果我们的 Fragment
是和 ViewPager
组合使用并且直接将包含这些实例对象(比如 ViewModel 用到 HolderFragment) FragmentManager.getFragments()
的结果丢给 FragmentPagerAdapter 的话那么就会达成本博客的第一项成就:Fragment重复添加
throw new IllegalStateException("Fragment already added: " + fragment
下面的这段代码我相信大家熟悉,就算自己没写过也看别人写过(没见就是我)
MainFragment mainFragment = (MainFragment) fm.getFragments().get(0)
// 略.......
SecondaryFragment secondaryFragment = (SecondaryFragment) fm.getFragments().get(1)
// 略.......
这样的写法就会帮助你达成第二项成就:类型转换异常
throw new ClassCastException("Cannot cast android.arch.lifecycle.HolderFragment to MainFragment")
从 ViewModel
相关源码那里可以知道FragmentManager.getFragments()
中包含了其他的Fragment,而这些Fragment的位置往往是不固定,以ViewModel为例,HolderFragment的位置是由初始化的时机决定的。
也就是说你调整了一下 ViewModel 初始化的调用顺序或者在Kotlin项目中将 lateinit
改成了 by lazy
都可能会发生这样的Crash!就 lateinit
改成 by lazy
这条就是我前不久在做项目时真实遇到的。
在版本25中Activity是新建的情况下返回的是null,在版本26中返回的是Cllections.EmptyList(), 前面我在维护公司项目时引入了 ROOM然后有几个界面崩溃了!
经排除发现问题就出在下面这段代码中。
mFragments = new ArrayList<>();
if(fm.getFragments() == null){
mFragments.add(new MainFragment())
mFragments.add(new SecondaryFragment())
}else{
mFragments.addAll(fm.getFragments())
}
mViewPager.setAdapter(new MyViewPagerAdapter(fm, mFragments))
mTabLayout.setupWithViewPager(mViewPager)
// .....
mTabLayout.getTabAt(0).setText("MainFragment")
// .....
原因就是版本26下,返回的不是 null
导致 mFragments 是空的,自然mTabLayout里面是没有Tab的,所以导致了 空针异常,如果这段代码不依赖 getFragments
方法的话其实是没有问题的。
不知道大家有没有注意,如果这个Activity也使用ViewModel,那么还可能会顺带达成上面的 成就一和成就二
通过上面的一些例子我们知道了既然直接通过 FM.getFragments()
不可靠,那么通过其他几种方式来获取我们想要找的 Fragment 实例结果如何呢,接着往下看。
该方法是用过 Fragment 所在的 ViewGroup 的 id(containerViewId) 来查找 Fragment,适合一个 ViewGroup 中只有一个 Fragment 的情况。
方法签名:
public abstract Fragment findFragmentById(@IdRes int id);
用法示例:
private MainFragment mainFragment;
@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mainFragment = (MainFragment) getSupportFragmentManager()
// 这个ID和下面添加 fragment 时指定的 id 要一致
.findFragmentById(android.R.id.content);
} else {
mainFragment = new MainFragment();
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, mainFragment)
.commit();
}
}
findFragmentByTag
方法。当一个 ViewGroup 中有多个 Fragment 时 findFragmentById 可能就不是太好使了,这种情况下就需要我们使用 findFragmentByTag 了。
由于是通过 tag 查找已经添加到 FragmentManager 里的 Fragment 实例对象,所以和 containerViewId 也就没有关系了,当然了在我们添加 Fragment 的时候也要注意给 fragment 指定 tag。
方法签名:
public abstract Fragment findFragmentByTag(String tag);
用法示例:
private MainFragment mainFragment;
@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mainFragment = (MainFragment) fm.findFragmentByTag(MainFragment.TAG);
} else {
mainFragment = new MainFragment();
fm.beginTransaction()
// 在添加的时候给其制定 tag,不然到时候上面的语句就没用了
.add(android.R.id.content, mainFragment, MainFragment.TAG)
.commit();
}
}
上面就是一个很简单的用 TAG 来获取Fragment 的例子,这里需要注意的就是 tag
参数是我们在进行 add
或 replace
操作的时候指定的。
提示
上面的 findFragmentById
和 findFragmentByTag
在使用的时候其实都是有一些隐藏限制的:
但是很不巧ViewPager与这两个情况都匹配不上,原因:
你去搜 【ViewPager find fragment】 可能别人告诉你的 调用 makeFragmentName 生成 tag 或者用
*findFragmentByTag(“android:switcher:” + viewPager.getId() + “:” + viewPager.getCurrentItem())的那些做法就不要再用了!
// FragmentPagerAdapter.javaprivate static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
正确的处理姿势示范:
private MainFragment mainFragment;
private SecondaryFragment secondaryFragment;
@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mainFragment = (MainFragment) fm.getFragment(savedInstanceState, MainFragment.TAG);
secondaryFragment = (SecondaryFragment) fm.getFragment(savedInstanceState, SecondaryFragment.TAG);
}
if (mainFragment == null) {
mainFragment = new MainFragment();
}
if(secondaryFragment == null){
secondaryFragment = new SecondaryFragment()
}
// ViewPager 的相关操作
}
@Overrideprotected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mainFragment.isAdded()) {
fm.putFragment(outState, MainFragment.TAG, mainFragment);
}
if (secondaryFragment.isAdded()) {
fm.putFragment(outState, SecondaryFragment.TAG, secondaryFragment);
}
}
两个方法的源码如下:
// FragmentManager.java,摘自版本 27.1.1
@Override
public void putFragment(Bundle bundle, String key, Fragment fragment) {
if (fragment.mIndex < 0) { // 没有被添加到 FragmentManager
throwException(new IllegalStateException("Fragment " + fragment
+ " is not currently in the FragmentManager"));
}
bundle.putInt(key, fragment.mIndex);
}
@Override
public Fragment getFragment(Bundle bundle, String key) {
int index = bundle.getInt(key, -1);
if (index == -1) {
return null;
}
Fragment f = mActive.get(index);
if (f == null) {
throwException(new IllegalStateException("Fragment no longer exists for key "
+ key + ": index " + index));
}
return f;
}
原理解析:
先放两张图,然后结合图片解析
上图只是给出了我们已经知道的,未知的Fragment没有表示出来,但不代表不存在
以 图中 Fragment A 为例,其他的同理
注意事项: