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


更多相关文章

  1. andorid 将布局文件(layout)转换为图片(Bitmap)简单使用详解
  2. Android数据库应用(《Android开发入门与实战》选摘)
  3. Android中使用Gson解析JSON数据,以及把JSON数据映射成一个对象
  4. Storm——Android SQLite数据库管理类库
  5. Android 监听ContentProvider中数据的变化 Android 监听ContentP
  6. Android 存储字符串数据到txt文件
  7. 如何简单修改Android的so文件
  8. Android视频文件格式解析相关分析
  9. 使用SAX或者DOM或者pull解析XML文件

随机推荐

  1. 微软Edge浏览器准备内置屏蔽广告功能
  2. 嗯,我来杭州了。
  3. 数据库两个神器索引和锁(修订版)
  4. 一个70后“大叔”的PMP学习考证之路
  5. 微软分析Pypi数据: 5月21日Python3战胜Py
  6. 一个有趣的360度照相机的开源项目:树莓派+
  7. Win10与Ubuntu合体详细解读(附视频)
  8. oracle安装rlwrap
  9. Powershell如何远程 添加管理员
  10. 过滤器入门看这一篇就够了(修订版)