注:本文同步發布於微信公眾號:stringwu的互聯網雜談 frament時長統計那些事
頁面停留時長作為應用統計的北極星指標里的重要指標之一,統計用戶在某個頁面的停留時長則變得很重要。而Fragment作為Android中頁面的重要組成部分,其停留時長的統計就顯得非常重要。目前業界能搜索到的方案,主要有兩種方案:
- 業務繼承於某一個特定的
Fragment; - 直接通過
Fragment的生命周期方法來統計頁面的時長;
方案一對於業務的侵入性過高,業務只有接入特定的Fragment,才能統計其時長。方案二適用面太小,只適於於沒有預加載的場景,如果存在預加載行為,則統計出來的時長是不准的。
本文主要根據筆者對Fragment的理解,從對業務侵入性和兼容性角度出來,.
1 Fragment簡介
Activity是 Android的界面組成元素,一個Activity就是一個頁面。而Fragment則允許將Activity拆分成多個完全獨立封裝的可重用的組件,從而構建出靈活的UI界面。目前市場上的多個TAB的UI一般都是通過Fragment去組裝完成的,如某應用渠道的TAB:
具體的Fragment的簡介可參數官方文檔官方文檔 ,本文不再詳細介紹;
2 Fragment的生命周期
Fragmennt不能單獨使用,始終需要依賴於Activity,因此,盡管Fragment擁有自己的生命周期,但還是會受到Activity的生命周期的影響,如Activity被 銷毀后,Fragment也會跟着銷毀。Fragment是 Activity的“寄生蟲”。
Fragment的生命周期可參考圖:
一般在實際應用過程中,只需要對Fragment的關鍵生命周期方法進行復寫就可以:
- onCreateView : 首次繪制
Fragment時會調用這個方法,需要從些方法中返回Fragment的根View; - onActivityCreated :
Fragment所在的Activity啟動完成時回調; - onResume : 當前
Fragment變成可交互狀態時回調; - onPause : 用戶離開
Fragment的回調方法;
甚至於只需要復寫onCreateView就能完成一個Fragment的開發了。
3 Fragment時長統計
3.1 背景
Android中最常用的兩種頁面的形態:Activity和Fragment。Activity做為Android中最原始的頁面,同一時刻只允許有一個Activity處於活躍狀態(onResume),因此Activity頁面的時長可以直接通過Activity的生命周期方法來進行統計:
完整的頁面周期:
- onResume :頁面開始時間;
- onPause: 頁面結束時間;
而Fragment不一樣,Fragment是可以存在預加載和多層嵌套的行為的,同一時刻會有多個Fragment執行了 onResume方法,但真正對於用戶可交互的可能就只有一個(多層嵌套時會有多個),如果單純的使用Fragment的生命周期方法來統計Fragment的頁面時長顯然會造成統計不准。因此需要對Fragment的頁面時長尋找獨立的統計解決方案。
本文不討論對業務侵入性比較大的方案,如自定義的Fragment等方式,只討論對業務侵入性最小的方案。
3.2 方案
備注:本文討論的方案默認是使用support包或者androix的Fragment來進行統計的;
Fragment其本身是有生命周期的, 因此整個方案會基於Fragment的生命周期來做進一步的處理:
- 注冊Fragment的生命周期的監聽
- 通過Fragment的
getUserVisibleHint方法來判斷頁面的可見性 - 維持兩種Fragment的List:當前可見的Fragment List 和 已執行onResume方法的Fragment List;
3.2.1 生命周期的監聽
Fragment的生命周期方法通過在Activity的onResume方法里注冊一個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還是可見的,這顯然是不符合邏輯的;如:
整個頁面由四個一級的Fragment組成,其中標簽為THREAD的fragment嵌入了三個子的Fragment;
如果點擊外層的FOUR tab,則 標簽為EIRST的 fragment的可見性是不會發生變化的(仍是可見的),但實際上,該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存在的預加載行為和多層嵌套的使用功能達到精准統計的功能。
