APM系列之四-原理篇-监听应用前后台切换

前言

本篇属于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()
    }
}

二.前后台切换如何关联到Activity

有时候排查anr或者crash问题,关联Activity会方便问题的排查。

针对这块如何监控,有两个方案:

1.基于第一章的内容,页面切换后,通过activityManager.getRunningTasks()获取当前的头部页面。

2.和第一章类似的方式,通过ActivityManagerService的方法registerTaskStackListener,注册监听回调。

第二个方案的功能暂时还未实现。

三.如何优雅的监听

监听应用的前后台切换其实有很多场景,比如高性能负载方案,记录应用使用时长等都会使用到。也就是说,这属于一个服务提供者,多个观察者的场景,这种场景自然是使用观察者模式最为合适。

被观察者

object AppStateSubject {
    private lateinit var context: Context
    //观察者列表
    private val list = mutableListOf<AppStateObserver>()
    private val packageMap = HashMap<Int, String>()

    //注册观察者
    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) {

            }
        })
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值