本篇主要介绍一些最常见的Fragment的坑以及官方Fragment库的那些自身的BUG,这些BUG在你深度使用时会遇到,比如Fragment嵌套时或者单Activity+多Fragment架构时遇到的坑。
如果想看较为实用的技巧,请直接看第二篇
Fragment是可以让你的app纵享丝滑的设计,如果你的app想在现在基础上性能大幅度提高,并且占用内存降低,同样的界面Activity占用内存比Fragment要多,响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。
简陋的目录
1、getActivity()空指针
2、Fragment重叠异常—–正确使用hide、show的姿势
3、Fragment嵌套的那些坑
4、不靠谱的出栈方法remove()
5、多个Fragment同时出栈的那些深坑BUG
6、超级深坑 Fragment转场动画
开始之前
最新版知乎,单Activity多Fragment的架构,响应可以说非常“丝滑”,非要说缺点的话,就是没有转场动画,并且转场会有类似闪屏现象。我猜测可能和Fragment转场动画的一些BUG有关。(这系列的最后一篇文章我会给出我的解决方案,可以自定义转场动画,并能在各种特殊情况下正常运行。)
但是!Fragment相比较Activity要难用很多,在多Fragment以及嵌套Fragment的情况下更是如此。
更重要的是Fragment的坑真的太多了,看Square公司的这篇文章吧,Square:从今天开始抛弃Fragment吧!
当然,不能说不再用Fragment,Fragment的这些坑都是有解决办法的,官方也在逐步修复一些BUG。
下面罗列一些,有常见的,也有极度隐蔽的一些坑,也是我在用单Activity多Fragment时遇到的坑,可能有更多坑可以挖掘…
在这之前为了方便后面文章的介绍,先规定一个“术语”,安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。
在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate
方法调用后紧接着恢复(从onAttach
生命周期开始)。
getActivity()空指针
可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。
大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()
了宿主Activity。
比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决办法:
更安全的方法
在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)
里赋值,使用mActivity代替getActivity()
,保证Fragment即使在onDetach
后,仍持有Activity的引用(有引起内存泄露的风险,但是相比闪退,这种做法更“好”些),即:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
protected Activity mActivity; @Override public void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = activity; } /** * 如果你用了support 23的库,上面的方法会提示过时,有强迫症的小伙伴,可以用下面的方法代替 */ @Override public void onAttach(Context context) { super.onAttach(context); this.mActivity = (Activity)context; } |
其实我们应该尽量避免在Fragment已经onDetach后,再去使用其宿主Activity对象,这样才是最安全的办法。
Fragment重叠异常—–正确使用hide、show的姿势
如果你add()
了几个Fragment,使用show()、hide()
方法控制,比如微信、QQ的底部tab等情景,如果你什么都不做的话,在“内存重启”后回到前台,app的这几个Fragment界面会重叠。
原因是FragmentManager帮我们管理Fragment,每当我们离开该Activity,FragmentManager都会保存它的Fragments,当发生“内存重启”,他会从栈底向栈顶的顺序恢复Fragments,并且全都都是以show()
的方式,所以我们看到了界面重叠。(如果是replace
,恢复顺序和Activity一致:栈顶先恢复,当pop返回上一个Fragment时,再恢复这个Fragment)
这里给出2个解决方案:(为方便描述,以下皆不考虑Fragment嵌套的情况)
1、是大家比较熟悉的 findFragmentByTag
:
即在add()
或者replace()
时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag
找到对应的Fragment,并hide()
需要隐藏的fragment。
下面是个标准恢复写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); TargetFragment targetFragment; HideFragment hideFragment; if (savedInstanceState != null) { // “内存重启”时调用 targetFragment = getSupportFragmentManager().findFragmentByTag(targetFragment.getClass().getName); hideFragment = getSupportFragmentManager().findFragmentByTag(hideFragment.getClass().getName); // 解决重叠问题 getFragmentManager().beginTransaction() .show(targetFragment) .hide(hideFragment) .commit(); }else{ // 正常时 targetFragment = TargetFragment.newInstance(); hideFragment = HideFragment.newInstance(); getFragmentManager().beginTransaction() .add(R.id.container, targetFragment, targetFragment.getClass().getName()) .add(R.id,container,hideFragment,hideFragment.getClass().getName()) .hide(hideFragment) .commit(); } } |
如果你想恢复到用户离开时的那个Fragment的界面,你还需要在onSaveInstanceState(Bundle outState)
里保存离开时的那个见面的tag或下标,在onCreate
“内存重启”代码块中,取出tag/下标,进行恢复。
2、使用getSupportFragmentManager().getFragments()恢复
通过getFragments()
可以获取到当前FragmentManager管理的栈内所有Fragment。
标准写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@Override protected void onCreate(Bundl="http://android.jobbole.com/83073/" target="_blank">第二篇
Fragment是可以让你的app纵享丝滑的设计,如果你的app想在现在基础上性能大幅度提高,并且占用内存降低,同样的界面Activity占用内存比Fragment要多,响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。 简陋的目录 开始之前最新版知乎,单Activity多Fragment的架构,响应可以说非常“丝滑”,非要说缺点的话,就是没有转场动画,并且转场会有类似闪屏现象。我猜测可能和Fragment转场动画的一些BUG有关。(这系列的最后一篇文章我会给出我的解决方案,可以自定义转场动画,并能在各种特殊情况下正常运行。) 但是!Fragment相比较Activity要难用很多,在多Fragment以及嵌套Fragment的情况下更是如此。 当然,不能说不再用Fragment,Fragment的这些坑都是有解决办法的,官方也在逐步修复一些BUG。 在这之前为了方便后面文章的介绍,先规定一个“术语”,安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。 在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的 getActivity()空指针可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。 大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经 解决办法:
其实我们应该尽量避免在Fragment已经onDetach后,再去使用其宿主Activity对象,这样才是最安全的办法。 Fragment重叠异常—–正确使用hide、show的姿势如果你 原因是FragmentManager帮我们管理Fragment,每当我们离开该Activity,FragmentManager都会保存它的Fragments,当发生“内存重启”,他会从栈底向栈顶的顺序恢复Fragments,并且全都都是以 这里给出2个解决方案:(为方便描述,以下皆不考虑Fragment嵌套的情况) 1、是大家比较熟悉的 即在 下面是个标准恢复写法:
如果你想恢复到用户离开时的那个Fragment的界面,你还需要在 2、使用getSupportFragmentManager().getFragments()恢复 通过 标准写法如下:
|