先描述問題:筆者需要完善項(xiàng)目中的友盟統(tǒng)計(jì)功能,主要是Fragment頁面的處理,下面將分三種方式
建議先看完友盟官方集成統(tǒng)計(jì)分析的參考鏈接,再看本文。
首先在講三種方式之前我將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的處理:
- 采用SparseArray將Fragment對(duì)象緩存起來,采用空間換時(shí)間的策略。
- 切換Fragment時(shí),若Fragment未初始化,則將通過FragmentTransaction#add()添加。
- 切換Fragment時(shí),若Fragment已初始化,則將通過FragmentTransaction#hide()隱藏上一個(gè)Fragment,F(xiàn)ragmentTransaction#show()顯示當(dāng)前Fragment。
接下來看看Fragment的生命周期變化情況:
-
初始化————————進(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ì)
-
初始化&切換————————從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
-
切換————————從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ì)
-
跳轉(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ì)
-
重現(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的生命周期變化情況:
-
初始化————————進(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則不需要處理。
-
初始化&切換————————從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
-
切換————————從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ì)
-
跳轉(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ì)
-
重現(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ò)誤請留言,必修改。