随着ViewModel的大量使用,曾经并不是那么好用的Fragment走上了主流的舞台。相较于简明易懂的Activity管理,Fragment的管理更为复杂。在上周的需求中,我和同事共同完成了一个旧页面的彻底改造。在改造的过程中,我们发现了一个Fragment偶尔重复添加的bug。由于报错信息有限,同时对于Fragment的理解不够,我直接就是一波反向定位:new_moon_with_face:, 认为主要的问题是Activity被kill后恢复时,未判断是否是第一次添加fragment导致了重复添加。最后发现并不是这个问题导致的重复添加:disappointed_relieved:,但是探索的过程非常有趣,也增加了我对Fragment的理解。所以写下此文与大家分享。
茴字有四样写法,你知道么?
Replace
Fragment的replace也许是最简单的替换方法,也是《第一行代码》第一个提到的Fragment管理方式。replace首先会清空同一容器Id下的Fragment栈(remove),然后才是添加新的Fragment。这样处理最大的优点就是简单,因为如果我们全部使用replace的话,显然栈中永远只有一个Fragment,管理起来非常方便。但是简单的背面是每次销毁重建Fragment,带来的性能消耗是难以想象的。所以引出了基于栈的管理方式。
Add, Show和Hide
Fragment的管理类BackStackRecord使用了一个ArrayList(为什么是ArrayList不是Stack?)来管理我们的每一个操作。
结合FragmentTransaction中的静态变量和Op类,FragmentTransaction的实现类能够记录我们每一次对Fragment进行的操作,这通常代表我们能够很轻松地完成回滚操作(已帮我们封装好)。同时给予了我们fragment复用的能力。要想享受这些能力,我们就应该使用add来添加新的Fragment,而不是replace。但是add同样有自己的问题。
当我们只add不进行hide,show管理时,fragment会重叠在一起(!)。所以添加多个Fragment时一定要进行hide,show处理。但是这样就高枕无忧了吗?看看这样一个片段,这里我开启了开发者模式退出app就kill进程的开关。
管理Fragment的Activity代码如下:
1 | class MainActivity : AppCompatActivity() { |
新出现的问题,和之前只add没有hide,show处理的情况有些类似。很容易联想到是否在哪个环节又出现了只add没有hide的操作。通过查阅资料我们能够了解到,当Activity因为内存不足被回收时,会调用onSaveInstance()来保存视图层(FragmentActivity提供实现)。当Activity再度重建时,之前实例化的fragment会恢复到Activity中。同时onCreate中又走了一遍创建新fragment的逻辑,所以导致了fragment的重叠。
解决方案
重写onSaveInstanceState
如果不走父类的onSaveInstanceState,那么恢复fragment的流程也不会走。当然,所有的状态也都会丢失
1 | override fun onSaveInstanceState(outState: Bundle) { |
通过onCreate的参数savedInstanceState判断是否第一次加载
在第一次创建时,savedInstanceState总是为null。而当恢复activity时,savedInstanceState中保存了数据不再为空,那么我们显然能够通过这种方式来判断是否需要创建新的fragment。这里需要注意的是我们不能使用viewmodel来保存当前的页面,因为viewmodel只在activity是在前台被销毁时,恢复时能够获取到相同的viewmodel。如果activity是在后台太久被杀死的,那么就获取不到相同的viewmodel了。所以唯一的解决方式是通过savedInstanceState来保存信息,恢复时重新读取并赋值给ViewModel一些相关值(比如我存了当前页的Id在ViewModel中,需要恢复)。这样就能完美解决我们的问题。
1 | class MainActivity : AppCompatActivity() { |