1.概述
UsageStatsService 它是收集、聚合和保存应用程序使用数据的服务, 这些数据可以被 AppOps 授权的应用查询。源码路径 framework/base/services/usage/java/com/android/server/usage/下。
UserUsageStatsService 用于收集、聚合和保存用户 应用程序使用数据的服务, 这些数据可以被 AppOps 授权的应用查询。源码路径 framework/base/services/usage/java/com/android/server/usage/下,由UsageStatsService 创建并启动。
2.服务的启动
#我们只看关键代码和流程 #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服务
frameworks\base\services\core\java\com\android\server\SystemService.java
执行流程主要是 onBootPhase---》onStart---》onUserStarting---》onUserUnlocking---》onUserUnlocked---》onUserSwitching---》onUserStopping---》onUserStopped
接下来,在看看onStart()生命周期方法中的代码
@Override public void onStart() { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mPackageManager = getContext().getPackageManager(); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mHandler = new H(BackgroundThread.get().getLooper()); mAppStandby = mInjector.getAppStandbyController(getContext()); mAppTimeLimit = new AppTimeLimitController(getContext(), new AppTimeLimitController.TimeLimitCallbackListener() { @Override public void onLimitReached(int observerId, int userId, long timeLimit, long timeElapsed, PendingIntent callbackIntent) { if (callbackIntent == null) return; Intent intent = new Intent(); intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId); intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit); intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed); try { callbackIntent.send(getContext(), 0, intent); } catch (PendingIntent.CanceledException e) { Slog.w(TAG, "Couldn't deliver callback: " + callbackIntent); } } @Override public void onSessionEnd(int observerId, int userId, long timeElapsed, PendingIntent callbackIntent) { if (callbackIntent == null) return; Intent intent = new Intent(); intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId); intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed); try { callbackIntent.send(getContext(), 0, intent); } catch (PendingIntent.CanceledException e) { Slog.w(TAG, "Couldn't deliver callback: " + callbackIntent); } } }, mHandler.getLooper()); mAppStandby.addListener(mStandbyChangeListener); mPackageMonitor.register(getContext(), null, UserHandle.ALL, true); IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter, null, mHandler); mRealTimeSnapshot = SystemClock.elapsedRealtime(); mSystemTimeSnapshot = System.currentTimeMillis(); //第二点 publishLocalService(UsageStatsManagerInternal.class, new LocalService()); publishLocalService(AppStandbyInternal.class, mAppStandby); publishBinderServices(); mHandler.obtainMessage(MSG_ON_START).sendToTarget(); }
# 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_ce/0/usagestats/ 文件夹,可以得知设备统计数据文件都是放到此目录下的
第二点: publishBinderService这个方法,就是 USS 向 ServiceManager 注册自己,这样子客户端通过 ServiceManager.getService(Context.USAGE_STATS_SERVICE) 就可以获取USS服务了。
第三点: 初始化UserUsageStatsService,每个用户(主用户,访客)的USS服务类,它是真正做事情的类,统计有Activity的启动和退出, Service启动和停止, 分享,锁屏,解锁,设备启动和关机等事件。
在onUserStarting 方法中创建用户对应的UserUsageStatsService
@Override public void onUserStarting(@NonNull TargetUser user) { // Create an entry in the user state map to indicate that the user has been started but // not necessarily unlocked. This will ensure that reported events are flushed to disk // event if the user is never unlocked (following the logic in #flushToDiskLocked) mUserState.put(user.getUserIdentifier(), null); }
@Override public void onUserUnlocking(@NonNull TargetUser user) { mHandler.obtainMessage(MSG_UNLOCKED_USER, user.getUserIdentifier(), 0).sendToTarget(); }
case MSG_UNLOCKED_USER: try { onUserUnlocked(msg.arg1); } catch (Exception e) { if (mUserManager.isUserUnlocked(msg.arg1)) { throw e; // rethrow exception - user is unlocked } else { Slog.w(TAG, "Attempted to unlock stopped or removed user " + msg.arg1); } } break;
private void onUserUnlocked(int userId) { // fetch the installed packages outside the lock so it doesn't block package manager. final HashMap<String, Long> installedPackages = getInstalledPackages(userId); // delay updating of package mappings for user 0 since their data is not likely to be stale. // this also makes it less likely for restored data to be erased on unexpected reboots. if (userId == UserHandle.USER_SYSTEM) { UsageStatsIdleService.scheduleUpdateMappingsJob(getContext()); } final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId)); synchronized (mLock) { // This should be safe to add this early. Other than reportEventOrAddToQueue, every // other user grabs the lock before accessing // mUserUnlockedStates. reportEventOrAddToQueue does not depend on anything other than // mUserUnlockedStates, and the lock will protect the handler. mUserUnlockedStates.add(userId); // Create a user unlocked event to report final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime()); unlockEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME; migrateStatsToSystemCeIfNeededLocked(userId); // Read pending reported events from disk and merge them with those stored in memory final LinkedList<Event> pendingEvents = new LinkedList<>(); loadPendingEventsLocked(userId, pendingEvents); final LinkedList<Event> eventsInMem = mReportedEvents.get(userId); if (eventsInMem != null) { pendingEvents.addAll(eventsInMem); } boolean needToFlush = !pendingEvents.isEmpty(); initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(), installedPackages, deleteObsoleteData); final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId); if (userService == null) { Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId); return; } // Process all the pending reported events while (pendingEvents.peek() != null) { reportEvent(pendingEvents.poll(), userId); } reportEvent(unlockEvent, userId); // Remove all the stats stored in memory and in system DE. mReportedEvents.remove(userId); deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats")); // Force a flush to disk for the current user to ensure important events are persisted. // Note: there is a very very small chance that the system crashes between deleting // the stats above from DE and persisting them to CE here in which case we will lose // those events that were in memory and deleted from DE. (b/139836090) if (needToFlush) { userService.persistActiveStats(); } } }
/** * Initializes the given user's usage stats service - this should ideally only be called once, * when the user is initially unlocked. */ 创建UUSS UserUsageStatsService private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis, HashMap<String, Long> installedPackages, boolean deleteObsoleteData) { final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), "usagestats"); final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this); try { service.init(currentTimeMillis, installedPackages, deleteObsoleteData); mUserState.put(userId, service); } catch (Exception e) { if (mUserManager.isUserUnlocked(userId)) { Slog.w(TAG, "Failed to initialized unlocked user " + userId); throw e; // rethrow the exception - user is unlocked } else { Slog.w(TAG, "Attempted to initialize service for stopped or removed user " + userId); } } }
将使用信息迁移到 system_ce 目录下
private void migrateStatsToSystemCeIfNeededLocked(int userId) { final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), "usagestats"); if (!usageStatsDir.mkdirs() && !usageStatsDir.exists()) { throw new IllegalStateException("Usage stats directory does not exist: " + usageStatsDir.getAbsolutePath()); } // Check if the migrated status file exists - if not, migrate usage stats. final File migrated = new File(usageStatsDir, "migrated"); if (migrated.exists()) { try (BufferedReader reader = new BufferedReader(new FileReader(migrated))) { final int previousVersion = Integer.parseInt(reader.readLine()); // UsageStatsDatabase.BACKUP_VERSION was 4 when usage stats were migrated to CE. if (previousVersion >= 4) { deleteLegacyDir(userId); return; } // If migration logic needs to be changed in a future version, do it here. } catch (NumberFormatException | IOException e) { Slog.e(TAG, "Failed to read migration status file, possibly corrupted."); deleteRecursively(usageStatsDir); if (usageStatsDir.exists()) { Slog.e(TAG, "Unable to delete usage stats CE directory."); throw new RuntimeException(e); } else { // Make the directory again since previous migration was not complete if (!usageStatsDir.mkdirs() && !usageStatsDir.exists()) { throw new IllegalStateException("Usage stats directory does not exist: " + usageStatsDir.getAbsolutePath()); } } } } Slog.i(TAG, "Starting migration to system CE for user " + userId); final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId)); if (legacyUserDir.exists()) { copyRecursively(usageStatsDir, legacyUserDir); } // Create a status file to indicate that the migration to CE has been completed. try (BufferedWriter writer = new BufferedWriter(new FileWriter(migrated))) { writer.write(Integer.toString(UsageStatsDatabase.BACKUP_VERSION)); writer.write("\n"); writer.flush(); } catch (IOException e) { Slog.e(TAG, "Failed to write migrated status file"); throw new RuntimeException(e); } Slog.i(TAG, "Finished migration to system CE for user " + userId); // Migration was successful - delete the legacy directory deleteLegacyDir(userId); }
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;
/** * Called by the Handler for message MSG_FLUSH_TO_DISK. */ void flushToDisk() { synchronized (mLock) { // Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app // usage. In case of abrupt power shutdown like battery drain or cold temperature, // all UsageStats has correct data up to last flush to disk. // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats' // EventList. Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime()); event.mPackage = DEVICE_EVENT_PACKAGE_NAME; reportEventToAllUserId(event); flushToDiskLocked(); } mAppStandby.flushToDisk(); }
private void flushToDiskLocked() { final int userCount = mUserState.size(); for (int i = 0; i < userCount; i++) { final int userId = mUserState.keyAt(i); if (!mUserUnlockedStates.contains(userId)) { persistPendingEventsLocked(userId); continue; } UserUsageStatsService service = mUserState.get(userId); if (service != null) { service.persistActiveStats(); } } mHandler.removeMessages(MSG_FLUSH_TO_DISK); }
void persistActiveStats() { if (mStatsChanged) { Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); try { mDatabase.obfuscateCurrentStats(mCurrentStats); mDatabase.writeMappingsLocked(); for (int i = 0; i < mCurrentStats.length; i++) { mDatabase.putUsageStats(i, mCurrentStats[i]); } mStatsChanged = false; } catch (IOException e) { Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); } } }
void writeMappingsLocked() throws IOException { final AtomicFile file = new AtomicFile(mPackageMappingsFile); FileOutputStream fos = file.startWrite(); try { UsageStatsProtoV2.writeObfuscatedData(fos, mPackagesTokenData); file.finishWrite(fos); fos = null; } catch (Exception e) { Slog.e(TAG, "Unable to write obfuscated data to proto.", e); } finally { file.failWrite(fos); } }
/** * Writes the packages mapping data to a ProtoBuf file. * * @param out the output stream to which to write the mappings. * @param packagesTokenData the packages data object holding the data to write. */ static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData) throws IOException, IllegalArgumentException { final ProtoOutputStream proto = new ProtoOutputStream(out); proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter); final int mapSize = packagesTokenData.tokensToPackagesMap.size(); for (int i = 0; i < mapSize; i++) { final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP); int packageToken = packagesTokenData.tokensToPackagesMap.keyAt(i); proto.write(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN, packageToken + 1); final ArrayList<String> strings = packagesTokenData.tokensToPackagesMap.valueAt(i); final int listSize = strings.size(); for (int j = 0; j < listSize; j++) { proto.write(ObfuscatedPackagesProto.PackagesMap.STRINGS, strings.get(j)); } proto.end(token); } proto.flush(); }
USS 每20分钟更新一次数据并存入到data/system_ce/0/usagestats/ 路径下的文件中,所有操作,例如读,写等,都被封装在类UsageStatsProtoV2.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 这两个方法比较重要,与我们的需求强相关:
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; }
3.3 UsageStats
这个类包含针对特定时间范围的应用程序包的使用情况统计信息,这里面的数据就是我们需要的应用使用情况的统计信息,该类里面主要包含了五个方法分别是:
4.UserUsageStatsService
UserUsageStatsService在UsageStatsService的initializeUserUsageStatsServiceLocked方法中创建
UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener) { mContext = context; mDailyExpiryDate = new UnixCalendar(0); mDatabase = new UsageStatsDatabase(usageStatsDir); mCurrentStats = new IntervalStats[INTERVAL_COUNT]; mListener = listener; mLogPrefix = "User[" + Integer.toString(userId) + "] "; mUserId = userId; mRealTimeSnapshot = SystemClock.elapsedRealtime(); mSystemTimeSnapshot = System.currentTimeMillis(); }
构造方法中创建了 UsageStatsDatabase
5.UsageStatsDatabase
UsageStatsDatabase创建了各种文件
@VisibleForTesting public UsageStatsDatabase(File dir, int version) { mIntervalDirs = new File[]{ new File(dir, "daily"), new File(dir, "weekly"), new File(dir, "monthly"), new File(dir, "yearly"), }; mCurrentVersion = version; mVersionFile = new File(dir, "version"); mBackupsDir = new File(dir, "backups"); mUpdateBreadcrumb = new File(dir, "breadcrumb"); mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length]; mPackageMappingsFile = new File(dir, "mappings"); mCal = new UnixCalendar(0); }