简介
之前写过两篇文章介绍了通过 APT 和 Javassist 做静态的代码插桩:
1. 使用 APT 自动生成代码
2. 使用 Javassist 实现代码静态插桩
我们可以通过代码插桩,实现 代码日志记录、方法耗时统计、界面自动生成 等功能。
但是,通过 Javassist 和 ASM 做的代码插桩,只能修改 apk 包内的代码,无法修改系统中的代码。
例如在 Android 的性能优化等场景,需要 hook Handler、Thread 等系统类才能实现,例如:
(1) 如何监控业务层代码没有使用线程池,而是创建 Thread 对象?
(2) 如何统计主线程 Message 的执行耗时,并在 Message 执行超时后打印其加入时的堆栈?
目前开源的代码动态插桩的技术,主要是基于 Xposed、Dexposed 以及 weishu 大佬的 epic 实现的。
我们在使用 epic 时,主要用它的 XposedBridge
类。这个类有三个重要的方法:hookMethod
、hookAllConstructors
、hookAllMethods
。
接下来我们就用上面两个性能监控的例子,看一下如何使用 动态代码插桩 来监控应用性能。
(这一篇只看怎么使用 epic,它的原理 weishu 大神在自己的博客中写得比较明白了,后续我也会补上一篇 epic 的原理)
一、如何监控线程的创建?
当我们要去监控某一个类是否有对象创建,例如 Thread,其实就是监控这个类的构造函数是否有被调用。
我们可以利用 XposedBridge
的 hookAllConstructors
方法做监控,它的函数原型如下:
public static
Set<Unhook> hookAllConstructors(Class klass, XC_MethodHook callback)
返回值可以用于解除 hook,参数一是想要 hook 的类,参数二是定义如何 hook 的回调。
XC_MethodHook
有两个方法,用于在原函数执行前后做插桩。
对于我们监控线程的这个目的,它的实现是:
class ThreadConstructorHooker(priority: Int) : XC_MethodHook(priority) {
/* ======================================================= */
/* Override/Implements Methods */
/* ======================================================= */
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
Log.i("ZHP_TEST", "即将执行线程的构造方法")
}
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
Log.i("ZHP_TEST", "线程的构造方法执行完毕")
// 通过 thisObject 属性获取到 this
val thread = param?.thisObject ?: return
Log.i("ZHP_TEST", "线程创建的堆栈:")
Throwable().printStackTrace();
}
}
在尽可能早的地方,执行以下 hook 即可监控线程的初始化了:
val hookHandler = ThreadConstructorHooker(1)
XposedBridge.hookAllConstructors(Thread::class.java, hookHandler)
在具体的业务处理中,我们可以根据传入的 param 对象,获取 this 的具体类型、调用的堆栈等等,用于判断是否需要抛出异常。
二、如何监控主线程 Message 执行超时?
以前我们可以用反射替换掉 Looper 的 mLogging 对象,在其打印日志时统计 Message 的执行耗时。 但这种办法不能记录这个 Message 是在什么时候加入到 消息队列 中的。
如果想要记录 Message 在加入时的堆栈,则需要在 Handler 的 sendMessageAtTime
方法中插桩。可惜的是 Handler 是系统的类,无法通过 Javassist 或者 ASM 插桩。
用 epic 就容易多了。
首先我们定义一个 Map 用于保存 Message 在添加时的堆栈:
object MessageStackTraceMap {
/* ======================================================= */
/* Fields */
/* ======================================================= */
private val map = HashMap<Message, Throwable>()
/* ======================================================= */
/* Public Methods */
/* ======================================================= */
fun put(message: Message, throwable: Throwable) {
map[message] = throwable
}
fun remove(message: Message) {
map.remove(message)
}
fun get(message: Message): Throwable? {
return map[message]
}
}
这只是一个简化版的代码~
接下来定义如何 hook Handler 的 sendMessageAtTime
方法:
class SendMessageAtTimeHooker(priority: Int) : XC_MethodHook(priority) {
/* ======================================================= */
/* Override/Implements Methods */
/* ======================================================= */
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
// 获取 Handler 对象
val handler = param?.thisObject as? android.os.Handler ?: return
// 如果不是主线程,不处理
if (handler.looper != Looper.getMainLooper()) {
return
}
// 加入到 map 中,记录其堆栈
MessageStackTraceMap.put(param.args[0] as Message, Throwable());
}
}
以及 dispatchMessage
方法,这个方法用于计算 Message 的执行耗时:
class DispatchMessageHooker(priority: Int) : XC_MethodHook(priority) {
/* ======================================================= */
/* Fields */
/* ======================================================= */
/** Message 开始执行的时刻 */
private var begin: Long = 0
/* ======================================================= */
/* Override/Implements Methods */
/* ======================================================= */
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
// 获取 Handler 对象
val handler = param?.thisObject as? android.os.Handler ?: return
// 如果不是主线程,不处理
if (handler.looper != Looper.getMainLooper()) {
return
}
// 记录 Message 执行的开始时间
begin = SystemClock.elapsedRealtime()
}
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
// 获取 Handler 对象
val handler = param?.thisObject as? android.os.Handler ?: return
// 如果不是主线程,不处理
if (handler.looper != Looper.getMainLooper()) {
return
}
// 计算这个 message 的执行耗时
val now = SystemClock.elapsedRealtime()
val cost = now - begin
// 获取消息对象
val msg = param.args[0] as Message
// 如果耗时小于 60 毫秒,还能接受
if (cost < 60) {
// 从队列中移除消息
MessageStackTraceMap.remove(msg)
return
}
// 对于大于 60 毫秒的消息,获取其添加时的堆栈,并报警
val throwable = MessageStackTraceMap.get(msg)
MessageStackTraceMap.remove(msg)
Log.w("ZHP_TEST", "消息执行超时,添加位置:")
throwable?.printStackTrace()
}
}
最后我们在使用 XposedBridge
将这个两个方法 hook 了:
val handlerClass = Handler::class.java
// hook Handler#sendMessageAtTime 方法
val sendMessageAtTime = handlerClass.getDeclaredMethod(
"sendMessageAtTime",
Message::class.java,
Long::class.java
)
XposedBridge.hookMethod(
sendMessageAtTime,
SendMessageAtTimeHooker(1)
)
// hook Handler#dispatchMessage 方法
val dispatchMessage = handlerClass.getDeclaredMethod(
"dispatchMessage",
Message::class.java
)
XposedBridge.hookMethod(
dispatchMessage,
DispatchMessageHooker(1)
)
这就实现了监控主线程消息执行耗时的功能啦~
这篇只是说一下怎么实现这两个常见的性能监控需求,下一篇我们了解 epic 的具体实现原理。