1、UsageStatsService作用是什么?
这是一个Android私有service,主要作用是收集用户使用每一个APP的频率、使用时常;
2、如何通过UsageStatsService获取用户使用APP的数据?
(1)必须要具备系统权限;(APP内置在/system/app下)
(2)必须要在manifest中申明权限:PACKAGE_USAGE_STATS;例如:
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
(3)调用UsageStatsService.getAllPkgUsageStats or UsageStatsService.getPkgUsageStats 接口获取用户使用APP频率:
<span style="white-space:pre"> </span> //相当于:IBinder oRemoteService = ServiceManager.getService("usagestats");
Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
Method mGetService = cServiceManager.getMethod("getService", java.lang.String.class);
Object oRemoteService = mGetService.invoke(null, "usagestats");
// 相当于:IUsageStats mUsageStatsService = IUsageStats.Stub.asInterface(oRemoteService)
Class<?> cStub = Class.forName("com.android.internal.app.IUsageStats$Stub");
Method mUsageStatsService = cStub.getMethod("asInterface", android.os.IBinder.class);
Object oIUsageStats = mUsageStatsService.invoke(null, oRemoteService);
// 相当于:PkgUsageStats[] oPkgUsageStatsArray =mUsageStatsService.getAllPkgUsageStats();
Class<?> cIUsageStatus = Class.forName("com.android.internal.app.IUsageStats");
Method mGetAllPkgUsageStats = cIUsageStatus.getMethod("getAllPkgUsageStats", (Class[]) null);
Object[] oPkgUsageStatsArray = (Object[]) mGetAllPkgUsageStats.invoke(oIUsageStats, (Object[]) null);
//相当于
//for (PkgUsageStats pkgUsageStats: oPkgUsageStatsArray)
//{
// 当前APP的包名:
// packageName = pkgUsageStats.packageName
// 当前APP的启动次数
// launchCount = pkgUsageStats.launchCount
// 当前APP的累计使用时间:
// usageTime = pkgUsageStats.usageTime
// 当前APP的每个Activity的最后启动时间
// componentResumeTimes = pkgUsageStats.componentResumeTimes
//}
Class<?> cPkgUsageStats = Class.forName("com.android.internal.os.PkgUsageStats");
for (Object pkgUsageStats : oPkgUsageStatsArray) {
String packageName = (String) cPkgUsageStats.getDeclaredField("packageName").get(pkgUsageStats);
int launchCount = cPkgUsageStats.getDeclaredField("launchCount").getInt(pkgUsageStats);
long usageTime = cPkgUsageStats.getDeclaredField("usageTime").getLong(pkgUsageStats);
Map<String, Long > componentResumeMap = (Map<String, Long>) cPkgUsageStats.getDeclaredField("componentResumeTimes").get(pkgUsageStats);
}
(3-1)首先是service的初始化,主要分为两步;
第一步是从/data/system/usagestats/usage-history.xml文件中读取每个APP中每个Activity最后启动的时间;
//初始化/data/system/usagestats/usage-history.xml
mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
//解析xml文件,将xml解析成为数据,并存储
readStatsFromFile();
第二步是从“/data/system/usagestats/usage-当前日志”文件中解析今天的使用记录的数据:
//指定文件前缀为usage,根据日志生成文件后缀,例如usage-20140817
mFileLeaf = getCurrentDateStr(FILE_PREFIX);
//解析文件,为文件流的格式,直接读取即可
readStatsFromFile();
其中“/data/system/usagestats/usage-当前日志”文件格式为2精制的序列化流,可直接从中读取相应的对象即可;如果对应文件不存在,则创建它;
(3-2)在初始化service完成后,需要组数据存储:
从usage-history.xml文件中解析出来的数据,放在:
// key为包名,value为Map<String, Long>,记录该包下的Activity名以及该Activity最后启动时间
final private Map<String, Map<String, Long>> mLastResumeTimes;
// String为包名,PkgUsageStatsExtended存储相应的启动信息
final private Map<String, PkgUsageStatsExtended> mStats;
private class PkgUsageStatsExtended {
//每个Activity最后启动时间
final HashMap<String, TimeStats> mLaunchTimes
= new HashMap<String, TimeStats>();
//当前APP启动次数
int mLaunchCount;
//当前APP使用时间
long mUsageTime;
//当前APP最后一次调用onPause时间
long mPausedTime;
//当前APP最后一个调用onResume时间
long mResumedTime;
//更新mResumedTime为当前时间
void updateResume(String comp, boolean launched) {
if (launched) {
mLaunchCount ++;
}
mResumedTime = SystemClock.elapsedRealtime();
}
//更新mPausedTime为当前时间,并且使用时常=mPausedTime - mResumedTime
void updatePause() {
mPausedTime = SystemClock.elapsedRealtime();
mUsageTime += (mPausedTime - mResumedTime);
}
//记录activity次数
void addLaunchCount(String comp) {
TimeStats times = mLaunchTimes.get(comp);
if (times == null) {
times = new TimeStats();
mLaunchTimes.put(comp, times);
}
times.incCount();
}
//记录activity启动时间
void addLaunchTime(String comp, int millis) {
TimeStats times = mLaunchTimes.get(comp);
if (times == null) {
times = new TimeStats();
mLaunchTimes.put(comp, times);
}
times.add(millis);
}
/*more code
*/
}
(3-3)当应用启动一个Activity时,UsageStatsService会发生什么行为?
ActivityManagerService会调用UsageStatsService.noteResumeComponent方法,在该方法中会有以下操作:
public void noteResumeComponent(ComponentName componentName) {
enforceCallingPermission();
String pkgName;
synchronized (mStatsLock) {
/*some code
*/
final boolean samePackage = pkgName.equals(mLastResumedPkg);
//1、mIsResumed会在onResume中变为true,在onPause中变为false
if (mIsResumed) {
if (mLastResumedPkg != null) {
//2、这里是为了避免没有调用onPause的情况出现,理论上不存在
PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
if (pus != null) {
//3、增加保护,调用一次updatePause
pus.updatePause();
}
}
}
final boolean sameComp = samePackage
&& componentName.getClassName().equals(mLastResumedComp);
//5、内部数据更新,记录最后一次启动的Activity
mIsResumed = true;
mLastResumedPkg = pkgName;
mLastResumedComp = componentName.getClassName();
PkgUsageStatsExtended pus = mStats.get(pkgName);
if (pus == null) {
pus = new PkgUsageStatsExtended();
mStats.put(pkgName, pus);
}
//6、更新Activity启动时间,如果用户是从一个App启动进入另外一个APP,那么需要App标识启动次数+1
pus.updateResume(mLastResumedComp, !samePackage);
if (!sameComp) {
//7、同上,Activity启动次数+1
pus.addLaunchCount(mLastResumedComp);
}
Map<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
if (componentResumeTimes == null) {
componentResumeTimes = new HashMap<String, Long>();
mLastResumeTimes.put(pkgName, componentResumeTimes);
}
//8、更新componentResumeTimes
componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
}
}
(3-4)当退出一个Activity,UsageStatsService会发生什么行为?
ActivityManagerService会调用UsageStatsService.notePauseComponent方法,UsageStatsService会更新当前展示Activity的OnPause时间:
public void notePauseComponent(ComponentName componentName) {
/*some code
*/
synchronized (mStatsLock) {
/*some code
*/
//1、mIsResumed会在onResume中变为true,在onPause中变为false
mIsResumed = false;
PkgUsageStatsExtended pus = mStats.get(pkgName);
if (pus == null) {
// Weird some error here
Slog.i(TAG, "No package stats for pkg:"+pkgName);
return;
}
//2、更新onPause的时间
pus.updatePause();
}
//3、视情况而定确认是否需要将内存数据保存成文件
writeStatsToFile(false, false);
}
UsageStatsService通过writeStatsToFile方法将数据持久化成文件,函数原型如下:
/**
* 在特定条件下,将mStats或者mLastResumeTimes写入到文件中,由用户操作触发
* @params force 强制将mStats实例化到文件中
*
* @params forceWriteHistoryStats 强制将mLastResumeTimes实例化到文件中
*/
private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
}
1、用户关机,触发writeStatsToFile(true, true);
2、用户打开一个新应用,触发writeStatsToFile(false,false);
3、在notePauseComponent最后,调用writeStatsToFile(false,false);
writeStatsToFile触发写文件操作的条件有:
1、关机强制触发;
2、当前日期与最后一次写文件日期不同;
3、除用户关机外,两次写文件间隔必须在30分钟以上;
(3-6)卸载APP后,已经记录的数据会被清除;