Android UsageStatsService:要点解析

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后,已经记录的数据会被清除;



你可能感兴趣的:(Android)