记录Fragment的显示时长

序言

最近在埋点,需要记录fragment的显示时长。想法很简单,使用我们熟知的fragment的生命周期即可。主需要在onResume记录开始时间,在onPase记录结束时间。相减就可以完成fragment显示时间的记录。但是这是最理想的情况,是在一个activity中只有一个fragment。 fragment不涉及hideshow等方法的情况下。

然后我们实际情况却很复杂,涉及到ViewPager中使用fragment会预加载fragment,此时会有多个fragment处于onResume状态但是真正显示的fragment只有一个,也涉及到hideshow方法的影响,hide是通过将Fragment的View设置为GONE来实现的。会调用onHiddenChanged方法。而不会调用onPause方法。

因此要正确记录Fragment的显示时长,还是很烦的。需要处理各种情况。下面分情况讨论。
在这里插入图片描述

android.support.v4.app.Fragment

下面介绍使用了android.support.v4.app.Fragment事的方法。至于Android X有更好的处理办法。

ViewPager的影响

测试代码如下

package com.vincent.testfragment;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;





/**
 * Created by zhuguohui
 * Date: 2021/6/30
 * Time: 10:16
 * Desc:
 */
public class TestFragment extends Fragment {

    private String title;

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.i("zzz",title+"执行 setUserVisibleHint:isVisibleToUser="+isVisibleToUser);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("zzz",title+"执行 onCreate");
    }


    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable  ViewGroup container, @Nullable  Bundle savedInstanceState) {
        Log.i("zzz",title+"执行 onCreateView");
        View view= inflater.inflate(R.layout.test_fragment,container,false);
        TextView textView= view.findViewById(R.id.tv_title);
        textView.setText(title);
        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.i("zzz",title+"执行 onViewCreated");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.i("zzz",title+"执行 onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("zzz",title+"执行 onResume");
    }


    @Override
    public void onPause() {
        super.onPause();
        Log.i("zzz",title+"执行 onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.i("zzz",title+"执行 onStop");
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Log.i("zzz",title+"执行 onHiddenChanged");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("zzz",title+"执行 onDestroy");
    }
}

package com.vincent.testfragment.adapter;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.util.Log;


import com.vincent.testfragment.TestFragment;

import java.util.List;


/**
 * Created by zhuguohui
 * Date: 2021/6/30
 * Time: 10:16
 * Desc:
 */
public class TestAdapter extends FragmentStatePagerAdapter {

    List<String> titles;

    public TestAdapter(FragmentManager fm, List<String> titles) {
        super(fm);
        this.titles = titles;
    }


    @Override
    public Fragment getItem(int position) {
        Log.i("zzz", "new " + titles.get(position));
        TestFragment testFragment = new TestFragment();
        testFragment.setTitle(titles.get(position));
        return testFragment;
    }

    @Override
    public int getCount() {
        return titles.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return titles.get(position);
    }
}

使用TabLayout+ViewPager测试,代码很简单就不贴出来。

测试日志

初始化ViewPager 打印的日志

2021-06-30 11:05:58.162 21249-21249/? W/nt.testfragmen: Accessing hidden method Landroid/app/LoadedApk;-><init>(Landroid/app/ActivityThread;Landroid/content/pm/ApplicationInfo;Landroid/content/res/CompatibilityInfo;Ljava/lang/ClassLoader;ZZZ)V (greylist-max-o, linking, denied)
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz: new1个fragment
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 setUserVisibleHint:isVisibleToUser=false
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz: new2个fragment
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 setUserVisibleHint:isVisibleToUser=false
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 setUserVisibleHint:isVisibleToUser=true
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onCreate
2021-06-30 11:05:58.486 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onCreate
2021-06-30 11:05:58.487 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onCreateView
2021-06-30 11:05:58.490 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onViewCreated
2021-06-30 11:05:58.491 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onStart
2021-06-30 11:05:58.491 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onResume
2021-06-30 11:05:58.491 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onCreateView
2021-06-30 11:05:58.493 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onViewCreated
2021-06-30 11:05:58.493 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onStart
2021-06-30 11:05:58.493 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onResume


点击第6个fragment执行的日志

2021-06-30 11:07:32.429 21249-21249/com.vincent.testfragment I/zzz: new6个fragment
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 setUserVisibleHint:isVisibleToUser=false
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz: new5个fragment
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz:5个fragment执行 setUserVisibleHint:isVisibleToUser=false
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz: new7个fragment
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz:7个fragment执行 setUserVisibleHint:isVisibleToUser=false
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 setUserVisibleHint:isVisibleToUser=false
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 setUserVisibleHint:isVisibleToUser=true
2021-06-30 11:07:32.430 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 onCreate
2021-06-30 11:07:32.431 21249-21249/com.vincent.testfragment I/zzz:5个fragment执行 onCreate
2021-06-30 11:07:32.431 21249-21249/com.vincent.testfragment I/zzz:7个fragment执行 onCreate
2021-06-30 11:07:32.432 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 onCreateView
2021-06-30 11:07:32.437 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 onViewCreated
2021-06-30 11:07:32.437 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 onStart
2021-06-30 11:07:32.437 21249-21249/com.vincent.testfragment I/zzz:6个fragment执行 onResume
2021-06-30 11:07:32.437 21249-21249/com.vincent.testfragment I/zzz:5个fragment执行 onCreateView
2021-06-30 11:07:32.440 21249-21249/com.vincent.testfragment I/zzz:5个fragment执行 onViewCreated
2021-06-30 11:07:32.440 21249-21249/com.vincent.testfragment I/zzz:7个fragment执行 onCreateView
2021-06-30 11:07:32.443 21249-21249/com.vincent.testfragment I/zzz:7个fragment执行 onViewCreated
2021-06-30 11:07:32.443 21249-21249/com.vincent.testfragment I/zzz:5个fragment执行 onStart
2021-06-30 11:07:32.443 21249-21249/com.vincent.testfragment I/zzz:5个fragment执行 onResume
2021-06-30 11:07:32.443 21249-21249/com.vincent.testfragment I/zzz:7个fragment执行 onStart
2021-06-30 11:07:32.443 21249-21249/com.vincent.testfragment I/zzz:7个fragment执行 onResume
2021-06-30 11:07:33.099 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onPause
2021-06-30 11:07:33.099 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onStop
2021-06-30 11:07:33.102 21249-21249/com.vincent.testfragment I/zzz:2个fragment执行 onDestroy
2021-06-30 11:07:33.102 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onPause
2021-06-30 11:07:33.103 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onStop
2021-06-30 11:07:33.103 21249-21249/com.vincent.testfragment I/zzz:1个fragment执行 onDestroy

分析

在ViewPager中使用Fragment 。onResume方法需要加以判断,onReusme方法会有两种方式被调用。一种是Fragment所在的Activity从后台到前台,一种ViewPager预加载。而此时能判断fragment是否显示就需要通过setUserVisibleHint方法中传递过来的参数。只有在fragment显示的时候,并且调用onResume方法的时候。才去调用相关的逻辑代码。当然在setUserVisibleHint方法中如果参数是true也可以判断。不过这个方法第一次调用的时候比onCreate还要早,此时fragment中需要的参数可能还不完整。建议保留到onResume方法。

show hide的影响

使用如下的代码来显示或隐藏fragment时,fragment中的onPauseonResume等方法不会被调用。
只会调用onHiddenChanged方法。需要在这个方法中接受变量,并保存。用于标识当前的fragment是否可见。

  		FragmentTransaction transaction=fm.beginTransaction();
        transaction.hide(fragemnt);
        transaction.commit();

特别需要注意的是onHiddenChanged方法 默认是空实现,而且该方法只有直接被**FragmentTransaction **影响到的fragment才会调用。如果你是fragment中还有子fragment,需要自己将调用传递下去。对于一般的使用了ViewPager的fragment。我自己写了一个工具类来实现。

/**
 * Created by zhuguohui
 * Date: 2021/6/29
 * Time: 15:26
 * Desc:用于从viewpager中获取正在显示的fragment
 */
public class ViewPagerUtil {

    public static Fragment getCurrentFragment(ViewPager viewPager) {
        if (viewPager == null) {
            return null;
        }
        try {
            Field field = viewPager.getClass().getDeclaredField("mItems");
            field.setAccessible(true);
            ArrayList arrayList = (ArrayList) field.get(viewPager);
            for (int i = 0; i < arrayList.size(); i++) {
                Object item = arrayList.get(i);
                Field fieldPosition = item.getClass().getDeclaredField("position");
                fieldPosition.setAccessible(true);
                int position = (int) fieldPosition.get(item);
                if (position != viewPager.getCurrentItem()) {
                    continue;
                }
                Field field1 = item.getClass().getDeclaredField("object");
                field1.setAccessible(true);
                Object obj = field1.get(item);
                if (obj instanceof Fragment) {
                    return (Fragment) obj;
                }
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

使用也很简单,在被调用了onHiddenChanged方法的fragment中,使用

  @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //为了统计fragment正确的显示时间,需要将hidden状态传递给正在显示的子fragment。
        Fragment currentFragment = ViewPagerUtil.getCurrentFragment(mViewPager);
        if (currentFragment != null) {
            currentFragment.onHiddenChanged(hidden);
        }
    }

解决方案

最后基于以上的事实,我写了个工具类

package com.trs.library.fragment;


import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewParent;

import com.trs.library.bean.TRSChannel;

/**
 * Created by zhuguohui
 * Date: 2021/6/29
 * Time: 14:44
 * Desc:用于记录fragment的显示时长的,可以在ViewPager中使用。也可以自动感知show和hide方法的影响。
 *
 * 需要在fragment的setUserVisibleHint,onPause,onResume,onHiddenChanged onViewCreated方法中。调用该类中的同名方法
 * 以达到记录fragment显示时长的作用
 * <p>
 * 如果是在含有ViewPager的fragment中使用。需要在onHiddenChanged方法中使用下列代码把,hide状态传递给子fragment
 * <pre>
 *  ...fragment.class
 *     public void onHiddenChanged(boolean hidden) {
 *         super.onHiddenChanged(hidden);
 *         //为了统计fragment正确的显示时间,需要将hidden状态传递给正在显示的子fragment。
 *         Fragment currentFragment = ViewPagerUtil.getCurrentFragment(mViewPager);
 *         if (currentFragment != null) {
 *             currentFragment.onHiddenChanged(hidden);
 *         }
 *     }
 * <pre>
 *  该类需要在onCreate()方法之前初始化,因为setUserVisibleHint 方法在onCreate之前执行。
 *  需要提前初始化。最好作为类的成员变量初始化
 *  <pre>
 *      ...fragment.class
 *      class TestFragment extends Fragment{
 *          FragmentShowTimeRecorder timeRecorder=new FragmentShowTimeRecorder();
 *      }
 *  </pre>
 */
public abstract class FragmentShowTimeRecorder {



    //是否是在ViewPager中显示,如果是则需要通过判断setUserVisibleHint传递的参数,来判断当前的显示状态
    //通过这个参数可以优化,页面浏览时长的记录。
    boolean showInViewPager = false;
    boolean isVisibleToUser = false;
    boolean isHide = false;
    boolean checkViewPager = false;//是否检查了当前是在viewPager中显示

    /**
     * 这个方法由ViewPager调用。
     *
     * @param isVisibleToUser
     */
    public final void setUserVisibleHint(boolean isVisibleToUser) {
        this.isVisibleToUser = isVisibleToUser;
        onFragmentShowStateChange(isVisibleToUser);

    }


    public final void onPause() {
        if (isShow()) {
            onFragmentShowStateChange(false);
        }
    }

    private boolean isShow() {
        if (isHide) {
            return false;//viewPager中也可能hide
        }
        if (showInViewPager) {
            return isVisibleToUser;
        }
        return true;
    }


    /**
     * 使用FragmentTransaction的show或者hide方法的时候。会回调改方法。
     * hide的本质就是将fragment中的view设置为GONE。
     * 注意这个状态需要自己手动分发,需要在能接受到该回调的fragment中。将状态传递给子fragment
     *
     * @param hidden
     */
    public final void onHiddenChanged(boolean hidden) {
        isHide = hidden;
        onFragmentShowStateChange(!hidden);
    }


    /**
     * 在viewPager中由于有预加载功能,当调用onResume的时候,其实并没有显示给用户
     * 需要通过isVisibleToUser 来判断是否显示
     *
     * @param view
     */
    public final void onResume(View view) {
        checkViewPager(view);
        if (isShow()) {
            onFragmentShowStateChange(true);
        }
    }

    private void checkViewPager(View view) {
        if (checkViewPager) {
            return;
        }

        if (view == null) {
            return;
        }
        ViewParent parent = view.getParent();
        while (parent != null) {
            if (parent instanceof ViewPager) {
                showInViewPager = true;
                break;
            }
            parent = parent.getParent();
        }
        checkViewPager = true;
    }

    /**
     * 子类重写该方法用于记录fragment给用户展示的时间
     *
     * @param showToUser
     */
    protected abstract void onFragmentShowStateChange(boolean showToUser);

}

使用只需要在你的BaseFragment中相关的生命周期方法中回调对应的方法。需要注意的是。由于setUserVisibleHint被调用的时间早于onCreate所以FragmentShowTimeRecorder的初始化要足够的早。最好作为成员变量初始化。

package com.trs.library.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.trs.library.bean.TRSChannel;
import com.trs.library.rx.bus.RxBus;

import java.util.LinkedList;
import java.util.Queue;

import rx.subscriptions.CompositeSubscription;

/**
 * Created by Vincent Woo
 * Date: 2016/6/8
 * Time: 10:41
 */
public class TRSFragment extends Fragment {

    FragmentShowTimeRecorder timeRecorder = getTimeRecorder();

 
    protected FragmentShowTimeRecorder getTimeRecorder() {
        return null;
    }


    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (timeRecorder != null) {
            timeRecorder.setUserVisibleHint(isVisibleToUser);
        }
    }


    @Override
    public void onResume() {
        super.onResume();
        if (timeRecorder != null) {
            timeRecorder.onResume(getView());
        }
    }


    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (timeRecorder != null) {
            timeRecorder.onHiddenChanged(hidden);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (timeRecorder != null) {
            timeRecorder.onPause();
        }
    }

}

Androidx.Fragment

对于AndroidX 有更优雅的解决办法。只要使fragment有正确的生命周期很多问题就迎刃而解。
直接使用FragmentTransactiosetMaxLifecycle就可以控制Fragment的生命周期。

    @NonNull
    public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
            @NonNull Lifecycle.State state) {
        addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
        return this;
    }

Lifecycle.State 有五中状态

INITIALIZING 初始状态
CREATED 已创建状态
ACTIVITY_CREATED 完全创建,但是没有started
STARTED 创建并启动,可见不可操作
RESUMED 创建启动并可操作

在这里插入图片描述

对于ViewPager,

只需要将FragmentStatePagerAdapter的构造方法中,使用BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT这个参数就可以了。

public class MyAdapter extends FragmentStatePagerAdapter {


    public MyAdapter ( FragmentManager fm) {
        super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
      
    }
 }

使用了这个标志,只会让当前正在显示的fragment被调用onResume。

  /**
     * Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
     * state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
     *
     * @see #FragmentStatePagerAdapter(FragmentManager, int)
     */
    public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;

对于hide和show

在hide一个fragment之前,需要将fragment设置到STARTED状态,这时如果fragment处于RESUMED状态,会调用onPause方法。

 transaction.setMaxLifecycle(from, Lifecycle.State.STARTED);

对于需要show的fragment需要使用。

  transaction.setMaxLifecycle(to, Lifecycle.State.RESUMED);

汇总

通过以上方法,就可以将fragment的显示与隐藏的行为和对于的生命周期方法关联。只需要在对于的生命周期方法中记录事件即可。

总结

在软件开发过程中,并不是一开始就能有最好的处理方法。就像Fragment的setUserHint方法。也是一种打补丁的办法,但是通过不断的思考,最后也能有更优雅的解决办法。所以代码是一时的,思考是永恒的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值