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));
}
}
}