code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群

作者:淮左明都
链接:https://www.jianshu.com/p/ec8627fe3fd7
声明:本文已获淮左明都授权发表,转发等请联系原作者授权

闲谈

好久没更新了,前两个月就定了标题,现在才开始动手写。其实之前觉得这种获取App累积时长的方法(以下内容会介绍到)不唯一,可能有很多种,所以想收集多点资料,后来看到腾讯和360都是这么实现的,那么现在就开始介绍一下这个需求。

相信做游戏sdk开发的最近经常有这个需求,为什么是游戏sdk开发需要?而且为什么是最近?

首先回答第一个问题,这个需求主要是要限制用户对于app的使用时长,一般较为常见的是限制游戏游玩时长,其次,很少有Android应用有统计使用时长这一个需求。

第二个问题,为什么是最近?

相信玩过王者荣耀的对这个很熟悉吧

没错,最近可以说国内的防沉迷越来越严重,导致出现几乎所有游戏,都需要提供防沉迷系统来限制未成年人的游戏时长。关于这个举措,我不作讨论,也不是这个blog的内容。。

案例

话不多说,Demo源码+示例图:

gif例

源码https://github.com/ok406lhq/DurationCountingDemo

实现

  • 现在有一些是通过在各个Activity中埋点来实现计时长的操作的,也就是通过跳转Activity触发其生命周期回调来作为监控点,从而去获取累积时长的,这种方法也可以。但是Google官方有提供一个api:Application.ActivityLifecycleCallbacks(API14以上,现在还有低于14开发的吗?不是吧不是吧) 这个api最好在Application中调用,作为贯穿整个应用的对象,applicationContext拥有比Activity更长的生命周期,更易维护,回调函数少,也不用申请权限(Android6.0),因为作为sdk开发而言,权限尽量越少越好。

public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();ActivityLifeCycle lifecycleCallbacks = new ActivityLifeCycle();registerActivityLifecycleCallbacks(lifecycleCallbacks);}
}

我们可以实现一个ActivityLifeCycle继承Application.ActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks 下面是实现的代码:

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;@Overridepublic void onActivityCreated(Activity activity, Bundle bundle) {Logger.msg("onActivityCreated" + getActivityName(activity));}@Overridepublic void onActivityStarted(Activity activity) {Logger.msg("onActivityStarted " + activity.getClass().getSimpleName() + " " + 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;}@Overridepublic void onActivityResumed(Activity activity) {Logger.msg("onActivityResumed" + getActivityName(activity));//在这里更新检查时间点,是为了保证从后台恢复到前台,持续计时的准确性。lastCheckTime = System.currentTimeMillis();addAppUseReduceTimeIfNeeded(activity);if (willSwitchToForeground && isInteractive(activity)) {isForegroundNow = true;Logger.msg("switch to foreground");}if (isForegroundNow) {willSwitchToForeground = false;}}@Overridepublic void onActivityPaused(Activity activity) {Logger.msg("onActivityPaused" + getActivityName(activity));lastPausedActivityName = getActivityName(activity);lastPausedActivityHashCode = activity.hashCode();lastPausedTime = System.currentTimeMillis();}@Overridepublic void onActivityStopped(Activity activity) {Logger.msg("onActivityStopped" + getActivityName(activity));addAppUseReduceTimeIfNeeded(activity);//如果这个Activity实在修改配置,如旋转等,则不保存时间直接返回if (activity.isChangingConfigurations()) {isChangingConfigActivity = true;return;}//该Activity要进入后台,前台Activity数量-1。foregroundActivityCount -= 1;//当前已经是最后的一个Activity,代表此时应用退出了,保存时间。// 如果跨天了,则从新一天的0点开始计时if (foregroundActivityCount == 0) {isForegroundNow = false;Logger.msg("switch to background (reduce time[" + appUseReduceTime + "])");
//            if (getTodayStartTime() > appStartTime){
//                runTimeThisDay = System.currentTimeMillis() - getTodayStartTime();
//            }else {runTimeThisDay = System.currentTimeMillis() - appStartTime;saveTodayPlayTime(activity, runTimeThisDay);lastCheckTime = System.currentTimeMillis();Logger.msg("run time  :" + runTimeThisDay);}}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle bundle) {Logger.msg("onActivitySaveInstanceState" + getActivityName(activity));}@Overridepublic void onActivityDestroyed(Activity activity) {runTimeThisDay = System.currentTimeMillis() - appStartTime;saveTodayPlayTime(activity, runTimeThisDay);lastCheckTime = System.currentTimeMillis();Logger.msg("onActivityDestroyed" + getActivityName(activity) + "--runTimeThisDay:" + runTimeThisDay + "--lastCheckTime" + lastCheckTime);}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();}/*** 保存运行时间** @param context* @param time*/private void saveTodayPlayTime(Context context, long time) {long todayTime = ShareprefUtils.getLong(context, "APP_USE_TIME", 0);Logger.msg("使用时长Log:" + (todayTime + time));ShareprefUtils.saveLong(context, "APP_USE_TIME", todayTime + time);}
}

累积时长会保存在本地sharepreference文件,当然也可以上传服务器。另外,ActivityLifecycleCallbacks这个api还可以用来管理 Activity 页面栈,判断应用前后台,应用新开进程假重启处理等

  • 参考博客

探究 Android 中的 ActivityLifecycleCallbacks
android开发之app在线时长统计sdk开发
Application - ActivityLifecycleCallbacks

相关阅读

1 Android DEPPLINK、APPLink 原理简析
2 简单分析 App 进程 Crash 机制
3 Flutter 实现 App 内更新安装包
4 APP上架到各大应用市场技巧
5 Koin in Android: 更简单的依赖注入


如果你有写博客的好习惯
欢迎投稿
赞+在看,小生感恩❤️

Android 获取 App 累积时长相关推荐

  1. php录音时长统计,音频app阅读时长统计分析

    描述 分析学习喜马拉雅app阅读时长统计机制,并应用到自己app中去 分析 使用抓包工具charles观测app统计发送时机,发现切换音频源,或本地缓存有阅读数据进入首页app时发送(nyx/v2/t ...

  2. 格式android id,android 获取APP的唯一标识applicationId的实例

    使用getIdentifier()方法可以方便的获各应用包下的指定资源ID. 方式一 int indentify = getResources().getIdentifier("com.te ...

  3. php获取音频的时长,PHP编程获取音频文件时长的方法【基于getid3类】

    本文实例讲述了PHP编程获取音频文件时长的方法.分享给大家供大家参考,具体如下: 问题: 昨天在新增论坛功能的时候,移动端显示音频文件需要知道是多长的音频: 具体解决方案如下: 首先就是数据库中增加保 ...

  4. Android app 标签,android 获取APP的唯一标识applicationId的实例

    使用getIdentifier()方法可以方便的获各应用包下的指定资源ID. 方式一 int indentify = getResources().getIdentifier("com.te ...

  5. SSM根据上传文件获取视频文件时长

    一.编写前端上传文件代码 <html> <head><title>file upload</title> </head> <body& ...

  6. ffmpeg 获取视频的时长

    简单写个程序获取视频的时长,以便做视频播放器的进度条. 很简单,头文件我懒的敲了,直接复制别的,所以有些多余. 代码如下: #include <libavcodec/avcodec.h> ...

  7. Java获取视频的时长——日期时间处理

    Problem: 如何通过Java获取视频的时长? 想通过Java来获取某文件夹(或多个文件夹)下的视频的总时长 经过查询资料,发现可以通过JAVE实现:The JAVE (Java Audio Vi ...

  8. Java获取3gp视频时长

    Java获取3gp视频时长,其他格式的好像也可以,没有全部去试 /*** 获取视频的时间长*/public static String getVideoTime(String destFile) {S ...

  9. js 获取视频文件时长

    file为上传后的file类文件类型 //获取视频时长 function FileValue(file) {var url = URL.createObjectURL(file);$("#v ...

最新文章

  1. GDCM:gdcm::Item的测试程序
  2. vue使用html渲染组件,Vue.js在渲染组件之前填充数据
  3. 什么是Freedoc?Freedoc是什么?
  4. leetcode170. 两数之和 III - 数据结构设计
  5. python 表示图论_Python 图论工具 | 学步园
  6. swift mvvm_Swift中的MVVM设计模式概述
  7. DBMS_PIPE包
  8. 华为交换机S5700系列配置通过STelnet登录设备示例
  9. 关于Jquery中 “$(document).ready(function(){ })”函数的使用
  10. vue脚手架安装 axios 安装 配置 轮播图
  11. java和javascript有什么区别_javascript与java有什么区别?
  12. flipboard的翻页效果的实现
  13. 中国IT互联网行业线下峰会清单
  14. java ms932_Shift_JIS, MS932 and Unicode(收藏自网络)
  15. 为人“着想”和站在别人的角度--7-11创始人的经营理念引发的管理思考
  16. 看完这篇招聘方法论,90%CEO会心痛
  17. 使用jQuery的click事件没反应
  18. 计算机词汇店名,电脑店名字200例
  19. 全国计算机一级考试用什么版本,计算机等级考试用的是那个版本的office?
  20. 一二线城市知名 IT互联网公司名单(最新整理版跳槽名单)~

热门文章

  1. [guzzlehttp/guzzle]使用起来更优雅的HTTP客户端
  2. win7引导安装ubuntu无需U盘启动
  3. 02325《计算机系统结构》自考大题:第 4 章
  4. RK平台之AI模型转换环境搭建
  5. Apache HttpClient 5 笔记: SSL, Proxy 和 Multipart Upload
  6. Linux usbkey自动登陆,使用public/private key让putty(ssh)自动登录(以及linux上使用密钥做ssh自动登陆)...
  7. ES6模板字符串的扩展
  8. 叶开|Token的10大设计模式及Token金融与治理(长篇干货)
  9. 前端实习——Vue学习笔记(一)
  10. 九、myeclipse开发背景保护色设置