Android UsageStatsService:要点解析
16lz
2021-01-23
1、UsageStatsService作用是什么?
这是一个Android私有service,主要作用是收集用户使用每一个APP的频率、使用时常;
2、如何通过UsageStatsService获取用户使用APP的数据?
(1)必须要具备系统权限;(APP内置在/system/app下)
(2)必须要在manifest中申明权限:PACKAGE_USAGE_STATS;例如:
(3)调用UsageStatsService.getAllPkgUsageStats or UsageStatsService.getPkgUsageStats 接口获取用户使用APP频率:
//相当于: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 componentResumeMap = (Map) cPkgUsageStats.getDeclaredField("componentResumeTimes").get(pkgUsageStats); }
3、UsageStatsService工作原理是什么? (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,记录该包下的Activity名以及该Activity最后启动时间 final private Map> mLastResumeTimes;
从“/data/system/usagestats/usage-当前日志”解析出来的数据,放在:
// String为包名,PkgUsageStatsExtended存储相应的启动信息 final private Map mStats;
可以发现此处封装了一个内部类PkgUsageStatsExtended做数据存储,其实PkgUsageStatsExtended还封装了许多操作,后面会逐步涉及,目前PkgUsageStatsExtended主要存储了这些信息:
private class PkgUsageStatsExtended { //每个Activity最后启动时间 final HashMap mLaunchTimes = new HashMap(); //当前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 componentResumeTimes = mLastResumeTimes.get(pkgName); if (componentResumeTimes == null) { componentResumeTimes = new HashMap(); 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); }
(3-5)现在,我们已经明白APP启动数据如何在内存中流转了,那么什么时候系统将数据持久化成文件? UsageStatsService通过writeStatsToFile方法将数据持久化成文件,函数原型如下:
/** * 在特定条件下,将mStats或者mLastResumeTimes写入到文件中,由用户操作触发 * @params force 强制将mStats实例化到文件中 * * @params forceWriteHistoryStats 强制将mLastResumeTimes实例化到文件中 */ private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) { }
具体触发writeStatsToFile的时机有: 1、用户关机,触发writeStatsToFile(true, true); 2、用户打开一个新应用,触发writeStatsToFile(false,false); 3、在notePauseComponent最后,调用writeStatsToFile(false,false);
writeStatsToFile触发写文件操作的条件有: 1、关机强制触发; 2、当前日期与最后一次写文件日期不同; 3、除用户关机外,两次写文件间隔必须在30分钟以上;
(3-6)卸载APP后,已经记录的数据会被清除;
更多相关文章
- andorid 将布局文件(layout)转换为图片(Bitmap)简单使用详解
- Android数据库应用(《Android开发入门与实战》选摘)
- Android中使用Gson解析JSON数据,以及把JSON数据映射成一个对象
- Storm——Android SQLite数据库管理类库
- Android 监听ContentProvider中数据的变化 Android 监听ContentP
- Android 存储字符串数据到txt文件
- 如何简单修改Android的so文件
- Android视频文件格式解析相关分析
- 使用SAX或者DOM或者pull解析XML文件