如何正确的判断当前的Fragment是否对用户可见?

前言:写这篇文章的背景是最近做项目处理Fragment中曝光埋点逻辑时,发现当我切换到其他的Fragment时,页面从不可见到可见的过程中,已经隐藏的Fragment的onResume方法仍然会执行,究其原因是因为Fragment的生命周期是跟随其载体Activity的生命周期走,所以这个时候载体MainActivity从onPause->onResume过程中,会触发每个已经存在了的Fragment的生命周期回调方法,显然这并不能满足我们的需求,那么今天来探索一下,到底如何能够准确的判断当前的Fragment是否对用户可见。

一、从onResume()方法说起

简单的onResume原理介绍就不说了,初学者可以自行百度查阅,这里单刀直入的看看感兴趣的东西,首先来看看Fragment中的onResume方法:

/**
 * Called when the fragment is visible to the user and actively running.
 * This is generally
 * tied to {@link Activity#onResume() Activity.onResume} of the containing
 * Activity's lifecycle.
 */
@CallSuper
public void onResume() {
    mCalled = true;
}

注释说明了当fragment对用户可见时会执行,并且和Activity的生命周期onResume方法是绑定在一起的,方法中有个参数mCalled=true,在哪儿会用到呢?别急~

咱们可以创建一个HomeFragment继承Fragment,实现onResume方法:
如何正确的判断当前的Fragment是否对用户可见?_第1张图片
这个是最简单的实现了,那么接下来思考几个问题:

  • 1.super.onResume()可以去掉吗?
    如何正确的判断当前的Fragment是否对用户可见?_第2张图片
    这个应该没什么好解释的吧,重写方法需要执行super.onResume()方法。
  • 2.super.onResume可以放到最后执行吗?
    我们给我们的onResume方法添加几行代码看看:
@Override
  public void onResume() {
    if (true) {
      return;
    }
    super.onResume();
  }

在执行super.onResume()之前,如果我们的逻辑有return的话,会报错吗?其实道理和第一点一样,都是没有成功的执行super.onResume方法,所以肯定是会报错的:
如何正确的判断当前的Fragment是否对用户可见?_第3张图片
可以看到在Fragment类中的2240行报错了,点进去看看:
如何正确的判断当前的Fragment是否对用户可见?_第4张图片
在红框代码之前,因为咱们没有调用super.onResume,所以mCalled并没有置为true,仍然是false,所以就会抛出异常提示我们没有调用到super.onResume方法,所以确定的一点是super.onResume必须得执行,否则就会报错。

二、网上几种常见的判断方式

1.onResume

通过咱们开头的分析和源码注释上的说明,其实简单的通过onResume方法来判断当前Fragment是否对用户可见的场景比较单一,如果只是一个Activity嵌套一个Fragment,那么这种判断方式无可厚非,如果在一个Activity中通过FragmentManager添加了多个Fragment,那么这种方式就不适合了,所有的Fragment的生命周期都依赖于Activity的生命周期,所以已经创建存在的Fragment都会回调此方法,从而给我们带来误导,导致不该执行的逻辑多次执行了,所以要考虑清楚应用场景。

2.onHiddenChanged

这个方法的应用场景也是有限的,只适用在通过FragmentManager添加多个Fragment切换的时候,看看源码中如何定义的:

/**
 * Called when the hidden state (as returned by {@link #isHidden()} of
 * the fragment has changed.  Fragments start out not hidden; this will
 * be called whenever the fragment changes state from that.
 * @param hidden True if the fragment is now hidden, false otherwise.
 */
public void onHiddenChanged(boolean hidden) {
}

这个方法在fragment的隐藏状态发生变化时调用,并且Fragment在开始是不隐藏的,如果Fragment现在是隐藏的返回true,否则返回false

接下来看看onHiddenChanged在哪里被调用了,在FragmentManager类中能找到这个调用的地方:
如何正确的判断当前的Fragment是否对用户可见?_第5张图片
可以看到,当通过FragmentManager的show或者hide方法时,会触发Fragment的onHiddenChanged方法,这里传递的参数是fragment.mHidden,全局搜索一下mHidden在哪儿有赋值:
如何正确的判断当前的Fragment是否对用户可见?_第6张图片
这里可以看到在hideFragment和showFragment方法中,分别对fragment.mHidden属性做了取非操作,然后在onHiddenChanged方法将这个参数回调给我们的调用者,然后在我们自己的Fragment实现里进行判断:

@Override
public void onHiddenChanged(boolean hidden) {
  super.onHiddenChanged(hidden);
  if (!hidden && isAdded()){
    //显示
  }else{
    //隐藏
  }
}

所以这种方式的使用也是存在局限性的,只适用于通过FragmentManager添加多个Fragment时,并且在通过hideFragment或者showFragment切换Fragment时才会调用该方法。

3.setUserVisibleHint

这个方法的应用场景是在FragmentPagerAdapter或FragmentStatePagerAdapter左右滑动切换Fragment时触发的,在Fragment的setUserVisibleHint方法中可以看到在FragmentPagerAdapter和FragmentStatePagerAdapter都调用了该方法。
如何正确的判断当前的Fragment是否对用户可见?_第7张图片
这里以FragmentPagerAdapter为例,看看都在哪些地方调用了方法:
如何正确的判断当前的Fragment是否对用户可见?_第8张图片
首先可以看到在初始化每个fragment的时候,会进行判断,如果初始化的Fragment不是当前展示的那一个的话,就会回调setUserVisibleHint方法参数为false。
如何正确的判断当前的Fragment是否对用户可见?_第9张图片
这个setPrimaryItem方法是在ViewPager中调用的:

mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);

可以看到PagerAdapter设置当前显示的Fragment时,会将上一个Fragment的状态设为false,然后当前即将显示的Fragment的状态设为true,所以setUserVisibleHint这个方法的应用场景也仅限于此了。

三、正确的判断姿势

综合上面各种方法,各有各的应用场景,不能说不对,只不过我们要分清楚我们自己能用到什么方法得出正确的判断,好比武林英雄也需要一件趁手的兵器,这里做一个总结吧:

方法名 应用场景
onResume 适用于单一Activity嵌套单一Fragment的场景,跟随载体Activity的生命周期,能得出正确的判断
onHiddenChanged 适用于通过FragmentManager添加多个Fragment并且在点击切换Fragment的场景,通过FragmentManager的hide和show方法触发,能得出正确的判断
setUserVisibleHint 适用于ViewPager中嵌套Fragment的场景,在PagerAdapter中左右滑动时通过设置当前显示的Fragment会触发该方法,能得出正确的判断

好了,最后如果我们想在一个Activity嵌套多个Fragment的场景下,对于存在且隐藏起来的Fragment,如何做出正确的判断呢?其实我们在通过FragmentManager添加Fragment时可以添加一个Tag,那么我们可以在Fragment的onResume方法中通过拿到当前的Fragment的Tag和自己的Tag进行对比,如果是同一个Tag,即可认为当前onResume显示的是自身,如果不是的话,则说明是其他的Fragment显示,只是由于Activity的onResume方法执行了而触发了自己的onResume的执行,这样就能解决开头我们提到的这个问题。

好了,希望上面这些对大家有帮助~

你可能感兴趣的:(android开发,技术)