Android統(tǒng)計(jì)應(yīng)用使用時(shí)間

前言

目前大部分應(yīng)用都需要統(tǒng)計(jì)用戶每日活躍時(shí)間,尤其是類似于趣頭條的這種激勵(lì)式應(yīng)用。本篇文章是本人之前實(shí)現(xiàn)的時(shí)長(zhǎng)統(tǒng)計(jì),主要利用了ActivityLifecycleCallbacks ,這個(gè)類管理當(dāng)前應(yīng)用所有Activity生命周期的回調(diào)來(lái)實(shí)現(xiàn),目前功能已經(jīng)穩(wěn)定,時(shí)間誤差較小。本文的思路是之前看了別人的思路搞出來(lái)的,今天分享出來(lái),希望大家共同進(jìn)步。

主要思路:

Application.ActivityLifecycleCallbacks,該類是一個(gè)Application內(nèi)部有Activity活動(dòng)時(shí)的一個(gè)回調(diào),當(dāng)前其中有Activity的生命周期發(fā)生改變時(shí),會(huì)回調(diào)調(diào)用對(duì)應(yīng)的方法,如onActivityCreated(Activity activity, Bundle bundle)。所以在這個(gè)接口中,我們可以知道應(yīng)用內(nèi)所有Activity的生命周期情況。

  • 1.在應(yīng)用每次有Activity創(chuàng)建時(shí),統(tǒng)計(jì)前一個(gè)Activity的開始在前臺(tái)(onStart)到當(dāng)前新的Activity在前臺(tái)的時(shí)間差;
  • 2.當(dāng)一個(gè)Activity銷毀時(shí),若銷毀后前臺(tái)沒(méi)有Activity,則記錄此時(shí)的事件和該Activity可見(jiàn)時(shí)的時(shí)間差,若前臺(tái)依然有,則記錄銷毀后展示的Activity onStart的時(shí)間和被銷毀的Activity onStart的時(shí)間差。
  • 3.以上的時(shí)間差進(jìn)行計(jì)算時(shí),要考慮跨天的情況,即應(yīng)用運(yùn)行時(shí)跨越了0點(diǎn)的情況,我們產(chǎn)品的要求是按照新一天的0點(diǎn)開始記錄。

我的數(shù)據(jù)時(shí)保存SP中,以當(dāng)天0點(diǎn)的毫秒值作為Key值,該值每次獲取會(huì)有微小的差值,我的做法是 /1000*1000保證每次都相同。
大家可以對(duì)照生命周期圖來(lái)理解。
代碼入下:
使用時(shí),在Application中注冊(cè)。
lifecycleCallbacks = new ActivityLifeCycle();
registerActivityLifecycleCallbacks(lifecycleCallbacks);

public class ActivityLifeCycle implements Application.ActivityLifecycleCallbacks {
    /**
     * 上次檢查時(shí)間,用于在運(yùn)行時(shí)作為基準(zhǔn)獲取用戶時(shí)間
     */
    public static long lastCheckTime = 0;
    /** 前臺(tái)Activity數(shù)量 **/
    private int foregroundActivityCount = 0;
    /**
     * Activity是否在修改配置,
     */
    private boolean isChangingConfigActivity = false;
    /**
     *  應(yīng)用將要切換到前臺(tái)
     */
    private boolean willSwitchToForeground = false;
    /**
     * 當(dāng)前是否在前臺(tái)
     */
    private boolean isForegroundNow = false;
    /**
     * 上次暫停的Activity信息
     */
    private String lastPausedActivityName;
    private int lastPausedActivityHashCode;
    private long lastPausedTime;
    private long appUseReduceTime = 0;
    /**
     *  每次有Activity啟動(dòng)時(shí)的開始時(shí)間點(diǎn)
     */
    private long appStartTime = 0L;

    /**
     * 本次統(tǒng)計(jì)時(shí),運(yùn)行的時(shí)間
     */
    private long runTimeThisDay = 0L;

    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {
        KLog.d("onActivityCreated" + getActivityName(activity));
    }

    @Override
    public void onActivityStarted(Activity activity) {
        KLog.d("onActivityStarted " + getActivityName(activity) + " " + foregroundActivityCount);
        //前臺(tái)沒(méi)有Activity,說(shuō)明新啟動(dòng)或者將從從后臺(tái)恢復(fù)
        if (foregroundActivityCount == 0 || !isForegroundNow) {
            willSwitchToForeground = true;
        }else {
            //應(yīng)用已經(jīng)在前臺(tái),此時(shí)保存今日運(yùn)行的時(shí)間。
            runTimeThisDay  = System.currentTimeMillis() - appStartTime;
            lastCheckTime = System.currentTimeMillis();
            saveTodayPlayTime(activity, runTimeThisDay);
        }
        appStartTime = System.currentTimeMillis();
        if (isChangingConfigActivity) {
            isChangingConfigActivity = false;
            return;
        }
        foregroundActivityCount += 1;
    }

    @Override
    public void onActivityResumed(Activity activity) {
        KLog.d("onActivityResumed" + getActivityName(activity));
        //在這里更新檢查時(shí)間點(diǎn),是為了保證從后臺(tái)恢復(fù)到前臺(tái),持續(xù)計(jì)時(shí)的準(zhǔn)確性。
        lastCheckTime = System.currentTimeMillis();
        addAppUseReduceTimeIfNeeded(activity);
        if (willSwitchToForeground && isInteractive(activity)) {
            isForegroundNow = true;
            KLog.d( "switch to foreground");
        }
        if (isForegroundNow) {
            willSwitchToForeground = false;
        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
        KLog.d("onActivityPaused" + getActivityName(activity));
        lastPausedActivityName = getActivityName(activity);
        lastPausedActivityHashCode = activity.hashCode();
        lastPausedTime = System.currentTimeMillis();
    }

    @Override
    public void onActivityStopped(Activity activity) {
        KLog.d("onActivityStopped" + getActivityName(activity));
        addAppUseReduceTimeIfNeeded(activity);
        //如果這個(gè)Activity實(shí)在修改配置,如旋轉(zhuǎn)等,則不保存時(shí)間直接返回
        if (activity.isChangingConfigurations()) {
            isChangingConfigActivity = true;
            return;
        }
        //該Activity要進(jìn)入后臺(tái),前臺(tái)Activity數(shù)量-1。
        foregroundActivityCount -= 1;
        //當(dāng)前已經(jīng)是最后的一個(gè)Activity,代表此時(shí)應(yīng)用退出了,保存時(shí)間。
        // 如果跨天了,則從新一天的0點(diǎn)開始計(jì)時(shí)
        if (foregroundActivityCount == 0) {
            isForegroundNow = false;
            KLog.d("switch to background (reduce time[" + appUseReduceTime + "])");
            if (getTodayStartTime() > appStartTime){
                runTimeThisDay = System.currentTimeMillis() - getTodayStartTime();
            }else {
                runTimeThisDay = System.currentTimeMillis() - appStartTime;
            }
            saveTodayPlayTime(activity, runTimeThisDay);
            lastCheckTime = System.currentTimeMillis();
            KLog.d("run time  :"  + runTimeThisDay);
        }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
        KLog.d("onActivitySaveInstanceState" + getActivityName(activity));
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        KLog.d("onActivityDestroyed" + getActivityName(activity));
    }

    private void addAppUseReduceTimeIfNeeded(Activity activity) {
        if (getActivityName(activity).equals(lastPausedActivityName) && activity.hashCode() == lastPausedActivityHashCode) {
            long now = System.currentTimeMillis();
            if (now - lastPausedTime > 1000) {
                appUseReduceTime += now - lastPausedTime;
            }
        }
        lastPausedActivityHashCode = -1;
        lastPausedActivityName = null;
        lastPausedTime = 0;
    }

    private boolean isInteractive(Context context) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
            return pm.isInteractive();
        } else {
            return pm.isScreenOn();
        }
    }

    private String getActivityName(final Activity activity) {
        return activity.getClass().getCanonicalName();
    }

    /**
     * 獲取今日0點(diǎn)的時(shí)間點(diǎn),/1000*1000保證每次取值相同。
     * @return
     */
    private long getTodayStartTime(){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        long time = calendar.getTimeInMillis()/1000*1000;
        return time;
    }

    /**
     * 保存今日運(yùn)行時(shí)間,以今日0點(diǎn)的時(shí)間作為Key值。
     * @param context
     * @param time
     */
    private void saveTodayPlayTime(Context context, long time){
        long todayTime = PreferenceUtil.getLongValue(context,PreferenceUtil.SP_NAME,String.valueOf(getTodayStartTime()) , 0);
        KLog.d("today :" + String.valueOf(getTodayStartTime()) + " : time : " + todayTime);
        KLog.d("today totalTime:" +time +" :" + (todayTime + time));
        PreferenceUtil.putLongValue(context,PreferenceUtil.SP_NAME,String.valueOf(getTodayStartTime()), todayTime+time);
        long yesterdayTime = PreferenceUtil.getLongValue(context,PreferenceUtil.SP_NAME_INJOY, String.valueOf(getTodayStartTime() - Constants.ONE_DAY) , 0);
        //清除前一天的值
        if ( yesterdayTime > 0){
            PreferenceUtil.removeData(context,PreferenceUtil.SP_NAME_INJOY,
                    String.valueOf(getTodayStartTime() - Constants.ONE_DAY));
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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