sponsored links

Android:Fragment与导航栏的“懒加载”

2018
写在前面:这篇文章主要分析导航栏,也就是ViewPager+Fragment+FragmentPagerAdapter的懒加载模式,重点还是Fragment生命周期的应用。
如果对如何使用导航栏还不太了解,可以看看 底部导航栏标签切换的实现 这篇文章。


(一)Fragment生命周期

大家都知道 Fragment 是绑定 Activity 的,不过,很多人会忽视了它们的生命周期也是会一定程度上同步的。下面表格的场景是在 Activity 内部实例化 Fragment,Fragment 与 Activity 生命周期之间的关系。

Activity Fragment
onCreate() onAttach(), onCreateView(), onCreateView, onActivityCreate()
onStart() onStart()
onResume() onResume()
onPause() onPause()
onStop() onStop()
onDestroy() onDestroyView(), onDestroy(), onDetach()

大多数时候我们将实例化 Fragment 放在 Activity.onCreate() 中,如new一个实例对象。不难理解 Fragment 在Activity.onCreate()之后才开始一系列生命周期方法的调用。而其他方法执行顺序会是下面这样:

Activity.onStart()-->Fragment.onStart()
Activity.onResume()-->Fragment.onResume()
Fragment.onPause()-->Activity.onPause()
Fragment.onStop()-->Activity.onStop()
Fragment.onDestroyView(),onDestroy(),onDetach()-->Activity.onDestroy()

Fragment 依赖于 Activity,故而,Fragment创建于Acivity之后,销毁于Acrivity之前。

ViewPager中Fragment的生命周期

结合 ViewPager 后,页面切换会有哪些生命周期方法的调用?

看过上一篇博文的朋友应该都比较清楚了,这里就再不厌其烦地给没看过的朋友讲解一下。

单独使用Fragment没什么好说的,调用的方法和上表一样,会和 Activity 同步。而结合 ViewPager 取决于选择哪一种适配器,现在大多数使用 FragmentPagerAdapter或者 FragmentPagerStateAdapter,两者的区别是到底会不会回收Fragment的内存。

前者只回收View,除非内存不足,否则不会销毁加载好的Fragment;后者为达到节省内存的目的,对于不在当前页面左右两边的其他Page,将会销毁其Fragment,只保留最多三个页面在内存中。

针对 FragmentPagerAdapter 的测试

Fragment--> 代表的是第一个Fragment 的生命周期,Activity--> 代表与之关联的活动的生命周期:

// 程序启动,初始化活动
I/MyTest: Activity-->onCreate()
I/MyTest: Activity-->onStart()
          Activity-->onResume()
// 加载Fragment
I/MyTest: Fragment-->getUserVisibleHint:true
I/MyTest: Fragment-->setUserVisibleHint(false)
          Fragment-->getUserVisibleHint:false
          Fragment-->setUserVisibleHint(true)
I/MyTest: Fragment-->onAttach()
          Fragment-->onCreate()
I/MyTest: Fragment-->onCreateView()
I/MyTest: Fragment-->onActivityCreated()
          Fragment-->onStart()
          Fragment-->onResume()
//点击第三个页面
I/MyTest: Fragment-->getUserVisibleHint:true
          Fragment-->setUserVisibleHint(false)
I/MyTest: Fragment-->onPause()
I/MyTest: Fragment-->onStop()
          Fragment-->onDestroyView() // View被回收,但是内存没有回收
//返回第一个页面
I/MyTest: Fragment-->getUserVisibleHint:false
          Fragment-->setUserVisibleHint(false)
          Fragment-->getUserVisibleHint:false
          Fragment-->setUserVisibleHint(true)
          Fragment-->onCreateView()
          Fragment-->onActivityCreated()
          Fragment-->onStart()
          Fragment-->onResume()
//销毁活动,与Activity生命周期同步
I/MyTest: Fragment-->onPause()
          Activity-->onPause()
I/MyTest: Fragment-->onStop()
I/MyTest: Activity-->onStop()
I/MyTest: Fragment-->onDestroyView()
          Fragment-->onDestroy()
          Fragment-->onDetach()
          Activity-->onDestroy()

发现

  • 切换到不是 当前页面左右两边的页面 时,当前页面会被回收View,但不被销毁,原因是Adapter默认只完整加载左右两边页面。
  • 切换到 当前页面左右两边的页面 时,只会调用setUserVisibleHint(true)
  • Fragment 的生命周期与Activity息息相关。经过另外的测试,如果Activity变得不可见(比如开启新活动),那么所有加载好的Fragment会和Activity一起执行onPause()onStop() ,返回时又会一起执行onStart()onResume(),这就是生命周期的同步。
  • 初始化的时候setUserVisibleHint()比Fragment任何生命周期方法要早调用。而从较远页面切换回来setUserVisibleHint()也至少比onCreateView()先执行,如页面3到页面1。

还是贴张图表示这个过程:

Android:Fragment与导航栏的“懒加载”

(二)懒加载在不同业务场景下的实现

有了上面的知识储备,灵活地使用“懒加载”也不难了,根据业务需要,我们可以设置不同的加载方式。

基本方法

使用 setUserVisibleHint() ,在进入一个页面之前我们肯定会需要调用这个方法。

场景1

只需要拉取一次数据,但是很可能整个活动期间都不会切换到这个页面,所以希望省点流量,只有切换到才加载,而且只有一次。写法如下:

private boolean hasLoad;

@Override
public void setUserVisibleHint(boolean isVisible){
    //设置为可见并且没有加载过数据的时候进行网络请求
    if(isVisible && (!hasLoad)){
        /*
         * 网路请求数据
         */
         hasLoad = true;
    }
    super.setUserVisibleHint(isVisible);
}

场景2

每次进入页面都要拉取数据,但只要加载数据成员不需要更新UI,下面这种写法就可以了。

@Override
public void setUserVisibleHint(boolean isVisible){
    if(isVisible){
        /*
         * 网路请求数据
         */
    }
    super.setUserVisibleHint(isVisible);
}

场景3

每次进入都需要拉取数据,数据加载后要更新UI。

为什么和第一种区分,留意“发现”的最后一点的两种情况,此时Fragment还没有加载View实例,而更新UI必须要确保已经获取View实例。那不妨使用一个boolean变量来标识获取View的状态,确保View获取了就加载,反之不加载。很有道理,不过还有缺陷,因为等View加载完,我们已经不会调用setUserVisibleHint() 了,所以还要在View获取后完成一次补充加载

下面这种写法就能确保无论是初始化、还是从较远页面切换过来或者从左右页面切换过来,都能马上拉取数据。大家可以直接用这种格式哦。

private boolean hasView;

@Override
public void setUserVisibleHint(boolean isVisible){
    if(isVisible && hasView){
        /*
         * 网路请求数据 + 更新UI
         */
    }
    super.setUserVisibleHint(isVisible);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_layout_1, container,false);    //动态加载布局
    /*
     * 使用 view.findViewById(long resourceId)来获取View实例
     */
    hasView = true;   //View加载好了
    if(getUserVisibleHint()){
        /*
         * 数据请求 + UI更新
         */
    }
    return view;
}

@Override
public void onDestroyView(){
    hasView=false;    // View被回收了
    super.onDestroyView();
}

留意上面onCreateView() 中的 if 代码块,这就是我们说的,如果是没有View的情况下不能直接使用 setUserVisibleHint() 加载,需要之后采取一个补充加载


(三)强调ViewPager中四种不同的Fragment状态

  1. 当前页面:Fragment 生命周期执行到onResume(),可见性getUserVisibleHint()true
  2. 预加载页面:位于当前页面左右两边,Fragment 生命周期执行到onResume()
    可见性getUserVisibleHint()false
  3. 曾经加载过但是被回收了View的页面:肯定不在当前页面左右,Fragment 生命周期执行到onCreate()getUserVisibleHint()false
  4. 从来没有加载过的页面:也肯定不在当前页面左右,所有方法都还没调用,getUserVisibleHint()false


暂时写到这里,关于数据懒加载我们也了解得差不多了,如有不正之处欢迎指正。

Tags: