Android5.1应用统计源码分析

在android中,系统自带一个统计应用打开次数和上次运行时间的api,但是每次版本升级都会带来很多的变
化,这一块也不例外,但唯一没有改变的就是从拨号盘输入*#*#4636#*#* 进入工程模式,然后点击使
用情况统计数据,你就会看到统计的界面了。这里我只分析5.1的这块代码,以前版本网上也有人写博客分
析,但是5.1的资料很少,以前那一套已经不适用。

frameworks / base / core / java / android / app / usage / UsageStatsManager.java
UsageStatsManager 统计这块主要是从这里来管理,我们可以看看代码

package android.app.usage;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.util.ArrayMap;
import java.util.Collections;
import java.util.List;
/**
 * Provides access to device usage history and statistics. Usage data is aggregated into
 * time intervals: days, weeks, months, and years.
 * 

* When requesting usage data since a particular time, the request might look something like this: *

 * PAST                   REQUEST_TIME                    TODAY                   FUTURE
 * ————————————————————————————||———————————————————————————¦-----------------------|
 *                        YEAR ||                           ¦                       |
 * ————————————————————————————||———————————————————————————¦-----------------------|
 *  MONTH            |         ||                MONTH      ¦                       |
 * ——————————————————|—————————||———————————————————————————¦-----------------------|
 *   |      WEEK     |     WEEK||    |     WEEK     |     WE¦EK     |      WEEK     |
 * ————————————————————————————||———————————————————|———————¦-----------------------|
 *                             ||           |DAY|DAY|DAY|DAY¦DAY|DAY|DAY|DAY|DAY|DAY|
 * ————————————————————————————||———————————————————————————¦-----------------------|
 * 
* A request for data in the middle of a time interval will include that interval. *

* NOTE: This API requires the permission android.permission.PACKAGE_USAGE_STATS, which * is a system-level permission and will not be granted to third-party apps. However, declaring * the permission implies intention to use the API and the user of the device can grant permission * through the Settings application. */ public final class UsageStatsManager { /** * An interval type that spans a day. See {@link #queryUsageStats(int, long, long)}. */ public static final int INTERVAL_DAILY = 0; /** * An interval type that spans a week. See {@link #queryUsageStats(int, long, long)}. */ public static final int INTERVAL_WEEKLY = 1; /** * An interval type that spans a month. See {@link #queryUsageStats(int, long, long)}. */ public static final int INTERVAL_MONTHLY = 2; /** * An interval type that spans a year. See {@link #queryUsageStats(int, long, long)}. */ public static final int INTERVAL_YEARLY = 3; /** * An interval type that will use the best fit interval for the given time range. * See {@link #queryUsageStats(int, long, long)}. */ public static final int INTERVAL_BEST = 4; /** * The number of available intervals. Does not include {@link #INTERVAL_BEST}, since it * is a pseudo interval (it actually selects a real interval). * {@hide} */ public static final int INTERVAL_COUNT = 4; private static final UsageEvents sEmptyResults = new UsageEvents(); private final Context mContext; private final IUsageStatsManager mService; /** * {@hide} */ public UsageStatsManager(Context context, IUsageStatsManager service) { mContext = context; mService = service; } /** * Gets application usage stats for the given time range, aggregated by the specified interval. *

The returned list will contain a {@link UsageStats} object for each package that * has data for an interval that is a subset of the time range given. To illustrate:

*
     * intervalType = INTERVAL_YEARLY
     * beginTime = 2013
     * endTime = 2015 (exclusive)
     *
     * Results:
     * 2013 - com.example.alpha
     * 2013 - com.example.beta
     * 2014 - com.example.alpha
     * 2014 - com.example.beta
     * 2014 - com.example.charlie
     * 
* * @param intervalType The time interval by which the stats are aggregated. * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. * @return A list of {@link UsageStats} or null if none are available. * * @see #INTERVAL_DAILY * @see #INTERVAL_WEEKLY * @see #INTERVAL_MONTHLY * @see #INTERVAL_YEARLY * @see #INTERVAL_BEST */
@SuppressWarnings("unchecked") public List queryUsageStats(int intervalType, long beginTime, long endTime) { try { ParceledListSlice slice = mService.queryUsageStats(intervalType, beginTime, endTime, mContext.getOpPackageName()); if (slice != null) { return slice.getList(); } } catch (RemoteException e) { // fallthrough and return null. } return Collections.EMPTY_LIST; } /** * Query for events in the given time range. Events are only kept by the system for a few * days. *

* NOTE: The last few minutes of the event log will be truncated to prevent abuse * by applications. * * @param beginTime The inclusive beginning of the range of events to include in the results. * @param endTime The exclusive end of the range of events to include in the results. * @return A {@link UsageEvents}. */ @SuppressWarnings("unchecked") public UsageEvents queryEvents(long beginTime, long endTime) { try { UsageEvents iter = mService.queryEvents(beginTime, endTime, mContext.getOpPackageName()); if (iter != null) { return iter; } } catch (RemoteException e) { // fallthrough and return null } return sEmptyResults; } /** * A convenience method that queries for all stats in the given range (using the best interval * for that range), merges the resulting data, and keys it by package name. * See {@link #queryUsageStats(int, long, long)}. * * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. * @return An {@link android.util.ArrayMap} keyed by package name or null if no stats are * available. */ public ArrayMap queryAndAggregateUsageStats(long beginTime, long endTime) { List stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime); if (stats.isEmpty()) { @SuppressWarnings("unchecked") ArrayMap emptyStats = ArrayMap.EMPTY; return emptyStats; } ArrayMap aggregatedStats = new ArrayMap<>(); final int statCount = stats.size(); for (int i = 0; i < statCount; i++) { UsageStats newStat = stats.get(i); UsageStats existingStat = aggregatedStats.get(newStat.getPackageName()); if (existingStat == null) { aggregatedStats.put(newStat.mPackageName, newStat); } else { existingStat.add(newStat); } } return aggregatedStats; } }

看注释我们就可以明确的知道,它是用来为设备提供使用历史和统计的,分为4个时间间隔 日,周,月,年。主要用的到的方法就几个,主要功能简单翻译下

//获取给定时间范围内的应用程序使用数据,并通过指定的时间间隔进行汇总。
queryUsageStats(int intervalType, long beginTime, long endTime)
//给定时间范围内的事件查询。事件只被系统保留几天。
queryEvents(long beginTime, long endTime)
//查询在给定的范围内的所有数据(使用最佳的时间间隔),合并产生的数据,并用包名作为key。
queryAndAggregateUsageStats(long beginTime, long endTime)

明显可以看到,它们都必须要一个beginTime和一个endTime。而前两个方法都是由IUsageStatsManager来获取的,根据我们的经验,这种命名方式的一般都是aidl的文件,所以我们想要知道是怎么查询的就必须找到实现的方法.

frameworks/base/services/usage/java/com/android/server/usage
这个就是我们要找的主要实现目录,具体实现方法在UserUsageStatsService,这里我们先看queryUsageStats 和 queryEvents

    List queryUsageStats(int bucketType, long beginTime, long endTime) {
        return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
    }

    List queryConfigurationStats(int bucketType, long beginTime, long endTime) {
        return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
    }

再跳到queryStats

/**
     * Generic query method that selects the appropriate IntervalStats for the specified time range
     * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
     * provided to select the stats to use from the IntervalStats object.
     */
    private  List queryStats(int intervalType, final long beginTime, final long endTime,
            StatCombiner combiner) {
        if (intervalType == UsageStatsManager.INTERVAL_BEST) {
            intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
            if (intervalType < 0) {
                // Nothing saved to disk yet, so every stat is just as equal (no rollover has
                // occurred.
                intervalType = UsageStatsManager.INTERVAL_DAILY;
            }
        }

        if (intervalType < 0 || intervalType >= mCurrentStats.length) {
            if (DEBUG) {
                Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
            }
            return null;
        }

        final IntervalStats currentStats = mCurrentStats[intervalType];

        if (DEBUG) {
            Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
                    + beginTime + " AND endTime < " + endTime);
        }

        if (beginTime >= currentStats.endTime) {
            if (DEBUG) {
                Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
                        + currentStats.endTime);
            }
            // Nothing newer available.
            return null;
        }

        // Truncate the endTime to just before the in-memory stats. Then, we'll append the
        // in-memory stats to the results (if necessary) so as to avoid writing to disk too
        // often.
        final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);

        // Get the stats from disk.
        List results = mDatabase.queryUsageStats(intervalType, beginTime,
                truncatedEndTime, combiner);
        if (DEBUG) {
            Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
            Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
                    " endTime=" + currentStats.endTime);
        }

        // Now check if the in-memory stats match the range and add them if they do.
        if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
            if (DEBUG) {
                Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
            }

            if (results == null) {
                results = new ArrayList<>();
            }
            combiner.combine(currentStats, true, results);
        }

        if (DEBUG) {
            Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
        }
        return results;
    }

上面的代码我们看到,最终的result是在这里获取的

// Get the stats from disk.
List results = mDatabase.queryUsageStats(intervalType, beginTime, truncatedEndTime, combiner);

这里我们看到有个mDatabase,看到这,相信大家可能会想,原来是存在数据库中的。我们看一下它的定义

private final UsageStatsDatabase mDatabase;

又出现一个新的类,不着急,进去看看

/**
 * Provides an interface to query for UsageStat data from an XML database.
 */
class UsageStatsDatabase {
    private static final int CURRENT_VERSION = 2;

    private static final String TAG = "UsageStatsDatabase";
    private static final boolean DEBUG = UsageStatsService.DEBUG;
    private static final String BAK_SUFFIX = ".bak";
    private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;

    private final Object mLock = new Object();
    private final File[] mIntervalDirs;
    private final TimeSparseArray[] mSortedStatFiles;
    private final UnixCalendar mCal;
    private final File mVersionFile;

    public UsageStatsDatabase(File dir) {
        mIntervalDirs = new File[] {
                new File(dir, "daily"),
                new File(dir, "weekly"),
                new File(dir, "monthly"),
                new File(dir, "yearly"),
        };
        mVersionFile = new File(dir, "version");
        mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
        mCal = new UnixCalendar(0);
    }
}
    /**
     * Find all {@link IntervalStats} for the given range and interval type.
     */
    public  List queryUsageStats(int intervalType, long beginTime, long endTime,
            StatCombiner combiner) {
        synchronized (mLock) {
            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
                throw new IllegalArgumentException("Bad interval type " + intervalType);
            }

            final TimeSparseArray intervalStats = mSortedStatFiles[intervalType];

            if (endTime <= beginTime) {
                if (DEBUG) {
                    Slog.d(TAG, "endTime(" + endTime + ") <= beginTime(" + beginTime + ")");
                }
                return null;
            }

            int startIndex = intervalStats.closestIndexOnOrBefore(beginTime);
            if (startIndex < 0) {
                // All the stats available have timestamps after beginTime, which means they all
                // match.
                startIndex = 0;
            }

            int endIndex = intervalStats.closestIndexOnOrBefore(endTime);
            if (endIndex < 0) {
                // All the stats start after this range ends, so nothing matches.
                if (DEBUG) {
                    Slog.d(TAG, "No results for this range. All stats start after.");
                }
                return null;
            }

            if (intervalStats.keyAt(endIndex) == endTime) {
                // The endTime is exclusive, so if we matched exactly take the one before.
                endIndex--;
                if (endIndex < 0) {
                    // All the stats start after this range ends, so nothing matches.
                    if (DEBUG) {
                        Slog.d(TAG, "No results for this range. All stats start after.");
                    }
                    return null;
                }
            }

            try {
                IntervalStats stats = new IntervalStats();
                ArrayList results = new ArrayList<>();
                for (int i = startIndex; i <= endIndex; i++) {
                    final AtomicFile f = intervalStats.valueAt(i);

                    if (DEBUG) {
                        Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
                    }

                    UsageStatsXml.read(f, stats);
                    if (beginTime < stats.endTime) {
                        combiner.combine(stats, false, results);
                    }
                }
                return results;
            } catch (IOException e) {
                Slog.e(TAG, "Failed to read usage stats file", e);
                return null;
            }
        }
    }

这里我只贴了变量定义,构造方法和queryUsageStats。看完之后发现并没有大家想的database,这里的AtomicFile 是封装的file,用来备份文件,挑重点可以看到UsageStatsXml.read(f, stats);最终失去读文件了,我们接着跟

/**
     * Reads from the {@link XmlPullParser}, assuming that it is already on the
     *  tag.
     *
     * @param parser The parser from which to read events.
     * @param statsOut The stats object to populate with the data from the XML file.
     */
    public static void read(XmlPullParser parser, IntervalStats statsOut)
            throws XmlPullParserException, IOException {
        statsOut.packageStats.clear();
        statsOut.configurations.clear();
        statsOut.activeConfiguration = null;

        if (statsOut.events != null) {
            statsOut.events.clear();
        }

        statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);

        int eventCode;
        int outerDepth = parser.getDepth();
        while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT
                && (eventCode != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (eventCode != XmlPullParser.START_TAG) {
                continue;
            }

            final String tag = parser.getName();
            switch (tag) {
                case PACKAGE_TAG:
                    loadUsageStats(parser, statsOut);
                    break;

                case CONFIG_TAG:
                    loadConfigStats(parser, statsOut);
                    break;

                case EVENT_TAG:
                    loadEvent(parser, statsOut);
                    break;
            }
        }
    }

//最后调用到loadUsageStats

private static final String PACKAGES_TAG = "packages";
    private static final String PACKAGE_TAG = "package";

    private static final String CONFIGURATIONS_TAG = "configurations";
    private static final String CONFIG_TAG = "config";

    private static final String EVENT_LOG_TAG = "event-log";
    private static final String EVENT_TAG = "event";

    // Attributes
    private static final String PACKAGE_ATTR = "package";
    private static final String CLASS_ATTR = "class";
    private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
    private static final String COUNT_ATTR = "count";
    private static final String ACTIVE_ATTR = "active";
    private static final String LAST_EVENT_ATTR = "lastEvent";
    private static final String TYPE_ATTR = "type";

    // Time attributes stored as an offset of the beginTime.
    private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
    private static final String END_TIME_ATTR = "endTime";
    private static final String TIME_ATTR = "time";

    private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
            throws XmlPullParserException, IOException {
        final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
        if (pkg == null) {
            throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
        }

        final UsageStats stats = statsOut.getOrCreateUsageStats(pkg);

        // Apply the offset to the beginTime to find the absolute time.
        stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
                parser, LAST_TIME_ACTIVE_ATTR);

        stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
        stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
    }

看到这里上面定义的变量和各种xml的工具,大家猜也猜出来是去读xml文件了,系统的应用信息是写在xml里面的,上面反复出现一个类UsageStats ,而且最终是把值赋给了它,那我们看看里面究竟是什么

/**
 * Contains usage statistics for an app package for a specific
 * time range.
 */
public final class UsageStats implements Parcelable {

    /**
     * {@hide}
     */
    public String mPackageName;

    /**
     * {@hide}
     */
    public long mBeginTimeStamp;

    /**
     * {@hide}
     */
    public long mEndTimeStamp;

    /**
     * {@hide}
     */
    public long mLastTimeUsed;

    /**
     * {@hide}
     */
    public long mTotalTimeInForeground;

    /**
     * {@hide}
     */
    public int mLaunchCount;

    /**
     * {@hide}
     */
    public int mLastEvent;

    /**
     * {@hide}
     */
    public UsageStats() {
    }

    public UsageStats(UsageStats stats) {
        mPackageName = stats.mPackageName;
        mBeginTimeStamp = stats.mBeginTimeStamp;
        mEndTimeStamp = stats.mEndTimeStamp;
        mLastTimeUsed = stats.mLastTimeUsed;
        mTotalTimeInForeground = stats.mTotalTimeInForeground;
        mLaunchCount = stats.mLaunchCount;
        mLastEvent = stats.mLastEvent;
    }

    public String getPackageName() {
        return mPackageName;
    }

    /**
     * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents,
     * measured in milliseconds since the epoch.
     * 

* See {@link System#currentTimeMillis()}. */ public long getFirstTimeStamp() { return mBeginTimeStamp; } /** * Get the end of the time range this {@link android.app.usage.UsageStats} represents, * measured in milliseconds since the epoch. *

* See {@link System#currentTimeMillis()}. */ public long getLastTimeStamp() { return mEndTimeStamp; } /** * Get the last time this package was used, measured in milliseconds since the epoch. *

* See {@link System#currentTimeMillis()}. */ public long getLastTimeUsed() { return mLastTimeUsed; } /** * Get the total time this package spent in the foreground, measured in milliseconds. */ public long getTotalTimeInForeground() { return mTotalTimeInForeground; } /** * Add the statistics from the right {@link UsageStats} to the left. The package name for * both {@link UsageStats} objects must be the same. * @param right The {@link UsageStats} object to merge into this one. * @throws java.lang.IllegalArgumentException if the package names of the two * {@link UsageStats} objects are different. */ public void add(UsageStats right) { if (!mPackageName.equals(right.mPackageName)) { throw new IllegalArgumentException("Can't merge UsageStats for package '" + mPackageName + "' with UsageStats for package '" + right.mPackageName + "'."); } if (right.mEndTimeStamp > mEndTimeStamp) { mLastEvent = right.mLastEvent; mEndTimeStamp = right.mEndTimeStamp; mLastTimeUsed = right.mLastTimeUsed; } mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp); mTotalTimeInForeground += right.mTotalTimeInForeground; mLaunchCount += right.mLaunchCount; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPackageName); dest.writeLong(mBeginTimeStamp); dest.writeLong(mEndTimeStamp); dest.writeLong(mLastTimeUsed); dest.writeLong(mTotalTimeInForeground); dest.writeInt(mLaunchCount); dest.writeInt(mLastEvent); } public static final Creator CREATOR = new Creator() { @Override public UsageStats createFromParcel(Parcel in) { UsageStats stats = new UsageStats(); stats.mPackageName = in.readString(); stats.mBeginTimeStamp = in.readLong(); stats.mEndTimeStamp = in.readLong(); stats.mLastTimeUsed = in.readLong(); stats.mTotalTimeInForeground = in.readLong(); stats.mLaunchCount = in.readInt(); stats.mLastEvent = in.readInt(); return stats; } @Override public UsageStats[] newArray(int size) { return new UsageStats[size]; } }; }

原来它就是用来封装信息的bean,里面个钟hide字段啊,好处是有提供get方法,但是最重要的mLaunchCount,它就是我们想要的打开次数啊,仔细看看,并没有提供get方法,但也没关系,我们可以用反射来拿到它。总算知道个大概了,但是读取的系统文件到底在那里呢。在UsageStatsService里面可以找到

@Override
    public void onStart() {
        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
        mHandler = new H(BackgroundThread.get().getLooper());

        File systemDataDir = new File(Environment.getDataDirectory(), "system");
        mUsageStatsDir = new File(systemDataDir, "usagestats");
        mUsageStatsDir.mkdirs();
        if (!mUsageStatsDir.exists()) {
            throw new IllegalStateException("Usage stats directory does not exist: "
                    + mUsageStatsDir.getAbsolutePath());
        }

        getContext().registerReceiver(new UserRemovedReceiver(),
                new IntentFilter(Intent.ACTION_USER_REMOVED));

        synchronized (mLock) {
            cleanUpRemovedUsersLocked();
        }

        mRealTimeSnapshot = SystemClock.elapsedRealtime();
        mSystemTimeSnapshot = System.currentTimeMillis();

        publishLocalService(UsageStatsManagerInternal.class, new LocalService());
        publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
    }

按照上面文件的路径可以知道,在手机/data/system/usagestats下面,而且不同的时间间隔里面有不同的文件备份,我把自己手机的备份格式贴出来看看


<usagestats version="1" endTime="9096154">
    <packages>
        <package lastTimeActive="8897847" package="com.android.mms" timeActive="4287" lastEvent="2" />
        <package lastTimeActive="9096133" package="com.tencent.mobileqq" timeActive="169783" lastEvent="2" />
        <package lastTimeActive="8913490" package="com.android.settings" timeActive="2022" lastEvent="2" />
        <package lastTimeActive="9096154" package="com.android.pplauncher3" timeActive="26109" lastEvent="1" />
    packages>
    <configurations />
    <event-log>
        <event time="3789717" package="com.android.mms" class="com.android.mms.ui.DialogModeActivity" type="1" />
        <event time="3789730" package="com.android.mms" class="com.android.mms.ui.DialogModeActivity" type="2" />
        <event time="8893573" package="com.android.mms" class="com.android.mms.ui.DialogModeActivity" type="1" />
        <event time="8897847" package="com.android.mms" class="com.android.mms.ui.DialogModeActivity" type="2" />
        <event time="8897877" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="1" />
        <event time="8911373" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="2" />
        <event time="8911468" package="com.android.settings" class="com.android.settings.Settings" type="1" />
        <event time="8913490" package="com.android.settings" class="com.android.settings.Settings" type="2" />
        <event time="8913506" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="1" />
        <event time="8921826" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="2" />
        <event time="8921888" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="1" />
        <event time="8942382" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="2" />
        <event time="8942402" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneGPUPluginProxyActivity" type="1" />
        <event time="9011492" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneGPUPluginProxyActivity" type="2" />
        <event time="9011504" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.QQBrowserDelegationActivity" type="1" />
        <event time="9011529" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.QQBrowserDelegationActivity" type="2" />
        <event time="9011537" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.QQBrowserActivity" type="1" />
        <event time="9029991" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.QQBrowserActivity" type="2" />
        <event time="9030006" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneGPUPluginProxyActivity" type="1" />
        <event time="9059232" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneGPUPluginProxyActivity" type="2" />
        <event time="9059238" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneTransWithKeyboardPluginProxyActivity" type="1" />
        <event time="9060943" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneTransWithKeyboardPluginProxyActivity" type="2" />
        <event time="9060961" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneGPUPluginProxyActivity" type="1" />
        <event time="9061334" package="com.tencent.mobileqq" class="cooperation.qzone.QzoneGPUPluginProxyActivity" type="2" />
        <event time="9061338" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="1" />
        <event time="9065342" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="2" />
        <event time="9065355" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.ChatActivity" type="1" />
        <event time="9067704" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.ChatActivity" type="2" />
        <event time="9067722" package="com.tencent.mobileqq" class="com.tencent.biz.pubaccount.PublicAccountBrowser" type="1" />
        <event time="9076313" package="com.tencent.mobileqq" class="com.tencent.biz.pubaccount.PublicAccountBrowser" type="2" />
        <event time="9076323" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.ChatActivity" type="1" />
        <event time="9078006" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.ChatActivity" type="2" />
        <event time="9078022" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="1" />
        <event time="9086438" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="2" />
        <event time="9086451" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="1" />
        <event time="9090744" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="2" />
        <event time="9090760" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="1" />
        <event time="9096133" package="com.tencent.mobileqq" class="com.tencent.mobileqq.activity.SplashActivity" type="2" />
        <event time="9096154" package="com.android.pplauncher3" class="com.android.pplauncher3.Launcher" type="1" />
    event-log>
usagestats>

可以看到,package节点是主要打开过的应用,但是并没有记录打开次数。这里的所有都对应usage相关的方法,包括读和写,而configuration和events相关的方法也是相同,系统最终读取的就是这里三个节点的东西,而至于app打开次数和怎么触发的,又是很长的一段,后续分析,这里我先放出app打开的次数增加的片段
IntervalStats

void update(String packageName, long timeStamp, int eventType) {
        UsageStats usageStats = getOrCreateUsageStats(packageName);

        // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
        // like double MOVE_TO_BACKGROUND, etc.
        if (eventType == UsageEvents.Event.MOVE_TO_BACKGROUND ||
                eventType == UsageEvents.Event.END_OF_DAY) {
            if (usageStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
                    usageStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
                usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
            }
        }
        usageStats.mLastEvent = eventType;
        usageStats.mLastTimeUsed = timeStamp;
        usageStats.mEndTimeStamp = timeStamp;

        if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
            usageStats.mLaunchCount += 1;
        }

        endTime = timeStamp;
    }

这里的update方法会被上层的其它方法调用,这些方法也都是用aidl去调用的。关键的次数增加大家也看到了,eventtype处于前台的时候后+1.看到这里想必大家也累了,感觉也好像知道这个打开次数的问题了。但问题没有结束,上面的xml文件里面如果细心的同学可能会发现,相同包名下有不同的class。所以系统记录的打开次数并不是精确的,比如打开一次会开启多个Activity,就会记录多次打开,后面的type 2代表后台,1代表前台,打开次数是只算1的。所以要想直接使用系统的统计次数明显是有问题的,这里我就放到下一篇博客来讲。对比系统的次数的测试和如何得到精确的次数。

你可能感兴趣的:(android系统)