先描述問(wèn)題:筆者需要完善項(xiàng)目中的友盟統(tǒng)計(jì)功能,主要是Fragment頁(yè)面的處理四康,下面將分三種方式
建議先看完友盟官方集成統(tǒng)計(jì)分析的參考鏈接搪搏,再看本文。
首先在講三種方式之前我將Umeng的統(tǒng)計(jì)分析代碼獨(dú)立成一個(gè)工具類:
public class UmengUtils {
/**
* 開(kāi)始跳轉(zhuǎn)統(tǒng)計(jì)
*/
public static void startStatistics(Class aClass) {
MobclickAgent.onPageStart(aClass.getName());
Log.d("umeng", aClass.getName() + "開(kāi)始統(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中闪金,通過(guò)hide和show的方式分別顯示和隱藏,關(guān)鍵在于監(jiān)聽(tīng)Fragment#onHiddenChanged(boolean hidden)
在筆者的項(xiàng)目中,首頁(yè)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);
}
/**
* 主頁(yè)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ì)象緩存起來(lái),采用空間換時(shí)間的策略囱嫩。
- 切換Fragment時(shí)恃疯,若Fragment未初始化,則將通過(guò)FragmentTransaction#add()添加墨闲。
- 切換Fragment時(shí)今妄,若Fragment已初始化,則將通過(guò)FragmentTransaction#hide()隱藏上一個(gè)Fragment损俭,F(xiàn)ragmentTransaction#show()顯示當(dāng)前Fragment蛙奖。
接下來(lái)看看Fragment的生命周期變化情況:
-
初始化————————進(jìn)入MainActivity潘酗,初始化AFragment杆兵,當(dāng)前AFragment可見(jiàn)
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)方法中開(kāi)始友盟統(tǒng)計(jì)
-
初始化&切換————————從AFragment切換到BFragment仔夺,實(shí)際上是AFragment被hide()琐脏,BFragment被add(),當(dāng)前BFragment可見(jiàn)
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()并沒(méi)有回調(diào)缸兔,這種情況AFragment需要調(diào)用onHiddenChanged來(lái)結(jié)束友盟統(tǒng)計(jì)日裙,BFragment初始化類似于上面的情況1
-
切換————————從BFragment切換回AFragment,實(shí)際上是BFragment被hide()惰蜜,AFragment被show()昂拂,當(dāng)前AFragment可見(jiàn)
03-02 11:20:25.923 BFragmenton----HiddenChanged----false 03-02 11:20:25.939 AFragmenton----HiddenChanged----true
注意:可以看到AFragment#onResume()并沒(méi)有回調(diào),這種情況AFragment和BFragment都已經(jīng)初始化了抛猖,事務(wù)調(diào)用show()和hide()方法時(shí)只會(huì)觸發(fā)onHiddenChanged()回調(diào)格侯。這種情況只需要在onHiddenChanged()中區(qū)分可見(jiàn),分別處理上一個(gè)可見(jiàn)界面的結(jié)束統(tǒng)計(jì)以及當(dāng)前可見(jiàn)界面的友盟統(tǒng)計(jì)
-
跳轉(zhuǎn)————————從AFragment點(diǎn)擊按鈕跳轉(zhuǎn)到CActivity财著,當(dāng)前CActivity可見(jiàn)
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è)頁(yè)面的onPaused和onStop都回調(diào)了联四,雖然當(dāng)前AFragment可見(jiàn),但是BFragment同樣會(huì)觸發(fā)生命周期回調(diào)撑教。這種情況下朝墩,需要具體區(qū)分哪個(gè)頁(yè)面是可見(jiàn)的,可見(jiàn)的情況下在onPaused中結(jié)束友盟統(tǒng)計(jì)
-
重現(xiàn)————————從CActivity回退伟姐,當(dāng)前AFragment可見(jiàn)
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è)頁(yè)面的onStart和onResume都回調(diào)了收苏,鎖屏和解鎖后觸發(fā)的Fragment生命周期回調(diào)與4-5一致,故歸結(jié)為同一情況愤兵。這種情況下鹿霸,區(qū)分可見(jiàn),可見(jiàn)的情況下在onResume中開(kāi)始友盟統(tǒng)計(jì)
以上總共分為四種情況:
處理的時(shí)候需要處理好4種情況恐似,并且防止統(tǒng)計(jì)交錯(cuò)杜跷。
如下是代碼,BaseHomeFragment繼承于筆者項(xiàng)目中的BaseFragment,應(yīng)用場(chǎ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;
/**
* 是否可見(jiàn)
*/
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)可見(jiàn)并開(kāi)啟友盟統(tǒng)計(jì)
if (mFirstInit) {
mVisiable = true;
mFirstInit = false;
UmengUtils.startStatistics(getClass());
return;
}
//若當(dāng)前界面可見(jiàn),調(diào)用友盟開(kāi)啟跳轉(zhuǎn)統(tǒng)計(jì)
if (mVisiable) {
UmengUtils.startStatistics(getClass());
}
}
@Override
public void onPause() {
Log.d(TAG, "onPause----" + this.toString());
super.onPause();
//若當(dāng)前界面可見(jiàn),調(diào)用友盟結(jié)束跳轉(zhuǎn)統(tǒng)計(jì)
if (mVisiable) {
UmengUtils.endStatistics(getClass());
}
}
}
2. Fragment與Viewpager聯(lián)動(dòng)的方式,關(guān)鍵在于監(jiān)聽(tīng)Fragment#setUserVisibleHint(boolean isVisibleToUser)
在筆者的項(xiàng)目中,查詢頁(yè)QActivity布局中存在一個(gè)Viewpager為容器配合四個(gè)Fragment(ABCD)使用葛闷,下面就不貼布局了憋槐,QActivity中適配器之類的代碼也不貼了,大同小異淑趾。
接下來(lái)看看Fragment的生命周期變化情況:
-
初始化————————進(jìn)入QActivity阳仔,初始化Viewpager,當(dāng)前AFragment可見(jiàn)
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需要在以上的生命周期中開(kāi)始友盟統(tǒng)計(jì)阱飘,BFragment則不需要處理斥杜。
-
初始化&切換————————從AFragment切換到BFragment,當(dāng)前BFragment可見(jiàn)
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()沒(méi)有調(diào)用沥匈,AFragment需要在setUserVisibleHint()中結(jié)束友盟統(tǒng)計(jì)蔗喂,BFragment類似情況1
-
切換————————從BFragment切換到AFragment,當(dāng)前AFragment可見(jiàn)
03-02 14:52:43.825 BFragment----setUserVisibleHint----false 03-02 14:52:43.826 AFragment----setUserVisibleHint----true
注意:可以看到AFragment#onResume()并沒(méi)有回調(diào)高帖,這種情況AFragment和BFragment都已經(jīng)初始化了缰儿,Viewpager切換item,F(xiàn)ragment僅回調(diào)setUserVisibleHint()散址,這種情況只需要在setUserVisibleHint()中區(qū)分可見(jiàn)乖阵,分別處理上一個(gè)可見(jiàn)界面的結(jié)束統(tǒng)計(jì)以及當(dāng)前可見(jiàn)界面的友盟統(tǒng)計(jì)
-
跳轉(zhuǎn)————————從AFragment跳轉(zhuǎn)到DActivity,當(dāng)前DActivity可見(jiàn)
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è)頁(yè)面的onPaused和onStop都回調(diào)了爪飘,雖然當(dāng)前AFragment可見(jiàn)义起,但是BFragment和CFragment同樣會(huì)觸發(fā)生命周期回調(diào)。這種情況下师崎,需要具體區(qū)分哪個(gè)頁(yè)面是可見(jiàn)的默终,可見(jiàn)的情況下在onPaused中結(jié)束友盟統(tǒng)計(jì)
-
重現(xiàn)————————從DActivity回退,當(dāng)前AFragment可見(jiàn)
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è)頁(yè)面的onStart和onResume都回調(diào)了犁罩,鎖屏和解鎖后觸發(fā)的Fragment生命周期回調(diào)與4-5一致齐蔽,故歸結(jié)為同一情況。這種情況下床估,區(qū)分可見(jiàn)含滴,可見(jiàn)的情況下在onResume中開(kāi)始友盟統(tǒng)計(jì)
如下是代碼,BaseVPFragment繼承于筆者項(xiàng)目中的BaseFragment丐巫,應(yīng)用場(chǎng)景類似的Fragment可以繼承該類:
/**
* 該基類適用于填充在Viewpager中的Fragment,用于友盟統(tǒng)計(jì)
*/
public abstract class BaseVPFragment extends BaseFragment {
private static final String TAG = "BaseVPFragment";
/**
* 是否可見(jiàn)
*/
private boolean mVisiable;
/**
* 是否已經(jīng)開(kāi)始統(tǒng)計(jì)
*/
private boolean hasStarted;
@Override
public void onResume() {
Log.d(TAG, this.toString() + "----onResume");
super.onResume();
//若當(dāng)前界面可見(jiàn),調(diào)用友盟開(kāi)啟跳轉(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)前界面可見(jiàn),調(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中谈况,通過(guò)replace的方式
這種最簡(jiǎn)單了勺美,只需要按照友盟統(tǒng)計(jì)官方的文檔,在onResume()中開(kāi)始統(tǒng)計(jì)碑韵,在onPaased()中結(jié)束回調(diào)即可赡茸。
總結(jié)
本篇針對(duì)友盟統(tǒng)計(jì),使用Flag和監(jiān)聽(tīng)獲取到當(dāng)前頁(yè)面可見(jiàn)Fragment祝闻,同樣適用于Fragment的懶加載占卧。
如有錯(cuò)誤請(qǐng)留言,必修改联喘。