Android獲取App累積時長

閑談

好久沒更新了,前兩個月就定了標題,現(xiàn)在才開始動手寫。其實之前覺得這種獲取App累積時長的方法(以下內(nèi)容會介紹到)不唯一,可能有很多種,所以想收集多點資料,后來看到騰訊和360都是這么實現(xiàn)的,那么現(xiàn)在就開始介紹一下這個需求。
相信做游戲sdk開發(fā)的最近經(jīng)常有這個需求,為什么是游戲sdk開發(fā)需要?而且為什么是最近?
首先回答第一個問題,這個需求主要是要限制用戶對于app的使用時長,一般較為常見的是限制游戲游玩時長,其次,很少有Android應用有統(tǒng)計使用時長這一個需求。
第二個問題,為什么是最近?


相信玩過王者榮耀的對這個很熟悉吧

沒錯,最近可以說國內(nèi)的防沉迷越來越嚴重,導致出現(xiàn)幾乎所有游戲,都需要提供防沉迷系統(tǒng)來限制未成年人的游戲時長。關(guān)于這個舉措,我不作討論,也不是這個blog的內(nèi)容。。


案例

話不多說,Demo源碼+示例圖:


gif例
(點我,我是源碼

實現(xiàn)

  • 現(xiàn)在有一些是通過在各個Activity中埋點來實現(xiàn)計時長的操作的,也就是通過跳轉(zhuǎn)Activity觸發(fā)其生命周期回調(diào)來作為監(jiān)控點,從而去獲取累積時長的,這種方法也可以。但是Google官方有提供一個api:
    Application.ActivityLifecycleCallbacks(API14以上,現(xiàn)在還有低于14開發(fā)的嗎?不是吧不是吧)
    這個api最好在Application中調(diào)用,作為貫穿整個應用的對象,applicationContext擁有比Activity更長的生命周期,更易維護,回調(diào)函數(shù)少,也不用申請權(quán)限(Android6.0),因為作為sdk開發(fā)而言,權(quán)限盡量越少越好。
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ActivityLifeCycle lifecycleCallbacks = new ActivityLifeCycle();
        registerActivityLifecycleCallbacks(lifecycleCallbacks);
    }
}

我們可以實現(xiàn)一個ActivityLifeCycle繼承Application.ActivityLifecycleCallbacks
implements Application.ActivityLifecycleCallbacks
下面是實現(xiàn)的代碼:

public class ActivityLifeCycle implements Application.ActivityLifecycleCallbacks {
    /**
     * 上次檢查時間,用于在運行時作為基準獲取用戶時間
     */
    public static long lastCheckTime = 0;
    /**
     * 前臺Activity數(shù)量
     **/
    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;

    /**
     * 本次統(tǒng)計時,運行的時間
     */
    private long runTimeThisDay = 0L;


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

    @Override
    public void onActivityStarted(Activity activity) {
        Logger.msg("onActivityStarted " + activity.getClass().getSimpleName() + " " + foregroundActivityCount);
        //前臺沒有Activity,說明新啟動或者將從從后臺恢復
        if (foregroundActivityCount == 0 || !isForegroundNow) {
            willSwitchToForeground = true;
        } else {
            //應用已經(jīng)在前臺,此時保存今日運行的時間。
            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) {
        Logger.msg("onActivityResumed" + getActivityName(activity));
        //在這里更新檢查時間點,是為了保證從后臺恢復到前臺,持續(xù)計時的準確性。
        lastCheckTime = System.currentTimeMillis();
        addAppUseReduceTimeIfNeeded(activity);
        if (willSwitchToForeground && isInteractive(activity)) {
            isForegroundNow = true;
            Logger.msg("switch to foreground");
        }
        if (isForegroundNow) {
            willSwitchToForeground = false;
        }
    }

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

    @Override
    public void onActivityStopped(Activity activity) {
        Logger.msg("onActivityStopped" + getActivityName(activity));
        addAppUseReduceTimeIfNeeded(activity);
        //如果這個Activity實在修改配置,如旋轉(zhuǎn)等,則不保存時間直接返回
        if (activity.isChangingConfigurations()) {
            isChangingConfigActivity = true;
            return;
        }
        //該Activity要進入后臺,前臺Activity數(shù)量-1。
        foregroundActivityCount -= 1;
        //當前已經(jīng)是最后的一個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);
        }
    }

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

    @Override
    public 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 頁面棧,判斷應用前后臺,應用新開進程假重啟處理等

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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