關(guān)鍵字: 應(yīng)用統(tǒng)計 Android源碼 應(yīng)用使用時長 應(yīng)用使用次數(shù)
上篇文章講到關(guān)于Android系統(tǒng)應(yīng)用數(shù)據(jù)統(tǒng)計的結(jié)果及其相關(guān)的數(shù)據(jù)結(jié)構(gòu),接下來主要從源碼方面分析系統(tǒng)是如何記錄相關(guān)數(shù)據(jù)的。
首先,我們可以找到這樣一個Service。
/**
* A service that collects, aggregates, and persists application usage data.
* This data can be queried by apps that have been granted permission by AppOps.
*/
public class UsageStatsService extends SystemService implements
UserUsageStatsService.StatsUpdatedListener {
@Override
public void onStart() {
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mHandler = new H(BackgroundThread.get().getLooper());
//正如第一篇文章所闡述的那樣,建立相關(guān)的文件目錄
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());
}
getContext().registerReceiver(new UserRemovedReceiver(),
new IntentFilter(Intent.ACTION_USER_REMOVED));
synchronized (mLock) {
cleanUpRemovedUsersLocked();
}
//這里的時間,也可以稍微注意下,因為每次記錄都會就愛那個相關(guān)時間戳寫入文件中
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
//以下兩行最為關(guān)鍵,注冊了兩個Service
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
將UsageStatsService的注釋翻譯過來,可知,該Service用于收集,統(tǒng)計,保存相關(guān)的應(yīng)用使用情況的數(shù)據(jù)。在第三方應(yīng)用權(quán)限允許的情況下,可以為該應(yīng)用提供這些數(shù)據(jù)。在檢查了相關(guān)文件目錄,完成對應(yīng)的準(zhǔn)備工作之后,UsageStatsService注冊了兩個Service,分別為 LocalService 和 BinderService。這兩個Service是UsageStatsService的兩個內(nèi)部類。具體如下:
LocalService
/**
* This local service implementation is primarily used by ActivityManagerService.
* ActivityManagerService will call these methods holding the 'am' lock, which means we
* shouldn't be doing any IO work or other long running tasks in these methods.
*/
private class LocalService extends UsageStatsManagerInternal {
@Override
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportConfigurationChange(Configuration config, int userId) {
if (config == null) {
Slog.w(TAG, "Configuration event reported with a null config");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = "android";
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
// we are shutting down.
//其實此處就是調(diào)用下文中提到的flushToDiskLocked(),這一函數(shù)
shutdown();
}
}
正如注釋的那樣,此Service由ActivityManagerService調(diào)用的,不進行I/O等耗時操作的服務(wù),記錄每一次event和config數(shù)據(jù)。從代碼中可以看出來,reportConfigurationChange()和reportEvent()都是new一個Event,然后交由mHandler處理。
mHandler
class H extends Handler {
public H(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REPORT_EVENT:
reportEvent((UsageEvents.Event) msg.obj, msg.arg1);
break;
case MSG_FLUSH_TO_DISK:
flushToDisk();
break;
case MSG_REMOVE_USER:
removeUser(msg.arg1);
break;
default:
super.handleMessage(msg);
break;
}
}
}
以上是mHandler的源碼,針對上面三條message,我們在UsageStatsService中可以找到其最終調(diào)用的函數(shù)如下
void reportEvent(UsageEvents.Event event, int userId) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
convertToSystemTimeLocked(event);
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
service.reportEvent(event);
}
}
private void flushToDiskLocked() {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
UserUsageStatsService service = mUserState.valueAt(i);
service.persistActiveStats();
}
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
//此函數(shù)就不過多注釋了,其目的就是在系統(tǒng)文件目錄中,刪除部分系統(tǒng)用戶,及其目錄下的一系列的Usage數(shù)據(jù)
void removeUser(int userId) {
synchronized (mLock) {
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
cleanUpRemovedUsersLocked();
}
}
從上面可以得出,上報event,并且寫入磁盤,最終都會調(diào)用UserUsageStatsService 這個類(此類會在下篇文章中講到)。
BinderService
private class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SYSTEM_UID) {
return true;
}
final int mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
callingUid, callingPackage);
if (mode == AppOpsManager.MODE_DEFAULT) {
// The default behavior here is to check if PackageManager has given the app
// permission.
return getContext().checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)
== PackageManager.PERMISSION_GRANTED;
}
return mode == AppOpsManager.MODE_ALLOWED;
}
@Override
public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
long endTime, String callingPackage) {
if (!hasPermission(callingPackage)) {
return null;
}
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
final List<UsageStats> results = UsageStatsService.this.queryUsageStats(
userId, bucketType, beginTime, endTime);
if (results != null) {
return new ParceledListSlice<>(results);
}
} finally {
Binder.restoreCallingIdentity(token);
}
return null;
}
@Override
public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType,
long beginTime, long endTime, String callingPackage) throws RemoteException {
if (!hasPermission(callingPackage)) {
return null;
}
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
final List<ConfigurationStats> results =
UsageStatsService.this.queryConfigurationStats(userId, bucketType,
beginTime, endTime);
if (results != null) {
return new ParceledListSlice<>(results);
}
} finally {
Binder.restoreCallingIdentity(token);
}
return null;
}
@Override
public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) {
if (!hasPermission(callingPackage)) {
return null;
}
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEvents(userId, beginTime, endTime);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump UsageStats from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ " without permission " + android.Manifest.permission.DUMP);
return;
}
UsageStatsService.this.dump(args, pw);
}
}
/**
* 被上述BinderService調(diào)用
* Called by the Binder stub.
*/
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
return service.queryUsageStats(bucketType, beginTime, endTime);
}
}
/**
* 被上述BinderService調(diào)用
* Called by the Binder stub.
*/
List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
long endTime) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
return service.queryConfigurationStats(bucketType, beginTime, endTime);
}
}
/**
* 被上述BinderService調(diào)用
* Called by the Binder stub.
*/
UsageEvents queryEvents(int userId, long beginTime, long endTime) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
return null;
}
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
return service.queryEvents(beginTime, endTime);
}
}
BinderService,可以看出主要用于查詢UsageStats數(shù)據(jù),包括UsageStats,ConfigurationStats,UsageEvents 。顯然,查詢借口和LocalService一樣,調(diào)用UserUsageStatsService接口,此類將在下一篇文章中繼續(xù)解析。
結(jié)語:
本文主要介紹了系統(tǒng)關(guān)于查詢獲取系統(tǒng)統(tǒng)計信息的api的部分相關(guān)源碼,主要是利用Service,進行數(shù)據(jù)的查詢和記錄。接下來的文章將主要介紹其調(diào)用的UserUsageStatsService 的具體實現(xiàn)及其邏輯。
轉(zhuǎn)載請注明出處。
github:UseTimeStatistic
參考文獻:
相關(guān)源碼鏈接
Android5.1應(yīng)用統(tǒng)計源碼分析
Android5.1應(yīng)用打開次數(shù)獲取
上一篇:Android應(yīng)用統(tǒng)計-使用時長及次數(shù)統(tǒng)計(二)
下一篇:Android應(yīng)用統(tǒng)計-使用時長及次數(shù)統(tǒng)計(四)