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