andnroid

Android统计应用使用时间

2019-03-19  本文已影响0人  AnduinLothar

前言

目前大部分应用都需要统计用户每日活跃时间,尤其是类似于趣头条的这种激励式应用。本篇文章是本人之前实现的时长统计,主要利用了ActivityLifecycleCallbacks ,这个类管理当前应用所有Activity生命周期的回调来实现,目前功能已经稳定,时间误差较小。本文的思路是之前看了别人的思路搞出来的,今天分享出来,希望大家共同进步。

主要思路:

Application.ActivityLifecycleCallbacks,该类是一个Application内部有Activity活动时的一个回调,当前其中有Activity的生命周期发生改变时,会回调调用对应的方法,如onActivityCreated(Activity activity, Bundle bundle)。所以在这个接口中,我们可以知道应用内所有Activity的生命周期情况。

  • 1.在应用每次有Activity创建时,统计前一个Activity的开始在前台(onStart)到当前新的Activity在前台的时间差;
  • 2.当一个Activity销毁时,若销毁后前台没有Activity,则记录此时的事件和该Activity可见时的时间差,若前台依然有,则记录销毁后展示的Activity onStart的时间和被销毁的Activity onStart的时间差。
  • 3.以上的时间差进行计算时,要考虑跨天的情况,即应用运行时跨越了0点的情况,我们产品的要求是按照新一天的0点开始记录。

我的数据时保存SP中,以当天0点的毫秒值作为Key值,该值每次获取会有微小的差值,我的做法是 /1000*1000保证每次都相同。
大家可以对照生命周期图来理解。
代码入下:
使用时,在Application中注册。
lifecycleCallbacks = new ActivityLifeCycle();
registerActivityLifecycleCallbacks(lifecycleCallbacks);

public class ActivityLifeCycle implements Application.ActivityLifecycleCallbacks {
    /**
     * 上次检查时间,用于在运行时作为基准获取用户时间
     */
    public static long lastCheckTime = 0;
    /** 前台Activity数量 **/
    private int foregroundActivityCount = 0;
    /**
     * Activity是否在修改配置,
     */
    private boolean isChangingConfigActivity = false;
    /**
     *  应用将要切换到前台
     */
    private boolean willSwitchToForeground = false;
    /**
     * 当前是否在前台
     */
    private boolean isForegroundNow = false;
    /**
     * 上次暂停的Activity信息
     */
    private String lastPausedActivityName;
    private int lastPausedActivityHashCode;
    private long lastPausedTime;
    private long appUseReduceTime = 0;
    /**
     *  每次有Activity启动时的开始时间点
     */
    private long appStartTime = 0L;

    /**
     * 本次统计时,运行的时间
     */
    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);
        //前台没有Activity,说明新启动或者将从从后台恢复
        if (foregroundActivityCount == 0 || !isForegroundNow) {
            willSwitchToForeground = true;
        }else {
            //应用已经在前台,此时保存今日运行的时间。
            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));
        //在这里更新检查时间点,是为了保证从后台恢复到前台,持续计时的准确性。
        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);
        //如果这个Activity实在修改配置,如旋转等,则不保存时间直接返回
        if (activity.isChangingConfigurations()) {
            isChangingConfigActivity = true;
            return;
        }
        //该Activity要进入后台,前台Activity数量-1。
        foregroundActivityCount -= 1;
        //当前已经是最后的一个Activity,代表此时应用退出了,保存时间。
        // 如果跨天了,则从新一天的0点开始计时
        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点的时间点,/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;
    }

    /**
     * 保存今日运行时间,以今日0点的时间作为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));
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读