本篇属于APM系列的第四篇,主要讲如果通过一个三方应用去监控系统中所有应用的前后台切换,以及获取位于屏幕前台的Activity。
为什么要监听所有应用的前后台切换呢?这么做主要有两个目的:
1.检测用户使用某个应用的时长,方便用户喜好分析。
2.关联协助分析一些异常问题,比如发现从A跳转B发生了ANR,就远比单纯的只有一份ANR日志好分析的多。
这里我们就不讲源码分析流程了,直接说结论。在AMS中,有一个方法registerProcessObserver,代码如下:
public void registerProcessObserver(IProcessObserver observer) {
enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
"registerProcessObserver()");
synchronized (this) {
mProcessObservers.register(observer);
}
}
我们在看IProcessObserver这个aidl文件:
oneway interface IProcessObserver {
void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
void onProcessDied(int pid, int uid);
}
看回调方法的声明,很明显是可以满足我们要求的。
onForegroundActivitiesChanged方法,代表进程切换到前台或者退出前台时的回调,
onProcessDied方法,代表的是某个进程死亡时的回调。
所以接下来,我们的目标就是如何注册这个binder类型的回调了。IProcessObserver.aidl文件是hide类型,我们无法调用。但是binder其实和socket通讯一样,是要进行序列化和反序列化的,所以,我们只要仿照IProcessObserver构造一个一模一样的aidl文件进行注册,就可以实现回调的注册,实际操作下来,效果也确实如我们所设想,完全可用,相关代码如下:
//生成IProcessObserver.aidl文件,注意包名也要一致。
// IProcessObserver.aidl
package android.app;
// Declare any non-default types here with import statements
interface IProcessObserver {
void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
void onForegroundServicesChanged(int pid, int uid, int serviceTypes);
void onProcessDied(int pid, int uid);
}
注册代码如下:
//注册回调
private fun monitorAppProcess(mProcessObserver: IProcessObserver?) {
try {
val activityManager = Class.forName("android.app.ActivityManager")
val getDefaultMethod: Method = activityManager.getMethod("getService")
val iActivityManager: Any = getDefaultMethod.invoke(null)
val registerMethod: Method = iActivityManager.javaClass.getMethod(
"registerProcessObserver", IProcessObserver::class.java
)
registerMethod.invoke(iActivityManager, mProcessObserver)
} catch (e: Exception) {
e.printStackTrace()
}
}
有时候排查anr或者crash问题,关联Activity会方便问题的排查。
针对这块如何监控,有两个方案:
1.基于第一章的内容,页面切换后,通过activityManager.getRunningTasks()获取当前的头部页面。
2.和第一章类似的方式,通过ActivityManagerService的方法registerTaskStackListener,注册监听回调。
第二个方案的功能暂时还未实现。
监听应用的前后台切换其实有很多场景,比如高性能负载方案,记录应用使用时长等都会使用到。也就是说,这属于一个服务提供者,多个观察者的场景,这种场景自然是使用观察者模式最为合适。
被观察者
object AppStateSubject {
private lateinit var context: Context
//观察者列表
private val list = mutableListOf()
private val packageMap = HashMap()
//注册观察者
fun attachObserver(observer: AppStateObserver) {
list.add(observer)
}
//取消注册观察者
fun detachObserver(observer: AppStateObserver) {
list.remove(observer)
}
//通知观察者前后台变化
private fun notifyObserverActivityState(packageName: String, isForeground: Boolean) {
for (observer in list) {
observer.notifyActivityForegroundState(packageName, isForeground)
}
}
//通知观察者进程开始或者死亡
private fun notifyObserverAppState(packageName: String, isBorn: Boolean) {
for (observer in list) {
observer.notifyAppState(packageName, isBorn)
}
}
fun initSubject(context: Context) {
this.context = context
}
/**
*
*/
fun notifyAppForegroundState(pid: Int, uid: Int, foregroundActivities: Boolean) {
var packageName = packageMap[pid]
if (packageName == null) {
packageName = WatchUtil.searchPackage(context, pid)
packageMap[pid] = packageName
notifyObserverAppState(packageName, true)
}
notifyObserverActivityState(packageName, foregroundActivities)
}
fun notifyDied(pid: Int, uid: Int) {
val packageName = packageMap.remove(pid) ?: ""
notifyObserverAppState(packageName, false)
}
}
其中一个观察者,用于监听前后台切换并且进行上报。
object WatchAppState {
private lateinit var mContext: Context
fun initWatchAppState(context: Context) {
this.mContext = context
AppStateSubject.attachObserver(object : AppStateObserver {
override fun notifyActivityForegroundState(packageName: String, isForeground: Boolean) {
//记录埋点
WatchDataTraceUtil.postAppState(packageName, if (isForeground) 1 else 2)
}
override fun notifyAppState(packageName: String, isBorn: Boolean) {
}
})
}
}