现在市面的应用界面大多是通过一个Fragment容器+底部导航栏框架来实现页面切换的,而当我们想要去搭建一个这样的框架时,上层的Fragment容器是可选的,常见的有FragmentContanerView、ViewPager、ViewPager2。我们应该如何选择呢?这时就需要考虑这三者的自身自带的一些特性区别:
在我目前写的项目中这三者都有使用到,一开始并没有觉得有什么区别,因为写的都是一些很简单的项目。但在一次使用FragmentContainerView的过程中,我明显的感觉到了其与ViewPager2的一个显著区别:切换Fragment时生命周期方法的调用。
在这个使用FragmentContainerView+BottomNavigationView的项目中,我在其中一个页面加入了自定义的自动轮播图控件,而当我通过底部导航栏切换到另一界面再切回来时,我发现我的轮播图控件总是会自动从第一张图开始加载,而在我之前使用ViewPager2+BottomNaviagtionView的项目中我也使用了自动轮播图控件,但是并没有出现这个问题:即通过底部导航栏切换到另一个页面再切回来并不会使轮播图控件重新轮播。
我开始思考:是什么导致了自动轮播图控件每次切回来都会从头开始轮播?
以下是我的排查过程:
一开始,我怀疑我的轮播图自动轮播的逻辑写的有问题,返回查看,发现与另一个项目的是一样的,那就可以排除这个原因
进而,我开始思考轮播图的加载时机。我的轮播图数据是网络数据,而请求轮播图网络数据是在Fragment的onViewCreated()生命周期方法中执行的。也就是说与Fragment的生命周期方法调用时机有关。
1.FragmentContainerView+BottomNavigationView切换Fragment时的生命周期调用情况:
我开始在Fragment的各个生命周期方法中打Log,然后发现,当我通过底部导航栏切换FragmentContainerView中的fragment时,被切换的Fragment除了首页的Fragment只会立即执行onPause()->onStop()->onDestroyView(),其他的都会立即执行onPause()->onStop()->onDestroyView(onDestory(),当我再切换回来之后,会执行onCreatedView()->onViewCreated()。也就是说每当我切换回去之后轮播图都会再重新请求一次数据,从而导致了每次切换回轮播图界面轮播图都会重新开始轮播。
其他Fragment被切换后的生命周期调用情况
重新切回其他Fragment后的生命周期调用情况
2.ViewPager2+BottomNavigationView切换Fragment的生命周期调用情况:
而后,我又返回去查看另一个项目(即使用ViewPager2+BottomNavigationView的)的界面切换导致的Fragment生命周期方法的变化。发现:当通过BottomNavigationView切换fragment时,被切换的fragment只会执行onPause(),但并不会立即执行onStop(),而是等待一段时间后或当用户退出应用才会调用onStop()方法;且当切回去时,也不会重新执行onCreatedView()->onViewCreated(),onCreatedView()->onViewCreated()只有当Fragment首次被展示时才会调用。故使用ViewPager2的项目中切换Fragment不会导致轮播图数据重新加载而导致轮播图重新轮播。
Fragment被切换后生命周期调用情况
Fragment被切回来后生命周期调用情况
3.ViewPager+BottomNaviagtionView切换Fragment的生命周期调用情况:
而后,我又研究了一下ViewPager+BottomNaviagtionView切换fragment的生命周期调用情况。ViewPager有预加载功能,比较复杂一点
同样是只有三个页面(Title,Leaderboard,Register),由BottomNavigationView控制切换:
初次加载时,ViewPager会预加载其临近的一个Fragment,在这个例子中,就是显示首页Title页面时也预加载了Leaderboard页面,生命周期调用如下:
当我从Title切换到临近的Leaderboard页面时,被切换的Title页面的生命周期没有任何变化,切换后的Leaderboard页面也没有任何变化(因为其已经被预加载过了),而此时,与Leaderboard临近的Register页面也会被预加载,生命周期调用如下:
而当我从Leaderboard页面切换到第三个页面Register时,Leaderboard页面和Register页面的生命周期都不会发生变化,但第一个页面Title会被销毁,生命周期调用如下:
同样的,当我从第三个页面Register切换到第二个页面Leaderboard时,第一个页面Title又会被重新加载,生命周期调用如下:
当我再从第二个页面Leaderboard切换到第一个界面时,第三个页面Register又会被销毁,生命周期方法调用如下:
由上述测试我们可以看到ViewPager的预加载机制和页面销毁机制,且当界面上只有三个页面时,除非用户退出界面,否则第二个页面是永远不会销毁的。
4.总结:
(1)FragmentContainerView+BottomNavigationView:
既没有预加载机制也没有缓存机制,每切换一次,被切换的Fragment就会被销毁,下次切回来要重新创建View:onCreatedView()->onViewCreated()
(2)ViewPager2+BottomNavigationView:
默认是没有预加载机制的,除非我们手动调用viewPager.offscreenPageLimit 属性进行设置,否则默认是没有预加载(其预加载与ViewPager的预加载机制还不太一样,预加载的界面不会调用onResume()渲染界面,而ViewPager的预加载界面是直接渲染好的),但是默认有缓存机制,当页面被加载到界面上后不会销毁(除非用户退出界面),只会暂停onPause,再次回来时直接onResume()
(3)ViewPager+BottomNavigationView:
默认有预加载机制,会预加载临近的左右页面。也有默认的缓存机制,但与ViewPager2的不同,当从本页面切换到间隔一个的页面,本页面就会被销毁(如从1切换到3时,1就会被销毁)。