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.
 * <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;
            }
        }
    }

//最后调用到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.
     * <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的。所以要想直接使用系统的统计次数明显是有问题的,这里我就放到下一篇博客来讲。对比系统的次数的测试和如何得到精确的次数。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值