Fragment可见与隐藏终极解决方案

背景

前一段时间,运营同事发现首页tab pv数据异常,希望我们可以修改下。先介绍下app首页架构:首先底部四个按钮,点击切换tab(fragment),第二个fragment中使用viewPager加载了三个fragment(后续成为内部fragment),而这三个fragment的pv埋点原先都是在setUserVisibleHint()中埋的,而此方法只有在内部fragment切换中才会触发,导致pv极为的少。就此希望调整下fragment可见与隐藏的实现方案,不试不知道,一试发现里面的水很深。。。

fragment基础

思考下activity的pv埋点方式,就可以很轻松知道fragment的pv埋点方式。activity pv是在onResume()和onPause()方法中记录。而我们只需要在BaseFragment中实现类似的方法,命名为onVisible()和onHidden()分别对应onResume()和onPause()。
而想要实现onVisible和onHidden,必须得了解两个方法,
onHiddenChange()和setUserVisibleHint(),源码注释:

/**
 * 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) {
}

 /**
 * Set a hint to the system about whether this fragment's UI is currently visible
 * to the user. This hint defaults to true and is persistent across fragment instance
 * state save and restore.
 *
 * <p>An app may set this to false to indicate that the fragment's UI is
 * scrolled out of visibility or is otherwise not directly visible to the user.
 * This may be used by the system to prioritize operations such as fragment lifecycle updates
 * or loader ordering behavior.</p>
 *
 * <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
 * and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
 *
 * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
 *                        false if it is not.
 */
public void setUserVisibleHint(boolean isVisibleToUser) {
    if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
            && mFragmentManager != null && isAdded()) {
        mFragmentManager.performPendingDeferredStart(this);
    }
    mUserVisibleHint = isVisibleToUser;
    mDeferStart = mState < STARTED && !isVisibleToUser;
} 

简单来说
onHiddenChange(): 触发时机从显示后开始,fragment状态发生改变后执行。使用FragmentTransaction执行add,show,hide方法时触发。

/**
 * fragment 切换
 *
 * @param from from
 * @param to   to
 * @param tag  tag
 */
private synchronized void switchFragment(Fragment from, Fragment to, String tag) {
    if (from == to) {
        return;
    }
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    if (from == null || !from.isAdded()) {
        if (!to.isAdded() && null == manager.findFragmentByTag(tag)) {
            transaction.add(R.id.rly_content_home, to, tag).commitAllowingStateLoss();
        } else {
            transaction.show(to).commitAllowingStateLoss();
        }
    } else {
        if (!to.isAdded() && null == manager.findFragmentByTag(tag)) {
            transaction.hide(from).add(R.id.rly_content_home, to, tag).commitAllowingStateLoss();
        } else {
            transaction.hide(from).show(to).commitAllowingStateLoss();
        }
    }
    mSelectedFragment = to;
}

setUserVisibleHint(): 当fragment中的UI对用户可见时触发,可能会在fragment生命周期之外被调用。(乍听此方法,是可以用于pv的显示时埋点,但后续实验发现仍有问题,因为注释后还有一句话,不保证和生命周期关联)
使用viewPager加载fragment,切换时,执行此方法。

实验分析

目前可以把fragment显示,隐藏分为三种方式,

  1. 使用add,show,hide方式
  2. 使用viewPager加载
  3. 使用viewPager预加载(看后面就知为何单独列出来)

先看图
在这里插入图片描述

activity切换对于前两个:在当前fragment,跳转到其他activity后再返回,fragment所经历的生命周期。
tab切换:同级fragment切换
外部tab切换:外部fragment切换时,内部fragment所经历的生命周期。

终极解决方案如下:

private boolean mIsHidden = false;
private boolean mIsUserVisibleHint = true;

//viewPager内第一次加载fragment,会执行setUserVisibleHint和onResume,字段防止onVisible()执行两遍
private boolean mIsExecuteOnVisible = false;

//viewPager 预加载默认会执行一次setUserVisibleHint,初始第一次都为false,避免执行onHidden方法
private boolean mIsFirstUserVisibleHint = true;

/**
 * 目前只有在底部tab切换到viewPager内fragment时, onVisible()不会执行,其余时机皆可
 */
protected void onVisible(){
    if (TextUtils.isEmpty(TAG)) {
        setTag();
    }
    LogUtils.log("======="+TAG+"===onVisible====");
}

protected void onHidden() {
    if (TextUtils.isEmpty(TAG)) {
        setTag();
    }
    LogUtils.log("======="+TAG+"===onHidden====");
}

@Override
public void onResume() {
    if (getUserVisibleHint() && !mIsHidden && !mIsExecuteOnVisible) {
        onVisible();
    }
    if (mIsExecuteOnVisible) {
        mIsExecuteOnVisible = false;
    }
    super.onResume();
}

@Override
public void onPause() {
    super.onPause();
    if (!mIsHidden && mIsUserVisibleHint) {
        onHidden();
    }
    mIsExecuteOnVisible = false;
}

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) {
        onHidden();
    } else {
        onVisible();
    }
    mIsHidden = hidden;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    mIsUserVisibleHint = isVisibleToUser;
    if (TextUtils.isEmpty(TAG)) {
        setTag();
    }
    if (isVisibleToUser) {
        if (!mIsExecuteOnVisible) {
            mIsExecuteOnVisible = true;
        }
        onVisible();
    } else {
        if (!mIsFirstUserVisibleHint) {
            onHidden();
        }
        mIsFirstUserVisibleHint = false;
    }
}

当然上述方法也不是完全没有问题,目前外部fragment切换时,只会触发外部fragment onVisible()方法,是无法触发到内部fragment的。解决方案也是有的,在触发到外部fragment onVisible()方法时,做逻辑判断,触发内部fragment的onVisible()方法。暂时就没有做这一步逻辑了,感兴趣的,可以自己实现

总结

fragment可见与不可见涉及方法有4个:onResume(), onPause(), onHiddenChange(boolean isHidden), setUserVisibleHint(boolean isVisibleToUser)。因为加载方式的不同,导致fragment加载的生命周期不如activity明确,需要较多逻辑判断,才可完善此功能!

https://github.com/yuhanXie/Yuhan

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值