當(dāng)友盟統(tǒng)計(jì)遇上Fragment

先描述問題:筆者需要完善項(xiàng)目中的友盟統(tǒng)計(jì)功能,主要是Fragment頁面的處理,下面將分三種方式

建議先看完友盟官方集成統(tǒng)計(jì)分析的參考鏈接,再看本文。

http://dev.umeng.com/analytics/android-doc/integration#1

首先在講三種方式之前我將Umeng的統(tǒng)計(jì)分析代碼獨(dú)立成一個(gè)工具類:

public class UmengUtils {

    /**
     * 開始跳轉(zhuǎn)統(tǒng)計(jì)
     */
    public static void startStatistics(Class aClass) {
        MobclickAgent.onPageStart(aClass.getName());
        Log.d("umeng", aClass.getName() + "開始統(tǒng)計(jì)");
    }

    /**
     * 結(jié)束跳轉(zhuǎn)統(tǒng)計(jì)
     */
    public static void endStatistics(Class aClass) {
        MobclickAgent.onPageEnd(aClass.getName());
        Log.d("umeng", aClass.getName() + "結(jié)束統(tǒng)計(jì)");
    }
}

1. Fragment存在于Activity中,通過hide和show的方式分別顯示和隱藏,關(guān)鍵在于監(jiān)聽Fragment#onHiddenChanged(boolean hidden)

在筆者的項(xiàng)目中,首頁MainActivity布局是標(biāo)準(zhǔn)BottomBar點(diǎn)擊切換顯示/隱藏Fragment,存在一個(gè)FrameLayout容器,底部三個(gè)按鈕點(diǎn)擊展示不同的Fragment(A,B,C)

如下為主界面布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    ...

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    ...

</RelativeLayout>

如下為MainActivity文件:

public class MainActivity extends BaseActivity {

    ...
    @BindView(R.id.container)
    FrameLayout container;

    public SparseArray<Fragment> mFragmentsCache = new SparseArray<Fragment>(3);    // Fragment列表
    private int mLastCheckedId = -1;// 記錄最后點(diǎn)擊的Fragment的id
    private FragmentTransaction transaction;
    
    @Override
    protected void onResume() {
        super.onResume();
        MobclickAgent.onResume(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        MobclickAgent.onPause(this);
    }

    /**
     * 主頁fragment切換
     *
     * @param fragmentId 下導(dǎo)航欄標(biāo)簽
     */
    private void switchFragment(int fragmentId) {
        if (mLastCheckedId == fragmentId)
            return;
        transaction = getSupportFragmentManager().beginTransaction();
        Fragment lastFragment = mFragmentsCache.get(mLastCheckedId);
        if (lastFragment != null) {
            transaction.hide(lastFragment);
        }
        Fragment fragment = mFragmentsCache.get(fragmentId);
        if (fragment == null) {
            switch (fragmentId) {
                case 0:
                    fragment = new AFragment();
                    break;
                case 1:
                    fragment = new BFragment();
                    break;
                case 2:
                    fragment = new CFragment();
                    break;
                default:
                    break;
            }
            mFragmentsCache.put(fragmentId, fragment);
            transaction.add(R.id.container, fragment).commit();
        } else {
            transaction.show(fragment).commit();
        }
        mLastCheckedId = fragmentId;
    }

    ...
}

MainActivity中對(duì)Fragment的處理:

  1. 采用SparseArray將Fragment對(duì)象緩存起來,采用空間換時(shí)間的策略。
  2. 切換Fragment時(shí),若Fragment未初始化,則將通過FragmentTransaction#add()添加。
  3. 切換Fragment時(shí),若Fragment已初始化,則將通過FragmentTransaction#hide()隱藏上一個(gè)Fragment,F(xiàn)ragmentTransaction#show()顯示當(dāng)前Fragment。

接下來看看Fragment的生命周期變化情況:

  1. 初始化————————進(jìn)入MainActivity,初始化AFragment,當(dāng)前AFragment可見

     03-01 10:44:18.608 AFragment----onAttach
     03-01 10:44:18.608 AFragment----onCreate
     03-01 10:44:18.608 AFragment----onCreateView
     03-01 10:44:18.649 AFragment----onActivityCreated
     03-01 10:44:18.650 AFragment----onStart
     03-01 10:44:18.653 AFragment----onResume
    

    注意:這種情況下,AFragment需要在以上的生命周期回調(diào)方法中開始友盟統(tǒng)計(jì)

  2. 初始化&切換————————從AFragment切換到BFragment,實(shí)際上是AFragment被hide(),BFragment被add(),當(dāng)前BFragment可見

     03-02 11:04:09.229 BFragment----onAttach
     03-02 11:04:09.230 BFragment----onCreate
     03-02 11:04:09.236 AFragment----onHiddenChanged----false
     03-02 11:04:09.239 BFragment----onCreateView
     03-02 11:04:10.147 BFragment----onActivityCreated
     03-02 11:04:10.147 BFragment----onStart
     03-02 11:04:10.147 BFragment----onResume
    

    注意:可以看到AFragment#onPaused()并沒有回調(diào),這種情況AFragment需要調(diào)用onHiddenChanged來結(jié)束友盟統(tǒng)計(jì),BFragment初始化類似于上面的情況1

  3. 切換————————從BFragment切換回AFragment,實(shí)際上是BFragment被hide(),AFragment被show(),當(dāng)前AFragment可見

     03-02 11:20:25.923 BFragmenton----HiddenChanged----false
     03-02 11:20:25.939 AFragmenton----HiddenChanged----true
    

    注意:可以看到AFragment#onResume()并沒有回調(diào),這種情況AFragment和BFragment都已經(jīng)初始化了,事務(wù)調(diào)用show()和hide()方法時(shí)只會(huì)觸發(fā)onHiddenChanged()回調(diào)。這種情況只需要在onHiddenChanged()中區(qū)分可見,分別處理上一個(gè)可見界面的結(jié)束統(tǒng)計(jì)以及當(dāng)前可見界面的友盟統(tǒng)計(jì)

  4. 跳轉(zhuǎn)————————從AFragment點(diǎn)擊按鈕跳轉(zhuǎn)到CActivity,當(dāng)前CActivity可見

     03-02 11:27:36.090 AFragment----onPause
     03-02 11:27:36.091 BFragment----onPause
     03-02 11:27:36.639 AFragment----onStop
     03-02 11:27:36.639 BFragment----onStop
    

    注意:兩個(gè)頁面的onPaused和onStop都回調(diào)了,雖然當(dāng)前AFragment可見,但是BFragment同樣會(huì)觸發(fā)生命周期回調(diào)。這種情況下,需要具體區(qū)分哪個(gè)頁面是可見的,可見的情況下在onPaused中結(jié)束友盟統(tǒng)計(jì)

  5. 重現(xiàn)————————從CActivity回退,當(dāng)前AFragment可見

     03-02 11:34:22.430 AFragment----onStart
     03-02 11:34:22.430 BFragment----onStart
     03-02 11:34:22.433 AFragment----onResume
     03-02 11:34:22.433 BFragment----onResume
    

    注意:兩個(gè)頁面的onStart和onResume都回調(diào)了,鎖屏和解鎖后觸發(fā)的Fragment生命周期回調(diào)與4-5一致,故歸結(jié)為同一情況。這種情況下,區(qū)分可見,可見的情況下在onResume中開始友盟統(tǒng)計(jì)

以上總共分為四種情況:

處理的時(shí)候需要處理好4種情況,并且防止統(tǒng)計(jì)交錯(cuò)。

如下是代碼,BaseHomeFragment繼承于筆者項(xiàng)目中的BaseFragment,應(yīng)用場景類似的Fragment可以繼承該類:

/**
 *  該基類適合于采用Hide和show分別隱藏和顯示Fragment時(shí)的友盟統(tǒng)計(jì)
 */
public abstract class BaseHomeFragment extends BaseFragment  {

    private static final String TAG = "BaseHomeFragment";

    /**
     * 首次初始化
     */
    private boolean mFirstInit = true;

    /**
     * 是否可見
     */
    private boolean mVisiable;

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Log.d(TAG, "onHiddenChanged----" + this.toString());
        if (!hidden) {
            mVisiable = true;
            UmengUtils.startStatistics(getClass());
        } else {
            mVisiable = false;
            UmengUtils.endStatistics(getClass());
        }
    }

    @Override
    public void onResume() {
        Log.d(TAG, "onResume----" + this.toString());
        super.onResume();
        //若首次初始化,默認(rèn)可見并開啟友盟統(tǒng)計(jì)
        if (mFirstInit) {
            mVisiable = true;
            mFirstInit = false;
            UmengUtils.startStatistics(getClass());
            return;
        }

        //若當(dāng)前界面可見,調(diào)用友盟開啟跳轉(zhuǎn)統(tǒng)計(jì)
        if (mVisiable) {
            UmengUtils.startStatistics(getClass());
        }
    }

    @Override
    public void onPause() {
        Log.d(TAG, "onPause----" + this.toString());
        super.onPause();
        //若當(dāng)前界面可見,調(diào)用友盟結(jié)束跳轉(zhuǎn)統(tǒng)計(jì)
        if (mVisiable) {
            UmengUtils.endStatistics(getClass());
        }
    }
}

2. Fragment與Viewpager聯(lián)動(dòng)的方式,關(guān)鍵在于監(jiān)聽Fragment#setUserVisibleHint(boolean isVisibleToUser)

在筆者的項(xiàng)目中,查詢頁QActivity布局中存在一個(gè)Viewpager為容器配合四個(gè)Fragment(ABCD)使用,下面就不貼布局了,QActivity中適配器之類的代碼也不貼了,大同小異。

接下來看看Fragment的生命周期變化情況:

  1. 初始化————————進(jìn)入QActivity,初始化Viewpager,當(dāng)前AFragment可見

     03-02 13:50:53.913 AFragment----setUserVisibleHint----false
     03-02 13:50:53.913 BFragment----setUserVisibleHint----false
     03-02 13:50:53.913 AFragment----setUserVisibleHint----true
    
     03-02 13:50:53.918 AFragment----onAttach
     03-02 13:50:53.918 AFragment----onCreate
    
     03-02 13:50:53.918 BFragment----onAttach
     03-02 13:50:53.918 BFragment----onCreate
    
     03-02 13:50:53.918 AFragment----onCreateView
     03-02 13:50:53.942 AFragment----onActivityCreated
     03-02 13:50:53.943 AFragment----onStart
     03-02 13:50:53.943 AFragment----onResume
    
     03-02 13:50:53.943 BFragment----onCreateView
     03-02 13:50:53.954 BFragment----onActivityCreated
     03-02 13:50:53.954 BFragment----onStart
     03-02 13:50:53.954 BFragment----onResume
    

    注意:因?yàn)閂iewpager預(yù)加載機(jī)制,BFragment也會(huì)進(jìn)行初始化,注意看setUserVisibleHint()回調(diào)的次數(shù)和結(jié)果,AFragment#setUserVisibleHint()回調(diào)兩次,一次為false一次為true,BFragment#setUserVisibleHint()則回調(diào)一次。AFragment需要在以上的生命周期中開始友盟統(tǒng)計(jì),BFragment則不需要處理。

  2. 初始化&切換————————從AFragment切換到BFragment,當(dāng)前BFragment可見

     03-02 14:29:48.770 CFragment----setUserVisibleHint----false
     03-02 14:29:48.772 AFragment----setUserVisibleHint----false
     03-02 14:29:48.772 BFragment----setUserVisibleHint----true
    
     03-02 14:29:48.802 CFragment----onAttach
     03-02 14:29:48.802 CFragment----onCreate
     03-02 14:29:48.804 CFragment----onCreateView
     03-02 14:29:48.854 CFragment----onActivityCreated
     03-02 14:29:48.854 CFragment----onStart
     03-02 14:29:48.854 CFragment----onResume
    

    注意:AFragment#onPaused()沒有調(diào)用,AFragment需要在setUserVisibleHint()中結(jié)束友盟統(tǒng)計(jì),BFragment類似情況1

  3. 切換————————從BFragment切換到AFragment,當(dāng)前AFragment可見

     03-02 14:52:43.825 BFragment----setUserVisibleHint----false
     03-02 14:52:43.826 AFragment----setUserVisibleHint----true
    

    注意:可以看到AFragment#onResume()并沒有回調(diào),這種情況AFragment和BFragment都已經(jīng)初始化了,Viewpager切換item,F(xiàn)ragment僅回調(diào)setUserVisibleHint(),這種情況只需要在setUserVisibleHint()中區(qū)分可見,分別處理上一個(gè)可見界面的結(jié)束統(tǒng)計(jì)以及當(dāng)前可見界面的友盟統(tǒng)計(jì)

  4. 跳轉(zhuǎn)————————從AFragment跳轉(zhuǎn)到DActivity,當(dāng)前DActivity可見

     03-02 15:08:11.411 AFragment----onPause
     03-02 15:08:11.413 BFragment----onPause
     03-02 15:08:11.414 CFragment----onPause
    
     03-02 15:08:12.259 AFragment----onStop
     03-02 15:08:12.259 BFragment----onStop
     03-02 15:08:12.259 CFragment----onStop
    

    注意:三個(gè)頁面的onPaused和onStop都回調(diào)了,雖然當(dāng)前AFragment可見,但是BFragment和CFragment同樣會(huì)觸發(fā)生命周期回調(diào)。這種情況下,需要具體區(qū)分哪個(gè)頁面是可見的,可見的情況下在onPaused中結(jié)束友盟統(tǒng)計(jì)

  5. 重現(xiàn)————————從DActivity回退,當(dāng)前AFragment可見

     03-02 15:08:51.834 AFragment----onStart
     03-02 15:08:51.834 BFragment----onStart
     03-02 15:08:51.835 CFragment----onStart
    
     03-02 15:08:51.838 AFragment----onResume
     03-02 15:08:51.840 BFragment----onResume
     03-02 15:08:51.840 CFragment----onResume
    

    注意:三個(gè)頁面的onStart和onResume都回調(diào)了,鎖屏和解鎖后觸發(fā)的Fragment生命周期回調(diào)與4-5一致,故歸結(jié)為同一情況。這種情況下,區(qū)分可見,可見的情況下在onResume中開始友盟統(tǒng)計(jì)

如下是代碼,BaseVPFragment繼承于筆者項(xiàng)目中的BaseFragment,應(yīng)用場景類似的Fragment可以繼承該類:

/**
 * 該基類適用于填充在Viewpager中的Fragment,用于友盟統(tǒng)計(jì)
 */
public abstract class BaseVPFragment extends BaseFragment {

    private static final String TAG = "BaseVPFragment";

    /**
     * 是否可見
     */
    private boolean mVisiable;

    /**
     * 是否已經(jīng)開始統(tǒng)計(jì)
     */
    private boolean hasStarted;

    @Override
    public void onResume() {
        Log.d(TAG, this.toString() +  "----onResume");
        super.onResume();
        //若當(dāng)前界面可見,調(diào)用友盟開啟跳轉(zhuǎn)統(tǒng)計(jì)
        if (mVisiable && !hasStarted) {
            UmengUtils.startStatistics(getClass());
        }
    }

    @Override
    public void onPause() {
        Log.d(TAG, this.toString() +  "----onPause");
        super.onPause();
        //若當(dāng)前界面可見,調(diào)用友盟結(jié)束跳轉(zhuǎn)統(tǒng)計(jì)
        if (mVisiable) {
            hasStarted = false;
            UmengUtils.endStatistics(getClass());
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.d(TAG, this.toString() +  "----setUserVisibleHint----" + isVisibleToUser);
        mVisiable = isVisibleToUser;
        if (isVisibleToUser) {
            hasStarted = true;
            UmengUtils.startStatistics(getClass());
        } else {
            if (hasStarted) {
                hasStarted = false;
                UmengUtils.endStatistics(getClass());
            }
        }
    }
}

3. Fragment存在與Activity中,通過replace的方式

這種最簡單了,只需要按照友盟統(tǒng)計(jì)官方的文檔,在onResume()中開始統(tǒng)計(jì),在onPaased()中結(jié)束回調(diào)即可。

總結(jié)

本篇針對(duì)友盟統(tǒng)計(jì),使用Flag和監(jiān)聽獲取到當(dāng)前頁面可見Fragment,同樣適用于Fragment的懶加載。

如有錯(cuò)誤請留言,必修改。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,029評(píng)論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,288評(píng)論 4 61
  • 寄語秋風(fēng)言何事 落葉紛紛念離愁 悠悠笛管聲聲慢 脈脈呢喃句句羞
    星塵夢羽閱讀 213評(píng)論 3 7
  • 1、 那時(shí)候,我還是天宮里叱咤風(fēng)云又豐姿神勇的天蓬元帥。每日里胄甲鮮明,享受尊敬和愛戴。 我最喜歡二郎神,仙界戰(zhàn)神...
    遇見而已閱讀 1,556評(píng)論 46 49
  • 小時(shí)候,盼著過年,一到冬天,就數(shù)著日子,最討厭期末考,最喜歡紅蘿卜,因?yàn)楦柚{如此:"紅蘿卜,咪咪甜,看著...
    昱君文化心理閱讀 806評(píng)論 35 57

友情鏈接更多精彩內(nèi)容