我随便在网上搜了个 ViewPager + Fragment用法,类似的代码很常见:
public class MainActivity extends FragmentActivity {
private ViewPager m_vp;
private ArrayList<Fragment> fragmentList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
m_vp = (ViewPager)findViewById(R.id.viewpager);
mfragment1 = new fragment1();
mfragment2 = new fragment2();
mfragment3 = new fragment3();
fragmentList = new ArrayList<Fragment>();
fragmentList.add(mfragment1);
fragmentList.add(mfragment2);
fragmentList.add(mfragment3);
m_vp.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager()));
}
public class MyViewPagerAdapter extends FragmentPagerAdapter{
@Override
public Fragment getItem(int arg0) {
return fragmentList.get(arg0);
}
@Override
public int getCount() {
return fragmentList.size();
}
}
}
很多同学都喜欢这么写,然后还经常通过 adapter.getItem(pos)去获取对应的 fragment。
这种写法其实是存在很大的问题的。
那么今天的问题是:
- 这种写法在什么情况下,会造成异常(问题以及对应的场景)?
- 造成该问题的原因是(原理)?
- 更好的写法应该是,(提供根据 position 获取对应 Fragment 方法)。
更多问答 >>
-
2020-03-23 23:45
-
每日一问 | 自定义控件测量模式真的和 match_parent,wrap_content 一一对应吗?
2020-03-30 01:01 -
每日一问 | Activity 启动动画对页面打开速度有影响吗?
2020-04-22 22:06 -
每日一问 | Fragment 是如何被存储与恢复的? 有更新
2020-06-07 09:01 -
每日一问 | 上周出现了大规模的github证书不可用的状态...但是真的是github服务器被攻击了么?
2020-04-01 21:49 -
每日一问 LifeCycle 对于 Lifecycle.Event 为啥不直接分发,而是通过 Lifecycle.State 中转?
2020-04-19 14:42 -
每日一问 今天考察下 Fragment 相关两个不常见 API
2020-03-19 00:55 -
每日一问 | 很久以前有Activity.onResume就是界面可见的说法,这种说法错了多少?
2020-03-15 23:10 -
每日一问 RecyclerView卡片中持有的资源,到底该什么时候释放?
2020-03-10 21:11 -
每日一问 | 事件到底是先到DecorView还是先到Window的?
2020-03-03 23:25
异常情况:
屏幕旋转 或者内存不足,导致activity被重建。原因:重建之后,activity会重新new 三个fragment,但是viewpager持有的三个fragment并不会被替换成这三个新的fragment,而是恢复之前的三个,这个是因为viewpager自身的恢复机制造成。解决方法:使用sparsearray 保存fragment,重写instantiateItem,destroyItem,在两个方法里面去增、删fragment。在getItem的时候,new 对应的fragment。PS:之前看过鸿洋大大的慕课网视频,大概总结了这么多 (^o^)/~
赞,看来我之前的视频讲解还是蛮清楚的~
viewPager2有没有默认解决这个问题呢?
应该是解决了,而且还一并解决了 notify不生效等问题。
也就是说普通app即只有竖屏的app这样写是没有问题的吧?该问题只存在与有横竖屏切换的app(准确的说是activity)
不是,即使你设置了竖屏,Activity也有在后台被杀掉的情况,用户切回来也会重建,会有相同的问题。
解决办法的代码能提供一下参考吗,谢谢。不知道自己写的对不对
viewpager持有的三个fragment并不会被替换成这三个新的fragment ,这是什么原因呢?可以解释下吗?
从哪里可以看出 viewpager2 也解决了这个问题?
使用sparsearray和list保存有什么区别?不都是容器
这种写法在什么情况下会有问题?
@licy 同学也已经说了,就是当ViewPager所属Activity被Destroy(非主动finish)后重新初始化过程中,此时新创建的Fragment实例并不会在ViewPager中显示。为什么会这样呢?
先来看看我们定义Adapter时重写的getItem
方法是在哪里被调用的:咦?在
如果从FragmentManager中找不到的话,才会调用instantiateItem
方法中,我们重写的getItem
方法竟然不是每次都会被调用的!它会先判断FragmentManager是否已添加了目标Fragment(findFragmentByTag
),如果已经添加了的话,就会把它取出来并重新关联上,而getItem
方法就不会被调用了。getItem
获取目标Fragment,然后通过事务来添加进去,注意此时add
方法的第三个参数(tag
)传的是makeFragmentName
方法的返回值,它跟上面查找时传的值是一样的,来看一下:超简单,就是拼接一个字符串。
看回instantiateItem
方法,可以看到makeFragmentName
的两个参数分别传的是container
的id值和getItemId
方法返回的值:getItemId
方法如果不重写的话,返回就是参数值,也就是ViewPager页面的索引值了。好,总结一下:
在FragmentPagerAdapter的
instantiateItem
方法(这个方法会在ViewPager滑动状态变更时调用)中,每个position所对应的Fragment只会添加一次到FragmentManager里面,也就是说,我们在Adapter中重写的getItem
方法,它的参数position
不会出现两次相同的值。当Fragment被添加时,会给这个Fragment指定一个根据
itemId
来区分的tag
,而这个itemId
就是根据getItemId
方法来获取的,默认就是当前页面的索引值。如何避免开头所说的问题?
现在我们已经知道了问题发生的原因,要解决的话,对症下药就行了:既然ViewPager在添加新Item时会优先查找FragmentManager中已存在的Fragment,那么我们在Activity重建后,实例Fragment时也可以像它那样,先看看FragmentManager中有没有,如果有的话就直接重用,不用new了。比如定义一个instantiateFragment
方法:然后在原来实例化Fragment的地方:
改成:
就OK啦!!!
这样的话,就算Activity被意外销毁,重新创建时,我们也一样能找回原来已经添加了的Fragment。改成在getItem(){} 里面new Fragment1、2、3 可以么?
不可以。一:如果对应的Fragment在Activity重建前已经添加进FM,那么Activity重建后,获取这个Fragment的那一次getItem方法就不会被调用(可以再看一次回答)。二,你在A ...查看更多
不可以。一:如果对应的Fragment在Activity重建前已经添加进FM,那么Activity重建后,获取这个Fragment的那一次getItem方法就不会被调用(可以再看一次回答)。二,你在Activity中操作Fragment时,总不会每次都从Adapter中获取吧?肯定也是像题目给出的代码一样,在Activity里面直接保存一个引用的,如果不更新这个引用的话,也还是会出问题啊。
问下。如果使用的是FragmentStatePagerAdapter方式,会有这样的问题吗?
不会,FragmentStatePagerAdapter不会直接从FragmentManager中取出Fragment
我看FragmentStatePagerAdapter在saveState中也是将fragments保存在FragmentManager,然后在restoreState中取出,跟FragmentPag ...查看更多
我看FragmentStatePagerAdapter在saveState中也是将fragments保存在FragmentManager,然后在restoreState中取出,跟FragmentPagerAdapter应该是一样的吧
不好意思,我说的是错的。就像你说的那样,FragmentStatePagerAdapter一样会直接保存fragment实例,在instantiateItem时也是会优先查找上次恢复的list有没有对 ...查看更多
不好意思,我说的是错的。就像你说的那样,FragmentStatePagerAdapter一样会直接保存fragment实例,在instantiateItem时也是会优先查找上次恢复的list有没有对应fragment的,如果有就直接重用,你说的是对的。
String tag = "android:switcher:" + viewPager.getId() + ":" + position; 大佬,这个tag必须跟系统makeFragmentName ...查看更多
String tag = "android:switcher:" + viewPager.getId() + ":" + position; 大佬,这个tag必须跟系统makeFragmentName()生成的tag保持一样是吗?
是的,只有这样才能从FragmentManager中把对应的Fragment实例取出来嘛
补充下:FragmentPagerAdapter组成tag的方式是("android:switcher:" + viewId + ":" + id),其中viewId是ViewPager的id。如果你 ...查看更多
补充下:FragmentPagerAdapter组成tag的方式是("android:switcher:" + viewId + ":" + id),其中viewId是ViewPager的id。如果你的ViewPager的id是通过View.generateViewId()生成的,那么viewId每次都会不一样,导致无法重用Fragment。可以在res/values/ids.xml文件中定义一个id,设置给ViewPager,保证重建前后viewid一致,才能正常复用。
利用 adapter.getItem(pos)这个获取fragment
出现问题我觉得应该都是使用不当导致的如下:在MainActivity获取到数据某一处数据后,需要通知到所有Fragment更新,书写错误就会这样发生崩溃或异常两种情况:1、PageFragment3这个Fragment没划到也没加载过,button一按 必然崩溃
2、加载过是不会崩溃的 ,但是这样修改的话 不设置这个setOffscreenPageLimit 肯定会有页面因为被回收异常就发生了,还有现在FragmentAdapter不用这个的,顺带一提改用这个Behavior,方便了处理生命周期了,也方便懒加载了大致我理解是这些,不知道是否回答到点上
嗯...还可以考虑如果当前页面因为内存不足发生重建,会导致什么问题。
页面重建后,之前的fragment实例还在.新的fragment会覆盖在之前的fragment上. 是这样么?
不会覆盖,而是后面创建的fragment不会被用上。。。因为Adapter它会优先查找已存在的
如果后面更新UI,直接用新创建的fragment的话,界面上是看不到效果的,因为现在显示的还是旧的
你这不考虑回答下?
为什么都在说和"ViewPager自身的恢复机制造成的"。难道不应该是在activity重建前调用FragmentManager的保存和activity重建后FragmentManager的恢复机制造成的。和ViewPager有什么关系?
而FragmentPagerAdapter在instantiateItem时根据tag获取fragment。那么activity重建后保存的fragment被恢复了。通过findFragmentByTag查找的还是原先的fragment。而此时重建后的activity又重新new了fragment。这就造成你以为是自己,其实不是。如果使用list去get某个位置fragment刷新时,就会刷新无效。而解决此问题的做法@陈小缘 已经解答。另外如果需要获取adapter中的fragment推荐使用FragmentManager# findFragmentByTag
另外如果list中保存的fragment后续再无根据此list操作fragment。其实这样也不会造成任何错误现象。只是list中可能存了不是该页面上展示的fragment。 ...查看更多
另外如果list中保存的fragment后续再无根据此list操作fragment。其实这样也不会造成任何错误现象。只是list中可能存了不是该页面上展示的fragment。
问题
如果这个只是基本的展示,那是没有问题的,可以正常展示,不会崩溃
但是在onCreate里面加一段新的代码:
同时在调试中把”不保留活动“打开,按home退到后台再点击icon回来的时候就崩溃了!
原因
这是因为ViewPager自身有恢复机制,这时ViewPager里的fragment的list和onCreate赋值的fragmentList已经不是一个对象了。fragmentList里的fragment并没有attch到activity里,所以并没有create,导致对里面的UI进行操作就会崩溃了
解决方案
在onCreate的时候恢复fragmentList
这样我们外面的fragmentList就和ViewPager恢复出来的list内部保存的对象一样了
不让ViewPager恢复
覆写FragmentPagerAdapter的saveState()方法
当然这种方式并不推荐
我也遇到过这个问题😄
https://blog.csdn.net/SS_S1gn/article/details/94456382大佬,请问怎么提问题呢?
目前不支持自主提问呢。
支持 学习了