Android UsageStatsService 系统数据统计和数据上报

1. 概况

        在项目中,有需求要实现监测手机系统中设备事件和应用事件,然后每隔固定的周期上报给服务器,后台拿到这些数据后,挖掘分析统计出设备的销售数量,安装应用使用活跃度,用户使用时长等有用的价值信息,统计的设备事件:比如手机锁屏,手机解锁, 应用事件:比如应用启动,应用退出,应用安装卸载等数据。

        在调研需求实现的过程中,通过查阅android源码,博客这方面的资料,得出Android在5.1版本之后就有搜集系统数据有自己的服务和API,那么我们就抽丝剥茧的去阅读理解源码,并运用到自己的项目中, 本篇文章讲解内容是基于Android10平台,UsageStatsService 后面简称为USS。

2. UsageStatsService

2.1 定义

        它是收集、聚合和保存应用程序使用数据的服务, 这些数据可以被 AppOps 授权的应用查询。源码路径 framework/base/services/usage/java/com/android/server/usage/下。

2.2 服务启动

        我们来看看UsageStatsService启动的流程:

#我们只看关键代码和流程
#1 /frameworks/base/services/java/com/android/server/SystemServer.java

    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }


#2 
     private void run() {

        ''''''''''''
        // Start services.
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices();
            startCoreServices();  // 在这个方法中启动UsageStatsService服务
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }

    }

#3

     private void startCoreServices() {

        ''''''''''''
        // Tracks application usage stats.
        traceBeginAndSlog("StartUsageService");
        mSystemServiceManager.startService(UsageStatsService.class);
        mActivityManagerService.setUsageStatsManager(
                LocalServices.getService(UsageStatsManagerInternal.class));
        traceEnd();

        ..........
    }

从上可以看出它是由系统SystemServer进程启动的,UsageStatsService 又是继承系统基类SystemService服务,接下来,在看看onStart()生命周期方法中的代码

# framework/base/services/usage/java/com/android/server/usage/UsageStatsService.java

 @Override
 public void onStart() {  
     
  '''''''''''''
        # 第一点
        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());
        }

        # 第二点
        publishLocalService(UsageStatsMMOVE_TO_BACKGROUNDanagerInternal.class, new LocalService());
        publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());

        
        # 第三点
        // Make sure we initialize the data, in case job scheduler needs it early.
        getUserDataAndInitializeIfNeededLocked(UserHandle.USER_SYSTEM, mSystemTimeSnapshot);
    
 ''''''''''''

}


# /frameworks/base/services/core/java/com/android/server/SystemService.java

  protected final void publishBinderService(String name, IBinder service,
            boolean allowIsolated, int dumpPriority) {
        ServiceManager.addService(name, service, allowIsolated, dumpPriority);
  }

第一点: 创建 data/system/usagestats 文件夹,可以得知设备统计数据文件都是放到此目录下的

第二点:  publishBinderService这个方法,就是 USS 向  ServiceManager 注册自己,这样子客户端通过 ServiceManager.getService(Context.USAGE_STATS_SERVICE) 就可以获取USS服务了。 

第三点: 初始化UserUsageStatsService,每个用户(主用户,访客)的USS服务类,它是真正做事情的类,统计有Activity的启动和退出,  Service启动和停止, 分享,锁屏,解锁,设备启动和关机等事件。

 private static String eventToString(int eventType) {
        switch (eventType) {
            case Event.NONE:
                return "NONE";
            case Event.ACTIVITY_PAUSED:
                return "ACTIVITY_PAUSED";
            case Event.ACTIVITY_RESUMED:
                return "ACTIVITY_RESUMED";
            case Event.FOREGROUND_SERVICE_START:
                return "FOREGROUND_SERVICE_START";
            case Event.FOREGROUND_SERVICE_STOP:
                return "FOREGROUND_SERVICE_STOP";
            case Event.ACTIVITY_STOPPED:
                return "ACTIVITY_STOPPED";
            case Event.END_OF_DAY:
                return "END_OF_DAY";
            case Event.ROLLOVER_FOREGROUND_SERVICE:
                return "ROLLOVER_FOREGROUND_SERVICE";
            case Event.CONTINUE_PREVIOUS_DAY:
                return "CONTINUE_PREVIOUS_DAY";
            case Event.CONTINUING_FOREGROUND_SERVICE:
                return "CONTINUING_FOREGROUND_SERVICE";
            case Event.CONFIGURATION_CHANGE:
                return "CONFIGURATION_CHANGE";
            case Event.SYSTEM_INTERACTION:
                return "SYSTEM_INTERACTION";
            case Event.USER_INTERACTION:
                return "USER_INTERACTION";
            case Event.SHORTCUT_INVOCATION:
                return "SHORTCUT_INVOCATION";
            case Event.CHOOSER_ACTION:
                return "CHOOSER_ACTION";
            case Event.NOTIFICATION_SEEN:
                return "NOTIFICATION_SEEN";
            case Event.STANDBY_BUCKET_CHANGED:
                return "STANDBY_BUCKET_CHANGED";
            case Event.NOTIFICATION_INTERRUPTION:
                return "NOTIFICATION_INTERRUPTION";
            case Event.SLICE_PINNED:
                return "SLICE_PINNED";
            case Event.SLICE_PINNED_PRIV:
                return "SLICE_PINNED_PRIV";
            case Event.SCREEN_INTERACTIVE:
                return "SCREEN_INTERACTIVE";
            case Event.SCREEN_NON_INTERACTIVE:
                return "SCREEN_NON_INTERACTIVE";
            case Event.KEYGUARD_SHOWN:
                return "KEYGUARD_SHOWN";
            case Event.KEYGUARD_HIDDEN:
                return "KEYGUARD_HIDDEN";
            case Event.DEVICE_SHUTDOWN:
                return "DEVICE_SHUTDOWN";
            case Event.DEVICE_STARTUP:
                return "DEVICE_STARTUP";
            default:
                return "UNKNOWN_TYPE_" + eventType;
        }
    }

2.3 业务处理

# framework/base/services/usage/java/com/android/server/usage/UsageStatsService.java 


    private static final long TWENTY_MINUTES = 20 * 60 * 1000;
    private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;

 @Override
    public void onStatsUpdated() {
        mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
    }


    class H extends Handler {
        public H(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ........

                case MSG_FLUSH_TO_DISK:
                    flushToDisk();
                    break;

 USS 每20分钟更新一次数据并存入到/data/system/usagestats/路径下的XML文件中,XML的所有操作,例如读,写等,都被封装在类UsageStatsXmlV1.java中,统一由UsageStatsDatabase去操作读写数据。

3. UsageStatsManager

3.1 定义

提供对此设备的使用统计数据的访问。 使用数据的访问时间间隔可以用天、周、月和年。简单理解为客户端对象,用来和USS通信的对象,就好比 AcitivityManager 与 AMS 的关系。我们来看看类主要的方法:

方法作用和用途
queryAndAggregateUsageStats(long beginTime, long endTime)获取指定时间区间内使用统计数据,以应用包名为键值进行数据合并。
queryConfigurations(int intervalType, long beginTime, long endTime)获取指定时间区间内硬件配置信息统计数据。
queryEventStats(int intervalType, long beginTime, long endTime)获取指定时间区间内发生组件状态变化事件统计数据。
queryEvents(long beginTime, long endTime)获取指定时间区间内组件状态变化事件
queryEventsForSelf(long beginTime, long endTime)与queryEvents相似,获取指定时间区间内本应用的组件状态变化事件
queryUsageStats(int intervalType, long beginTime, long endTime获取指定时间区间内应用使用统计数据。

queryEventStats和  queryUsageStats 这两个方法比较重要,与我们的需求强相关:

public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
        try {
            @SuppressWarnings("unchecked")
            ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
                    endTime, mContext.getOpPackageName());
            if (slice != null) {
                return slice.getList();
            }
        } catch (RemoteException e) {
            // fallthrough and return the empty list.
        }
        return Collections.emptyList();
    }



 public List<EventStats> queryEventStats(int intervalType, long beginTime, long endTime) {
        try {
            @SuppressWarnings("unchecked")
            ParceledListSlice<EventStats> slice = mService.queryEventStats(intervalType, beginTime,
                    endTime, mContext.getOpPackageName());
            if (slice != null) {
                return slice.getList();
            }
        } catch (RemoteException e) {
            // fallthrough and return the empty list.
        }
        return Collections.emptyList();
    }

通过返回参数List<UsageStats>   List<EventStats>,我们继续来看看UsageStats EventStats这两个Parcelable 序列化类

3.2  EventStats

它是在指定时间区间内某个类型事件统计数据的封装类,它有一个内部类Event类,这里面才是应用真正的事件统计信息.  我们可以去看看定义哪些事件类型变量:

    /**
     * An event representing a state change for a component.
     */
    public static final class Event {

         //Activity Resume 和 pause 事件 
         public static final int ACTIVITY_RESUMED= 1;
         public static final int ACTIVITY_PAUSED = 2;
          
         public static final int END_OF_DAY = 3;
         public static final int CONTINUE_PREVIOUS_DAY = 4;

         //设备configuration改变事件
         public static final int CONFIGURATION_CHANGE = 5;
         //表示系统以某种方式与应用进行了交互的事件。
         public static final int SYSTEM_INTERACTION = 6;
         //表示用户以某种方式与应用进行了交互的事件。
         public static final int USER_INTERACTION = 7;
         //表示用户进行过快捷方式操作的事件
         public static final int SHORTCUT_INVOCATION = 8;
         //表示用户为 ChooserActivity 选择了一个应用事件
         public static final int CHOOSER_ACTION = 9;
         //用户查看了通知的事件类型
         public static final int NOTIFICATION_SEEN = 10;
         //
         public static final int STANDBY_BUCKET_CHANGED = 11;
         //表示应用发布中断通知的事件类型。
         public static final int NOTIFICATION_INTERRUPTION = 12;
         //
         public static final int SLICE_PINNED_PRIV = 13;
         public static final int SLICE_PINNED = 14;
         //表示屏幕已进入交互状态的事件
         public static final int SCREEN_INTERACTIVE = 15;
         //表示屏幕处于非交互状态事件
         public static final int SCREEN_NON_INTERACTIVE = 16;
         //设备锁屏事件
         public static final int KEYGUARD_SHOWN = 17;
         //设备解锁事件
         public static final int KEYGUARD_HIDDEN = 18;
         //启动一个前台service事件
         public static final int FOREGROUND_SERVICE_START = 19;
         //停止一个前台service事件
         public static final int FOREGROUND_SERVICE_STOP = 20;
         //表示前台服务在时间间隔开始时处于启动状态。
         public static final int CONTINUING_FOREGROUND_SERVICE = 21;
         //表示当统计信息在时间间隔结束时翻转时前台服务处于启动状态。
         public static final int ROLLOVER_FOREGROUND_SERVICE = 22;
         //Activity处于stopped 和 Destoryed 状态
         public static final int ACTIVITY_STOPPED = 23;
         public static final int ACTIVITY_DESTROYED = 24;
         //表示更新数据到磁盘事件
         public static final int FLUSH_TO_DISK = 25;
         // 设备启动和关机事件
         public static final int DEVICE_SHUTDOWN = 26;
         public static final int DEVICE_STARTUP = 27;
  }

常用的共有方法如下:

方法功能
getEventType()获取事件类型

getPackageName()

和UsageStats类中的getPackageName()方法一样,获取应用的包名

getTimeStamp()

发生此事件的时间戳,以毫秒为单位

getClassName()

该方法的作用是获取类名,在UsageStats类中没有此方法,该方法配合getPackageName()方法可以精确的找到是那个应用的那个界面的统计信息

getConfiguration()

和ConfigurationStats类中的getConfiguration()方法一样,获取Configuration类的对象

以上是Event内部类中的主要方法和作用

hasNextEvent()

返回是否有更多要使用的事件读取,没有会返回false,有会返回true,一般在循环读取数据的时候使用

getNextEvent(Event eventOut)

获取应用事件的统计信息,会将数据保存到eventOut中,返回值为boolean型,true表示获取成功,false表示获取失败

3.3  UsageStats

这个类包含针对特定时间范围的应用程序包的使用情况统计信息,这里面的数据就是我们需要的应用使用情况的统计信息,该类里面主要包含了五个方法分别是:

方法功能

getPackageName()

获取应用程序的包名

getFirstTimeStamp()

获取第一次运行的时间,以毫秒为单位

getLastTimeStamp()

获取最后一次运行的时间,以毫秒为单位

getLastTimeUsed()

获取上一次运行的时间,以毫秒为单位

getTotalTimeInForeground()

获取应用在前台的总时间,以毫秒为单位

getLaunchCount()

获取应用的启动次数 这个是通过反射的方式获取的,没有直接的API,如下:

   // 利用反射,获取UsageStats中统计的应用使用次数
    public int getLaunchCount(UsageStats usageStats) throws IllegalAccessException {
        Field field = null;
        try {
            field = usageStats.getClass().getDeclaredField("mLaunchCount");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return (int) field.get(usageStats);
    }

4. 需求实现

把上面必备的知识了解清楚后,开始着手我们的需求实现, 通过上述API方法把需要的事件类型统计出来,然后上报给服务器端。实现大致逻辑:通过开机广播启动一个自定义service,此服务的作用整理搜集起来的数据,每隔一段时间上报给服务器。

4.1 定义权限

如果要使用这些API方法的话,首先您必须先在清单中声明 android.permission.PACKAGE_USAGE_STATS 权限,用户还必须通过 Settings > Security > Apps 为该应用启用访问使用情况的权限

  public static void checkUsageStateAccessPermission(Context context) {
        if(!AppUsageUtil.checkAppUsagePermission(context)) {
            AppUsageUtil.requestAppUsagePermission(context);
        }
    }

    public static boolean checkAppUsagePermission(Context context) {
        UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
        if(usageStatsManager == null) {
            return false;
        }
        long currentTime = System.currentTimeMillis();
        // try to get app usage state in last 1 min
        List<UsageStats> stats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, currentTime - 60 * 1000, currentTime);
        if (stats.size() == 0) {
            return false;
        }

        return true;
    }

    public static void requestAppUsagePermission(Context context) {
        Intent intent = new Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            context.startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Log.i(TAG,"Start usage access settings activity fail!");
        }
    }

4.2 统计手机在一天中锁屏和解锁事件

这个需求的用途:可以用来衡量用户在一天中使用手机的频率,以这个小需求做为例子讲解

# 1.BootReceiver.java
#此类的作用就是接收开机广播,然后启动自己搜集数据的服务类。

public class BOOTReceiver extends BroadcastReceiver {
    private static final String TAG = "BOOTReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (null == context) {
            return;
        }
        Log.d(TAG, "BOOTReceiver");
        try {
           context.startService(new Intent(context, MyUsageStatsService.class));
        } catch (Exception e) {
           Log.e(TAG, "    " + e.getMessage());
        }
    }
}

获取系统数据UsageEvents , UsageStats 工具类

/**
 * 获取系统的数据,包括event和Usage
 */

public class EventUtils {

    public  static final String TAG  = "EventUtils";
    private static final SimpleDateFormat dateFormat  = new SimpleDateFormat("M-d-yyyy HH:mm:ss");

    @SuppressWarnings("ResourceType")
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static ArrayList<UsageEvents.Event> getEventList(Context context, long startTime, long endTime){
        ArrayList<UsageEvents.Event> mEventList = new ArrayList<>();

        UsageStatsManager mUsmManager = (UsageStatsManager) context.getSystemService("usagestats");
        UsageEvents events = mUsmManager.queryEvents(startTime, endTime);

        while (events.hasNextEvent()) {
            UsageEvents.Event e = new UsageEvents.Event();
            events.getNextEvent(e);
            if (e != null) {
                mEventList.add(e);
            }
        }

        return mEventList;
    }

    @SuppressWarnings("ResourceType")
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static ArrayList<UsageStats> getUsageList(Context context, long startTime, long endTime) {

        ArrayList <UsageStats> list = new ArrayList<>();

        UsageStatsManager mUsmManager = (UsageStatsManager) context.getSystemService("usagestats");
        Map<String, UsageStats> map = mUsmManager.queryAndAggregateUsageStats(startTime, endTime);
        for (Map.Entry<String, UsageStats> entry : map.entrySet()) {
            UsageStats stats = entry.getValue();
            list.add(stats);
        }
        return list;
    }
}

下面这个类是搜集和封装数据类:

public class UseTimeDataManager {
    
    //锁屏和解锁事件字段
    public static final int KEYGUARD_SHOWN = 17;
    public static final int KEYGUARD_HIDDEN = 18;
    
    //上报给服务器的字段
    public static final int KEYGUARD_SHOWN_REPORT = 13;
    public static final int KEYGUARD_HIDDEN_REPORT = 14;


    //记录从系统中读取的数据
    private ArrayList<UsageEvents.Event> mEventList;


    //设备锁屏数据
    private ArrayList<UsageEvents.Event> mDeviceLockedChecked;

    //设备锁屏 ArrayList上报数据集合
    private ArrayList<DeviceEvent> mDeviceLockedObject = new ArrayList<>();

    public ArrayList<DeviceEvent> getDeviceLockedObject() {
        return mDeviceLockedObject;
    }

    //设备解锁数据
    private ArrayList<UsageEvents.Event> mDeviceUnLockedChecked;

    //设备解锁 ArrayList上报数据集合
    private ArrayList<DeviceEvent> mDeviceUnLockedObject = new ArrayList<>();

    public ArrayList<DeviceEvent> getDeviceUnLockedObject() {
        return mDeviceUnLockedObject;
    }


    //从系统中获取event数据
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private ArrayList<UsageEvents.Event> getEventList(int dayNumber) {
        ArrayList<UsageEvents.Event> mEventList = new ArrayList<>();

        long endTime = 0, startTime = 0;
        if (dayNumber == 0) {
            endTime = System.currentTimeMillis();
            startTime = DateTransUtils.getZeroClockTimestamp(endTime);
        } else {
            endTime = DateTransUtils.getZeroClockTimestamp(System.currentTimeMillis() - (dayNumber - 1) * DateTransUtils.DAY_IN_MILLIS) - 1;
            startTime = endTime - DateTransUtils.DAY_IN_MILLIS + 1;
        }
        return EventUtils.getEventList(mContext, startTime, endTime);
    }
    
    //构造方法
    public UseTimeDataManager() {
        // 这个dayNumber变量,表示获取前xx天的数据
        mEventList = getEventList(dayNumber);

        mDeviceLockedChecked = getActionEventChecked(KEYGUARD_SHOWN);
        generateDeviceLockedObject(mDeviceLockedChecked);

        mDeviceUnLockedChecked = getActionEventChecked(KEYGUARD_HIDDEN);
        generateDeviceUnLockedObject(mDeviceUnLockedChecked);

    }


     //根据事件类型来获取相应的数据
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private ArrayList<UsageEvents.Event> getActionEventChecked(int eventType) {
        ArrayList<UsageEvents.Event> mList = new ArrayList<>();
        for (int i = 0; i < mEventList.size(); i++) {
            if (mEventList.get(i).getEventType() == eventType) {
                mList.add(mEventList.get(i));
            }
        }
        return mList;
    }


    //通过封装DeviceEvent这个javaBean对象,转换成json数据上报给服务器
    private void generateDeviceUnLockedObject(ArrayList<UsageEvents.Event> deviceUnLockedChecked) {
        for (int i=0; i<deviceUnLockedChecked.size(); i++) {
            mDeviceUnLockedObject.add(new DeviceEvent(
                    mContext,
                    deviceUnLockedChecked.get(i).getTimeStamp(),
                    KEYGUARD_HIDDEN_REPORT
            ));
        }
    }


    private void generateDeviceLockedObject(ArrayList<UsageEvents.Event> deviceLockedChecked) {
        for (int i=0; i<deviceLockedChecked.size(); i++) {
            mDeviceLockedObject.add(new DeviceEvent(
                    mContext,
                    deviceLockedChecked.get(i).getTimeStamp(),
                    KEYGUARD_SHOWN_REPORT
            ));
        }
    }

}

最后我们来看服务端MyUsageStatsService.java, 每隔1天上报一次数据,对时间间隔要求比较准备,所以我用的是AlarmManager + Broadcast 的组合,用AlarmManager 每隔24小时去启动一次广播,然后在广播里面在启动MyUsageStatsService, 因为MyUsageStatsService在第一次开机已经被启动过,当再次调用startService()方法时,不会走onCreate 只会走onStartCommand 方法。我们把上报数据的任务就放在onStartCommand生命周期方法中。

public class MyUsageStatsService extends Service {

    
     @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

            //通过AlamrManager + Broadcast 循环(每一天)上报数据的任务
            Log.d(TAG, " MyUsageStatsService onStartCommand,  Report datas once a day");
            executeTasksLooper(mContext);
            
            //每隔一天唤醒广播一次,广播里面在启动MyUsageStatsService 
            Intent intent1 = new Intent(this, WakeServiceReceiver.class);
            generateCycleTask(intent1, WAKE_SERVICE_USAGE, AlarmManager.INTERVAL_DAY);

    }


    private void executeTasksLooper(Context mContext) {
        ''''''''''''''''
        //这里是上报服务数据的地方
    }


      //每24小时循环唤醒请求任务
    public void generateCycleTask(Intent intent, String action, long interval){
        AlarmManager alarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
        intent.setAction(action);
        long intervalTime = SystemClock.elapsedRealtime() + interval;
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
        alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, intervalTime, pendingIntent);
    }

}

此广播是专用来唤醒MyUsageStatsService服务类的

public class WakeServiceReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent i) {
        if (null == context) {
           return;
        }
        try {
            context.startService(new Intent(context, MyUsageStatsService.class));
            Log.d("UseTimeDataManager", "start MyUsageStatsService from WakeServiceReceiver : report data recyle task");
        } catch (Exception e) {
            Log.d("UseTimeDataManager", "WakeServiceReceiver start service failed    :" + e.getMessage());
        }
    }
}

通过上述代码,就可以实现每隔24小时上报数据给服务器端,封装的DeviceEvent javaBean对象就是一些关于手机型号,内存大小,安卓版本号,制造商等信息,就不细列出来了。这里的循环任务就是通过AlarmManager 和 Broadcast 联合一起实现的。当然还有上报给服务器端的代码没有贴出来,由于是公司的项目,不好全部码出,只说明大致思路,供参考。

在UsageEvents.Event中定义的27种事件类型,参考上述代码,需要数据上报的话,都是可以实现的。还有需求是关于应用启动和退出的上报,这类数据可以用来衡量APP的活跃程度,我是参考USS获取系统数据的原理在Fwk层中实现的,应用启动依据这个app新创建task, 应用退出依据是这个task已销毁,如果想要了解具体实现的话,可以私我沟通讨论下。

另外,AlarmManager + Broadcast 循环启动任务的Demo已上传,供参考。AlarmManager+Broadcast循环启动任务-Android文档类资源-CSDN下载

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值