• Fragment时长统计那些事


    注:本文同步发布于微信公众号:stringwu的互联网杂谈 frament时长统计那些事
    页面停留时长作为应用统计的北极星指标里的重要指标之一,统计用户在某个页面的停留时长则变得很重要。而Fragment作为Android中页面的重要组成部分,其停留时长的统计就显得非常重要。目前业界能搜索到的方案,主要有两种方案:

    • 业务继承于某一个特定的 Fragment
    • 直接通过Fragment的生命周期方法来统计页面的时长;

    方案一对于业务的侵入性过高,业务只有接入特定的Fragment,才能统计其时长。方案二适用面太小,只适于于没有预加载的场景,如果存在预加载行为,则统计出来的时长是不准的。
    本文主要根据笔者对Fragment的理解,从对业务侵入性和兼容性角度出来,.

    1 Fragment简介

    ActivityAndroid的界面组成元素,一个Activity就是一个页面。而Fragment则允许将Activity拆分成多个完全独立封装的可重用的组件,从而构建出灵活的UI界面。目前市场上的多个TAB的UI一般都是通过Fragment去组装完成的,如某应用渠道的TAB:fragment_tab

    具体的Fragment的简介可参数官方文档官方文档 ,本文不再详细介绍;

    2 Fragment的生命周期

    Fragmennt不能单独使用,始终需要依赖于Activity,因此,尽管Fragment拥有自己的生命周期,但还是会受到Activity的生命周期的影响,如Activity被 销毁后,Fragment也会跟着销毁。FragmentActivity的“寄生虫”。
    Fragment的生命周期可参考图:fragment_lifecycle
    一般在实际应用过程中,只需要对Fragment的关键生命周期方法进行复写就可以:

    • onCreateView : 首次绘制Fragment时会调用这个方法,需要从些方法中返回Fragment的根View;
    • onActivityCreated : Fragment所在的Activity启动完成时回调;
    • onResume : 当前Fragment变成可交互状态时回调;
    • onPause : 用户离开Fragment的回调方法;
      甚至于只需要复写onCreateView 就能完成一个Fragment的开发了。

    3 Fragment时长统计

    3.1 背景

    Android中最常用的两种页面的形态:ActivityFragmentActivity做为Android中最原始的页面,同一时刻只允许有一个Activity处于活跃状态(onResume),因此Activity页面的时长可以直接通过Activity的生命周期方法来进行统计:
    完整的页面周期:

    • onResume :页面开始时间;
    • onPause: 页面结束时间;

    Fragment不一样,Fragment是可以存在预加载和多层嵌套的行为的,同一时刻会有多个Fragment执行了 onResume方法,但真正对于用户可交互的可能就只有一个(多层嵌套时会有多个),如果单纯的使用Fragment的生命周期方法来统计Fragment的页面时长显然会造成统计不准。因此需要对Fragment的页面时长寻找独立的统计解决方案。

    本文不讨论对业务侵入性比较大的方案,如自定义的Fragment等方式,只讨论对业务侵入性最小的方案。

    3.2 方案

    备注:本文讨论的方案默认是使用support包或者androixFragment来进行统计的;

    Fragment其本身是有生命周期的, 因此整个方案会基于Fragment的生命周期来做进一步的处理:

    • 注册Fragment的生命周期的监听
    • 通过Fragment的getUserVisibleHint方法来判断页面的可见性
    • 维持两种Fragment的List:当前可见的Fragment List 和 已执行onResume方法的Fragment List;

    3.2.1 生命周期的监听

    Fragment的生命周期方法通过在ActivityonResume方法里注册一个Fragment`的生命周期的回调方法:

    //在Activity onResume时调用
    public void onActivityResume(Activity activity) {
            FragmentActivity fragmentActivity;
            if (!(activity instanceof FragmentActivity)) {
                return;
            }
            fragmentActivity = (FragmentActivity) activity;
            //拿到当前Activity的fragment管理
            FragmentManager manager = fragmentActivity.getSupportFragmentManager();
            manager.registerFragmentLifecycleCallbacks(fragmentcallback, true);
    }
    
    //fragmentcallback
    fragemntcallback = new FragmentManager.FragmentLifecycleCallbacks(){
      ....
       @Override
            public void onFragmentPreAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
                super.onFragmentPreAttached(fm, f, context);
            }
         @Override
            public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
                super.onFragmentResumed(fm, f);
                onFragmentResume(f);
    
            }
            @Override
            public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
                super.onFragmentPaused(fm, f);
                onFragmentPause(f);
    
            }
            ....
    }
    

    3.2.2 可见性判断

    Fragment可见性判断主要是通过getUserVisibleHint方法来判断是否可见的。

    // 可见性判断方法
    boolean curState = fragment.getUserVisibleHint();
    

    如果Fragment没有嵌套的情况,则直接通过其本身的getUserVisibleHint方法就能判断当前页面的可见性,但如果Fragment又嵌入Fragmnent,则只有其本身的getUserVisibleHint方法来判断当前页面的可见性是不够的,会出现外层的Fragment不可见了,但内部的Fragment还是可见的,这显然是不符合逻辑的;如:multi_fragment
    整个页面由四个一级的Fragment组成,其中标签为THREAD的fragment嵌入了三个子的Fragment
    如果点击外层的FOUR tab,则 标签为EIRSTfragment的可见性是不会发生变化的(仍是可见的),但实际上,该fragment已经不可见了。
    因此我们不能简单在通过该Fragment的可见性来判断其页面的真实可见性,需要结合外层Fragment的可见性来判断页面的真实可见性:

     //完成的页面可见性方法判断。
        private boolean isFragmentVisible(Fragment fragment) {
            boolean curState = fragment.getUserVisibleHint();
            //本身不可见就直接返回不可见了
            if (!curState) {
                return false;
            }
            //本身可见情况下,判断父fragment的情况
            Fragment parentFragment = fragment.getParentFragment();
            boolean parentState = true;
            while (parentFragment != null) {
                parentState = parentFragment.getUserVisibleHint();
                if (!parentState) {
                    break;
                }
                parentFragment = parentFragment.getParentFragment();
            }
    
            return parentState;
        }
    

    这个可见性方法生效的前提是子Fragment使用的FragmentManager是通过外层的fragment.getChildFragmentManager()拿到的。这样子Fragment的关系里才会获取到其 ParentFragment

    3.2.3 Fragment遍历

    通过 Fragment的生命周期方法的监听和页面可见性的判断,内部需要维持两个List:

    • 执行了onResume方法的Maps:mFragmentResumeMap;//key:fragment;value:执行onResume时间;
    • 页面可见的Maps:mFragmentStartTimes;// key:fragment;value:页面开始可见时间

    内部通过去遍历这两个Maps来判断页面的事件(进入或者退出)。在有Fragment执行onResume或者onPause时去触发遍历的操作。
    遍历的伪代码为:

     //可见的list应该不只有一个,可能有多个
     //先遍历FragmentResumeMap,判断哪个Fragment变成可见了,加入到可见的List列表里;
     //然后遍历可见List里哪个变成不可见了,然后就开始上报当前结束的列表;
     //onPause时,要移除掉当前的onResume的List;
     Set<Fragment> mResumeFragmentSet = mFragmentResumeMap.keySet();
     //新的可见Fragment:由不可见变成可见
     HashMap<Fragment, Long> newVisibleFragment = new HashMap<>();
     for (Fragment fragment : mResumeFragmentSet) {
         if (!isFragmentVisible(fragment)) {
             continue;
         }
        if (mFragmentStartTimes.containsKey(fragment)) {
             continue;
         }
         Long time = SystemClock.elapsedRealtime();
         //记录下当前Fragment开始可见时间
         newVisibleFragment.put(fragment, time);
         mFragmentStartTimes.put(fragment, time);
    }
    
    //对新的可见Fragment newVisibleFragment 执行页面进入事件的回调;
    ....
    
    //判断由可见变为不可见的Fragment
    Set<Fragment> mVisibleSet = mFragmentStartTimes.keySet();
    HashMap<Fragment, Long> unVisibleFragment = new HashMap<>();
    for (Fragment fragment : mVisibleSet) {
        if (isFragmentVisible(fragment)) {
            continue;
        }
        unVisibleFragment.put(fragment, SystemClock.elapsedRealtime());
    }
    
    //对于从可见变成不可见的fragment 执行页面退出的回调;
    ....
    

    4 总结

    本文通过监听Fragment的生命周期和页面可见性的判断逻辑,提出了一个对于业务侵入性很小的Fragment页面时长的统计方法。Fragment时长的精准统计方案通过在内部的逻辑来兼容Fragment存在的预加载行为和多层嵌套的使用功能达到精准统计的功能。

  • 相关阅读:
    SSM商城项目(二)
    SSM商城项目(一)
    Solr
    Lucene
    TP5分页函数paginate中的each()传参
    微信小程序+php 授权登陆,完整代码
    thinkphp5 + barcode 生成条形码
    微信小程序-获取当前位置和城市名
    PHP查询附近的人及其距离的实现方法
    thinkphp5连接sql server
  • 原文地址:https://www.cnblogs.com/WoodJim/p/13656199.html
Copyright © 2020-2023  润新知