Android應用統(tǒng)計-使用時長及次數(shù)統(tǒng)計(四)

關(guān)鍵字: 應用統(tǒng)計 Android源碼 應用使用時長 應用使用次數(shù)

上篇文章講到LocalService以及BinderService都是調(diào)用了UserUsageStatsService的相關(guān)函數(shù)接口,實現(xiàn)相關(guān)功能。以下,主要分為記錄和查詢兩個方面對UserUsageStatsService進行解析。

數(shù)據(jù)的記錄

上篇文章曾講到,不論是Event還是ConfigurationChange,都是調(diào)用UserUsageStatsService.reportEvent(event)這一函數(shù),用來記錄event和config數(shù)據(jù)。

 void reportEvent(UsageEvents.Event event) {
        if (DEBUG) {
            Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
                    + "[" + event.mTimeStamp + "]: "
                    + eventToString(event.mEventType));
        }

        if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
            // Need to rollover
            //切換文件(因為一個文件僅記錄當天的數(shù)據(jù),如果數(shù)據(jù)的時間超過了當天的標記時間,則需要新建另一文件,記錄第二天的數(shù)據(jù))
            rolloverStats(event.mTimeStamp);
        }
        final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];

        final Configuration newFullConfig = event.mConfiguration;
        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
                currentDailyStats.activeConfiguration != null) {
           // Make the event configuration a delta.
            event.mConfiguration = Configuration.generateDelta(
                    currentDailyStats.activeConfiguration, newFullConfig);
        }

        // Add the event to the daily list.
        //將event數(shù)據(jù)計入統(tǒng)計數(shù)據(jù)中。
       if (currentDailyStats.events == null) {
            currentDailyStats.events = new TimeSparseArray<>();
        }
        currentDailyStats.events.put(event.mTimeStamp, event);

         //依次更新日,周,月,年的統(tǒng)計數(shù)據(jù)
        for (IntervalStats stats : mCurrentStats) {
            if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
                stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
            } else {
                stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
            }
        }
        //關(guān)鍵:下一函數(shù)最終會調(diào)用上篇文章講到的flushToDiskLocked(),用于將數(shù)據(jù)寫入文件,而flushToDiskLocked()調(diào)用的是UserUsageStatsService.persistActiveStats()
        notifyStatsChanged();
    }

綜上可知,但凡記錄數(shù)據(jù),最終均需要調(diào)用UserUsageStatsService.persistActiveStats(),具體如下

 void persistActiveStats() {
        if (mStatsChanged) {
            Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
            try {
                for (int i = 0; i < mCurrentStats.length; i++) {
                    mDatabase.putUsageStats(i, mCurrentStats[i]);
                }
                mStatsChanged = false;
            } catch (IOException e) {
                Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
            }
        }
    }

UsageStatsDatabase,進行讀寫操作的主要接口,會在相關(guān)目錄下建立或者查詢daily,weekly,monthly,yearly四個xml文件,并操作之。
mDatabase.putUsageStats()的具體源碼如下。先從注釋上來說,這是一個寫文件的函數(shù),而且它還特意注明,寫入文件不會立刻執(zhí)行,所以在第三篇文章中的LocalService中會有一個prepareShutdown()函數(shù),會在系統(tǒng)關(guān)機前將保存于內(nèi)存中的相關(guān)數(shù)據(jù)寫入到文件中。這就導致內(nèi)存中的數(shù)據(jù)是具有實時性的,但是文件中的數(shù)據(jù)不具備實時性,萬一出現(xiàn)意外情況,比如說拔除手機電池,這樣一來內(nèi)存中的數(shù)據(jù)無法及時寫入文件。再次開機,系統(tǒng)會讀取文件中的數(shù)據(jù),并以這些數(shù)據(jù)為準,顯然這些數(shù)據(jù)是丟失一部分的,不完全的。以上是當前5.1版本的源碼邏輯的不足之一。

   /**
     * Update the stats in the database. They may not be written to disk immediately.
     */
    public void putUsageStats(int intervalType, IntervalStats stats) throws IOException {
        synchronized (mLock) {
            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
                throw new IllegalArgumentException("Bad interval type " + intervalType);
            }

            AtomicFile f = mSortedStatFiles[intervalType].get(stats.beginTime);
            if (f == null) {
                f = new AtomicFile(new File(mIntervalDirs[intervalType],
                        Long.toString(stats.beginTime)));
                mSortedStatFiles[intervalType].put(stats.beginTime, f);
            }

            UsageStatsXml.write(f, stats);
            stats.lastTimeSaved = f.getLastModifiedTime();
        }
    }

接下來,函數(shù)如下,依次調(diào)用UsageStatsXml.write(AtomicFile file, IntervalStats stats) -->UsageStatsXml.write(OutputStream out, IntervalStats stats) --> UsageStatsXmlV1.write(XmlSerializer xml, IntervalStats stats) ,將數(shù)據(jù)寫入xml文件中。

public static void write(AtomicFile file, IntervalStats stats) throws IOException {
        FileOutputStream fos = file.startWrite();
        try {
            write(fos, stats);
            file.finishWrite(fos);
            fos = null;
        } finally {
            // When fos is null (successful write), this will no-op
            file.failWrite(fos);
        }
    }

private static void write(OutputStream out, IntervalStats stats) throws IOException {
        FastXmlSerializer xml = new FastXmlSerializer();
        xml.setOutput(out, "utf-8");
        xml.startDocument("utf-8", true);
        xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
       xml.startTag(null, USAGESTATS_TAG);
        xml.attribute(null, VERSION_ATTR, Integer.toString(CURRENT_VERSION));

        UsageStatsXmlV1.write(xml, stats);

        xml.endTag(null, USAGESTATS_TAG);
        xml.endDocument();
    }

     /**
     * Writes the stats object to an XML file. The {@link XmlSerializer}
     * has already written the <code><usagestats></code> tag, but attributes may still
     * be added.
     *
     * @param xml The serializer to which to write the packageStats data.
     * @param stats The stats object to write to the XML file.
     */
    public static void write(XmlSerializer xml, IntervalStats stats) throws IOException {
        XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime);

        xml.startTag(null, PACKAGES_TAG);
        final int statsCount = stats.packageStats.size();
        for (int i = 0; i < statsCount; i++) {
            writeUsageStats(xml, stats, stats.packageStats.valueAt(i));
        }
        xml.endTag(null, PACKAGES_TAG);

       xml.startTag(null, CONFIGURATIONS_TAG);
        final int configCount = stats.configurations.size();
        for (int i = 0; i < configCount; i++) {
            boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
            writeConfigStats(xml, stats, stats.configurations.valueAt(i), active);
        }
        xml.endTag(null, CONFIGURATIONS_TAG);

        xml.startTag(null, EVENT_LOG_TAG);
        final int eventCount = stats.events != null ? stats.events.size() : 0;
        for (int i = 0; i < eventCount; i++) {
            writeEvent(xml, stats, stats.events.valueAt(i));
        }
        xml.endTag(null, EVENT_LOG_TAG);
    }

從最后調(diào)用的UsageStatsXmlV1.write(XmlSerializer xml, IntervalStats stats) 的代碼上可以看出,正如第二篇文章所介紹的xml文件那樣,數(shù)據(jù)先寫入統(tǒng)計數(shù)據(jù)Stats,再寫入ConfigStats數(shù)據(jù),最后在把Event數(shù)據(jù)寫入文件。針對以上三個write***函數(shù),可見其源碼如下:

 private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,final UsageStats usageStats) throws IOException {
        xml.startTag(null, PACKAGE_TAG);

        // Write the time offset.
        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
                usageStats.mLastTimeUsed - stats.beginTime);

        XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
        XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);

        xml.endTag(null, PACKAGE_TAG);
    }

    private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats, final ConfigurationStats configStats, boolean isActive) throws IOException {
        xml.startTag(null, CONFIG_TAG);

        // Write the time offset.
        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
                configStats.mLastTimeActive - stats.beginTime);

        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, configStats.mTotalTimeActive);
        XmlUtils.writeIntAttribute(xml, COUNT_ATTR, configStats.mActivationCount);
        if (isActive) {
            XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true);
        }

        // Now write the attributes representing the configuration object.
        Configuration.writeXmlAttrs(xml, configStats.mConfiguration);

        xml.endTag(null, CONFIG_TAG);
    }

    private static void writeEvent(XmlSerializer xml, final IntervalStats stats,final UsageEvents.Event event) throws IOException {
        xml.startTag(null, EVENT_TAG);

       // Store the time offset.
        XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp - stats.beginTime);

        XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage);
        if (event.mClass != null) {
            XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass);
        }
        XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);

        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
                && event.mConfiguration != null) {
            Configuration.writeXmlAttrs(xml, event.mConfiguration);
        }

        xml.endTag(null, EVENT_TAG);
    }

以上三個函數(shù)皆是將數(shù)據(jù)寫入xml文件中,但是,值得注意的是,所有關(guān)于時間的節(jié)點,記錄的并非是絕對時間,而是類似于 usageStats.mLastTimeUsed - stats.beginTime 這樣的減去beginTime之后的相對時間。

結(jié)語:

本文主要介紹了關(guān)于Android系統(tǒng)中統(tǒng)計各個app的使用情況的解決方案,主要是介紹了在統(tǒng)計數(shù)據(jù)的時候,將數(shù)據(jù)寫入對應文件這一具體流程,以及寫入數(shù)據(jù)的詳細情況。接下來的文章中將會詳細闡述如何讀取這些記錄的數(shù)據(jù),并且使用他們。

轉(zhuǎn)載請注明出處。

github:UseTimeStatistic
參考文獻:
Android 5.1相關(guān)源碼目錄
Android UsageStatsService:要點解析
Android5.1應用打開次數(shù)獲取

上一篇:Android應用統(tǒng)計-使用時長及次數(shù)統(tǒng)計(三)
下一篇:Android應用統(tǒng)計-使用時長及次數(shù)統(tǒng)計(五)

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

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,828評論 25 709
  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,597評論 0 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,525評論 19 139
  • 當定時器T0用做方式3時,由于TR1位已被T0占用,如何控制定時器T1的開啟和關(guān)閉? 【答案】定時器T0用做方式3...
    0201_侯曉彤閱讀 621評論 1 1
  • 正在看一本書-《自控力》,里面說情緒不是用來壓抑的,而是需要引導的。一語點醒壓抑人,自己原來一向覺得自己脾氣很好,...
    非讓起名字閱讀 327評論 0 0

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