Android UsageStatsService(應用使用統計服務)的學習與調研


一. 簡介

UsageStatsService是一個系統服務,其主要通過AMS等,來監測並記錄各個應用的使用數據,如上次調用com.android.settings的時間等。

UsageStatsService,a service that collects, aggregates, and persists application usage data. This data can be queried by apps that have been granted permission by AppOps.

代碼位置:frameworks/base/services/usage/java/com/android/server/usage/

UsageStatsService創建時,其在onStart()方法中會調用如下方法提供服務,

         publishLocalService(UsageStatsManagerInternal.class, new LocalService()); // AMS會調用

         publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); // 給其他Service和APP調用

其中重點關注LocalService,ActivityManagerService有一個成員變量mUsageStatsService,其會統計4個UsageStatsService自定義的事件(MOVE_TO_FOREGROUND,MOVE_TO_BACKGROUND,CONFIGURATION_CHANGE,SYSTEM_INTERACTION)。mUsageStatsService的賦值在SystemServer#startCoreServices()方法中,如下:

         mActivityManagerService.setUsageStatsManager(LocalServices.getService(UsageStatsManagerInternal.class));

 

二. 事件

數據的事件類型有7種,全部定義在UsageEvents.java中,如下:

數值
事件
解釋
調用方
備注
1 MOVE_TO_FOREGROUND An event type denoting that a component moved to the foreground ActivityManagerService 例如當Activity被置於前台顯示時,會記錄此事件及時間,包括Activity名字
2 MOVE_TO_BACKGROUND An event type denoting that a component moved to the background ActivityManagerService 例如當Activity被置於后台時,會記錄此事件及時間,包括Activity名字
3 END_OF_DAY An event type denoting that a component was in the foreground when the stats * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND} UsageStatsService

每一次記錄事件時間時,都會判定,如果此次記錄時系統時間越過一天,此時會記錄此事件,它的作用是這次的時間會做為今天的最后一個記錄時間。

那么下次再記錄事件時,將會當作新的一天來記錄,新建一個新的XML文件

4 CONTINUE_PREVIOUS_DAY An event type denoting that a component was in the foreground the previous day. * This is effectively treated as a {@link #MOVE_TO_FOREGROUND} UsageStatsService -
5 CONFIGURATION_CHANGE An event type denoting that the device configuration has changed ActivityManagerService 記錄下系統配置變化的數據,時間等
6 SYSTEM_INTERACTION An event type denoting that a package was interacted with in some way by the system ActivityManagerService 較多事件是此類型,具體解釋可見下方2
7 USER_INTERACTION An event type denoting that a package was interacted with in some way by the user NotificationManagerService 當一條通知顯示給用戶看時,會記錄此事件,包括時間,package等

重點介紹以下3個事件,

1. 事件【MOVE_TO_FOREGROUND與MOVE_TO_BACKGROUND】

Activity在前台顯示時,ActivityStackSupervisor.java中方法reportResumedActivityLocked(),其會調用ActivityManagerService#updateUsageStats(),resumedf參數為true,

Activity在后台時,ActivityStack.java中方法startPausingLocked()或removeHistoryRecordsForAppLocked(),其會調用ActivityManagerService#updateUsageStats(),resumedf參數為false,

void updateUsageStats(ActivityRecord componentboolean resumed) {

        ......
        if (resumed) {
            if (mUsageStatsService != null) {
                mUsageStatsService.reportEvent(component.realActivity, component.userId,
                        UsageEvents.Event.MOVE_TO_FOREGROUND);
            }
        ......
        } else {
            if (mUsageStatsService != null) {
                mUsageStatsService.reportEvent(component.realActivity, component.userId,
                        UsageEvents.Event.MOVE_TO_BACKGROUND);
            }
       ......
        }
}

在updateUsageStats()中會調用UsageStatsService的reportEvent方法,來記錄下MOVE_TO_FOREGROUND或MOVE_TO_FOREGROUND事件,以及Activity等,這些數據會通過UsageStatsService被保存。

 

2. 事件【SYSTEM_INTERACTION】

統計此事件的代碼調用順序是:AcitivityManagerService#applyOomAdjLocked()  -> ActivityManagerService#maybeUpdateUsageStatsLocked() -> mUsageStatsService.reportEvent(packages[i], app.userId, UsageEvents.Event.SYSTEM_INTERACTION);

代碼:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#maybeUpdateUsageStatsLocked

其中在方法maybeUpdateUsageStatsLocked中,判斷是否向UsageStatsService發送此事件統計的依據之一是變量isInteraction(通過對比app.curProcState)。

舉個例子:

百度小米輸入法(com.baidu.input_mi)的進程狀態由PROCESS_STATE_NONEXISTENT(-1)變更為PROCESS_STATE_BOUND_FOREGROUND_SERVICE(3)時,AMS會向UsageStatsService發送此事件記錄。debug log如下:

Checking proc [[com.baidu.input_mi]] state changes: old = -1, new = 3

report SYSTEM_INTERACTION event, package = com.baidu.input_mi

 

另外,重點注意以下2點:

A. UsageStatsService中,SYSTEM_INTERACTION事件在數據存儲時,其event type會被記為0。代碼依據:

frameworks/base/services/usage/java/com/android/server/usage/IntervalStats.java#108

B. SYSTEM_INTERACTION事件的上次使用時間,在數據存儲時,會被記為0,通過UTC時間轉換后,是1970年1月1日

112        if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
113            usageStats.mLastTimeUsed = timeStamp;
114        }

frameworks/base/services/usage/java/com/android/server/usage/IntervalStats.java#112

以上2點是應用使用統計服務的by design邏輯

 

三. 數據存儲

UsageStatsService的數據存儲在哪里?有一個類在管理UsageStatsDatabase,通過它的源碼即可發現,真正的數據持久化是存儲在XML中,XML位置:/data/system/usagestats/。XML的所有操作,例如讀,寫等,都被封裝在類UsageStatsXmlV1中,由UsageStatsDatabase調用。

以下介紹3個方面,以及重點介紹下【時間跳變時UsageStatsService是如何更新已記錄的時間】

1. 緩存與文件存儲

UsageStatsService每次在啟動時,都會先按照user生成各個UserUsageStatsService,其中每個對象都會先去各自的文件路徑下讀取數據到內存中。代碼如下:

98        for (int i = 0; i < mCurrentStats.length; i++) {
99            mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
100          ......

此后每次外界reportEvent,都會先更新內存中的數據,相當於緩存。那什么時候內存中的數據會更新至文件中呢?主要有以下幾種情況:

  情況

 

內存中數據更新至文件中的時機

1. 手機關機,具體見:UsageStatsService.java#shutdown
2. 系統時間跳變(如人為修改系統時間或時間隨網絡校准)
3. 一天結束時,因為daily下面xml文件存儲一天的數據,此時需下次新建文件
4. ......

數據在內存中保存在mCurrentStats變量中

 

2. 數據目錄按daily,monthly,weekly,yearly四個文件夾存儲,每個文件夾中包含若干個XML文件

如:

 

3. 每一個XML中的數據

打開一個XML,即可看到,存儲的數據包括三類package上次訪問和使用的時間,configurationsevent-log。舉例如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>

       <usagestats version="1" endTime="93054">

             <packages>

                   <package lastTimeActive="92995" package="com.android.settings" timeActive="87841" lastEvent="2" />

                   <package lastTimeActive="93054" package="com.miui.home" timeActive="5076" lastEvent="1" />

             </packages>

       <configurations>

             <config lastTimeActive="0" timeActive="0" count="1" active="true" fs="1065353216" locales="zh-CN" touch="3" key="1" keyHid="1" hardKeyHid="2" nav="1" navHid="2" ori="1" scrLay="268435810" ui="17" width="360" height="620" sw="360" density="480" />

       </configurations>

       <event-log>

             <event time="0" package="com.android.settings" class="com.android.settings.UsageStatsActivity" type="2" />

             <event time="61" package="com.miui.home" class="com.miui.home.launcher.Launcher" type="1" />

             <event time="5137" package="com.miui.home" class="com.miui.home.launcher.Launcher" type="2" />

             <event time="5154" package="com.android.settings" class="com.android.settings.MiuiSettings" type="1" />

             <event time="92995" package="com.android.settings" class="com.android.settings.MiuiSettings" type="2" />

             <event time="93054" package="com.miui.home" class="com.miui.home.launcher.Launcher" type="1" />

    </event-log>

</usagestats>

數據類型
簡介
package 以包為記錄單位,例如com.android.settings上一次被訪問的時間,上次被使用的時間以及事件類型。注意:其數值是能在event log中查詢找到,對應起來
configurations 由AMS發送事件給UsageStatsService來統計,記錄下系統配置變化的數據,時間等
event-log 以Activity為單位,例如桌面界面,上一次被訪問的時間,以及事件類型

注意:XML中package的lastEvent字段,event-log的type字段,都是指上面介紹過的事件類型。但是注意,事件類型是有7種,但真正記錄在XML中,除了4種(MOVE_TO_FOREGROUND,MOVE_TO_BACKGROUND,END_OF_DAY,CONTINUE_PREVIOUS_DAY)記錄其int值,其他的事件(CONFIGURATION_CHANGE,SYSTEM_INTERACTION,USER_INTERACTION)都記錄為0,所以在XML中看到事件類型為0,那么是指這三種。這段邏輯是在數據記錄的IntervalStats#update方法中,

95    void update(String packageName, long timeStamp, int eventType) {
96        UsageStats usageStats = getOrCreateUsageStats(packageName);
......
108        if (isStatefulEvent(eventType)) {
109            usageStats.mLastEvent = eventType;
110        }
111
112        if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
113            usageStats.mLastTimeUsed = timeStamp;
114        }
......
122    }

isStatefulEvent的實現如下:

84    private boolean isStatefulEvent(int eventType) {
85        switch (eventType) {
86            case UsageEvents.Event.MOVE_TO_FOREGROUND:
87            case UsageEvents.Event.MOVE_TO_BACKGROUND:
88            case UsageEvents.Event.END_OF_DAY:
89            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
90                return true;
91        }
92        return false;
93    }

另外,格外注意以上方法中第112行,SYSTEM_INTERACTION這個事件在存儲時,上次訪問時間是不記錄真實時間的,取初始默認值0,轉換成現代時間,就是1970年1月1日

 

以下重點介紹:

1. 數據隨時間跳變而調整

    手機系統時間會每秒發生變化,但也會發生跳變,常見的方式是2種,一是人為修改時間,二是系統時間通過網絡自動校准(SIM卡或WiFi等)。

    舉個例子,手機第一次使用,未聯網校准時,手機時間是錯誤的,可能顯示為1970年3月25日,這時候用戶在手機的操作,各個應用的上次使用時間肯定是被記錄為1970年3月25日。但手機聯網后,時間被通過網絡校准為2017年11月29日。那這樣會有一個情況?UsageStatsService中統計的時間仍然記錄為1970年3月25日嗎?顯然不會的,Google的工程師想到了這一點,因此在UsageStatsService中有一個巧妙的機制,來保證記錄時間的准確性。

    A. UsageStatsService中有一個方法checkAndGetTimeLocked,此方法會在每次reportEvent記錄應用事件時,獲取系統時間,在獲取的同時呢,它也記錄了上一次使用的系統時間。通過差值計算,能夠判定出系統的時間是否發生了跳變,例如人為的修改,通過網絡進行的時間自動校准等。如果判定系統時間發生了跳變,UsageStatsService會調用onTimeChanged()方法,它會負責更新UsageStatsService記錄的時間,以便他們能夠跟隨系統時間跳變,而相應更新。

         void onTimeChanged(long oldTime, long newTime) {
                persistActiveStats();
                mDatabase.onTimeChanged(newTime - oldTime);
                loadActiveStats(newTime);
         }

     B. 這里的時間是直接存儲在XML中嗎?例如XML中Activity1,上次使用時間:2017年11月29日XX時XX分XX秒。不是這樣的,這里的設計也它的特別之處,

         首先時間的存儲全部是按毫秒來存儲的,此毫秒也就是對比1970年1月1日,換算來的差值。

         其次時間的存儲分為2部分,這里有一個公式,應用的上次使用時間  = XML文件名 + XML中此應用的上次使用時間。注意:XML文件的名字不是隨便起的,是用某個基准時間的毫秒值來存儲的。舉個例子:

         XML文件名為:1511953275497,打開文件,其中的數據如下:

   <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
           <usagestats version="1" endTime="2936118">
           <packages>
           <package lastTimeActive="2936118" package="com.android.settings" timeActive="78268" lastEvent="2" />

           .......  

         那么在查詢上次使用數據時,Settings的上次使用時間為:1511953275497 + 2936118 = 1511956211615,通過對比1970年1月1日,換算為日常時間是大約是2017/11/29 19:50:11。所以這就是手機中設置Settings上次的使用時間。

         這樣設計的好處是,當系統時間跳變時,只需要更新XML的文件名時間,XML中所有的值不需要逐條更新。那么通過算加法得到的時間,也就是正確的時間了。再舉個例子:

         1. 用戶手機時間為1970年3月25日,這時UsageStatsService中XML的文件名為:7142400,其中設置的上次使用時間,在XML中存儲的值是10000,那么設置的上次使用時間是:

             7142400 + 10000 = 7152400,換算為正常時間是:1970年3月25日。(注意,舉例中時間用得秒,並非毫秒,實際XML中存儲的都是毫秒)

         2. 用戶手機時間通過跳變,校准為2017年11月29日。這時UsageStatsService通過onTimeChanged方法,XML中文件名變為1511953275497,在XML中存儲的值還是10000,那么設置的上次使用時間是:

             1511953275 + 10000 = 1511963275,換算為正常時間是:2017年11月29日。(注意,舉例中時間用得秒,並非毫秒,實際XML中存儲的都是毫秒)

         通過以上這樣的機制,UsageStatsService中記錄的時間,就會隨着系統時間的跳變(人為修改或網絡校准)而保持為正確的值。用戶查詢時也不會感到詫異。

 

 

 

 我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM