Android5.1应用统计源码分析

来源:互联网 发布:磁贴数据库已损坏 编辑:程序博客网 时间:2024/05/21 10:09

在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. * <p /> * When requesting usage data since a particular time, the request might look something like this: * <pre> * PAST                   REQUEST_TIME                    TODAY                   FUTURE * ————————————————————————————||———————————————————————————¦-----------------------| *                        YEAR ||                           ¦                       | * ————————————————————————————||———————————————————————————¦-----------------------| *  MONTH            |         ||                MONTH      ¦                       | * ——————————————————|—————————||———————————————————————————¦-----------------------| *   |      WEEK     |     WEEK||    |     WEEK     |     WE¦EK     |      WEEK     | * ————————————————————————————||———————————————————|———————¦-----------------------| *                             ||           |DAY|DAY|DAY|DAY¦DAY|DAY|DAY|DAY|DAY|DAY| * ————————————————————————————||———————————————————————————¦-----------------------| * </pre> * A request for data in the middle of a time interval will include that interval. * <p/> * <b>NOTE:</b> 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.     * <p>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:</p>     * <pre>     * 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     * </pre>     *     * @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<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {        try {            ParceledListSlice<UsageStats> 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.     * <p />     * <b>NOTE:</b> 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<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {        List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);        if (stats.isEmpty()) {            @SuppressWarnings("unchecked")            ArrayMap<String, UsageStats> emptyStats = ArrayMap.EMPTY;            return emptyStats;        }        ArrayMap<String, UsageStats> 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<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {        return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);    }    List<ConfigurationStats> 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 <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,            StatCombiner<T> 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<T> 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<T> 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<AtomicFile>[] 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 <T> List<T> queryUsageStats(int intervalType, long beginTime, long endTime,            StatCombiner<T> combiner) {        synchronized (mLock) {            if (intervalType < 0 || intervalType >= mIntervalDirs.length) {                throw new IllegalArgumentException("Bad interval type " + intervalType);            }            final TimeSparseArray<AtomicFile> 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<T> 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     * <code><usagestats></code> 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;            }        }    }//最后调用到loadUsageStatsprivate 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.     * <p/>     * 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.     * <p/>     * See {@link System#currentTimeMillis()}.     */    public long getLastTimeStamp() {        return mEndTimeStamp;    }    /**     * Get the last time this package was used, measured in milliseconds since the epoch.     * <p/>     * 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<UsageStats> CREATOR = new Creator<UsageStats>() {        @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下面,而且不同的时间间隔里面有不同的文件备份,我把自己手机的备份格式贴出来看看

<?xml version='1.0' encoding='utf-8' standalone='yes' ?><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的。所以要想直接使用系统的统计次数明显是有问题的,这里我就放到下一篇博客来讲。对比系统的次数的测试和如何得到精确的次数。

1 0
原创粉丝点击