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的。所以要想直接使用系统的统计次数明显是有问题的,这里我就放到下一篇博客来讲。对比系统的次数的测试和如何得到精确的次数。
- Android5.1应用统计源码分析
- Android5.1启动流程源码分析
- Android5.0 源码分析--- Launcher启动应用的过程
- Android5.1源码分析系列(一)Settings源码分析
- android5.1应用打开次数统计(优化篇)
- Android5.1系统自带的应用启动次数统计
- PackageManagerService(Android5.1)深入分析(三)扫描应用目录
- PackageManagerService(Android5.1)深入分析(四)安装应用
- Android framework 应用安装流程 分析 PackageManagerService(Android5.1)
- Android5.1 -Recents分析
- PackageManagerService(Android5.1)分析
- Android5.0以太网流程源码情景分析
- Android5.0以太网流程源码情景分析
- Android5.0以太网流程源码情景分析
- Android5.0以太网流程源码情景分析
- Android5.0 广播机制源码分析
- android5.1 RelativeLayout源码浅析
- android5.1 FrameLayout源码浅析
- 设置eclipse编码方式
- 搬瓦工安装Netspeeder加速教程
- 线程之间的数据传输之AsyncTask
- Android Binder 机制(超级详尽): reference 1#
- [AC自动机+数位DP] ZOJ3494 BCD Code
- Android5.1应用统计源码分析
- adb基本命令和Logcat基本命令
- Android 学习资料收集
- 关于centos7中使用rpm方式安装mysql5.7版本后无法使用root登录的问题
- Android深入浅出之Binder机制: Reference 2#
- Java - Socket example: EchoClient and EchoServer
- codevs2178 表达式运算Cuties
- 关于Android中textView的背景设置
- SELECT查询结果排序