我是靠谱客的博主 洁净豆芽,这篇文章主要介绍Android卡顿监控一、概述二、代码实现,现在分享给大家,希望可以做个参考。

一、概述

如果想要自己实现一个简单的卡顿监控功能,可以看下这篇文章。我们都知道,Android程序 是基于事件驱动的,程序主线程一直在执行Looper的loop,loop的循环不断的读取事件进行处理,没有事件就等待着,退出程序也就退出这个循环。在Looper中,处理消息前后会打印log:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } ... try { msg.target.dispatchMessage(msg); ... } finally { ... } ... if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); }

我们给主线程的Looper设置一个Printer,这样可以根据前后打印log的时间来获取事件的处理时长,如果处理时间过长,那就导致不能及时处理下一个事件,自然也就会导致出现卡顿或者ANR,所以当时长超过一定阈值时,我们就记录到日志文件中,从而方便我们排查问题。

二、代码实现

下面看看具体的代码实现:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/** * 监控 Message 执行时长,检测卡顿 */ class BlockCatch(context: Context) { private val startSymbol = ">>>>> Dispatching" private val endSymbol = "<<<<< Finished" private val thresholdValue = 600L//message处理完成的时间超过多少毫秒认为卡顿 private val writeLogThreshold = 3000L//message处理时间超过多少毫秒还是没有处理,记录日志,以免出现anr时没有msgEnd导致日志不会写入 private val collectLogGap = 32L//收集主线程堆栈的时间间隔毫秒 private val msgStart = 1 private val msgCollectLog = 2 private val msgEnd = 3 private val handlerThread: HandlerThread = HandlerThread("BlockCatch") private var handler: Handler? = null private val logFilePath: String? = context.externalCacheDir?.path private var lastFrameTimeNanos: Long = 0 init { if (!logFilePath.isNullOrEmpty()) { handlerThread.start() handler = object : Handler(handlerThread.looper) { private val logMap = SparseArray<String>()//存储主线程堆栈信息 private val countMap = SparseIntArray()//存储堆栈信息的计数 private var startTime = 0L override fun handleMessage(msg: Message) { when (msg.what) { msgStart -> { startTime = msg.obj as Long sendEmptyMessageDelayed(msgCollectLog, collectLogGap) } msgCollectLog -> { Log.i("TAG", "msgCollectLog -------------------------->") val stackTrace = Looper.getMainLooper().thread.stackTrace val sb = StringBuilder() stackTrace.forEach { sb.append(it.toString()).append("n") } val string = sb.toString() val hash = string.hashCode() val count = countMap.get(hash) if (count == 0) { logMap.put(hash, string) } countMap.put(hash, count + 1) sendEmptyMessageDelayed(msgCollectLog, collectLogGap) } msgEnd -> { val time = msg.obj as Long val delay = time - startTime if (delay >= thresholdValue) {//消息处理时间超过thresholdValue try { val file = File(logFilePath, "BlockCatchLog.txt") val fileOutputStream = FileOutputStream(file, true) fileOutputStream.write("BlockCatch: t $time d $delay-->n".toByteArray()) for (i in 0 until logMap.size()) { val key = logMap.keyAt(i) val count = countMap.get(key) if (count > 1) { fileOutputStream.write(logMap.get(key).toByteArray()) fileOutputStream.write("<-c $countn".toByteArray()) } else { Log.i("TAG", "msgBlock: skip") } } fileOutputStream.close() } catch (e: Exception) { e.printStackTrace() } } logMap.clear() countMap.clear() Log.i("TAG", "msgBlock ${logMap.size()} $time<---------------") } } } } Looper.getMainLooper().setMessageLogging { x -> // Log.i("TAG", "looper MessageLogging time ${System.currentTimeMillis()} $x") if (x.startsWith(startSymbol)) { handlerStart() } else if (x.startsWith(endSymbol)) { handlerEnd() } } // Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback { // override fun doFrame(frameTimeNanos: Long) { // Choreographer.getInstance().postFrameCallback(this) // if (lastFrameTimeNanos == 0L) { // lastFrameTimeNanos = frameTimeNanos // } else { // val diff = frameTimeNanos - lastFrameTimeNanos // lastFrameTimeNanos = 0 // if (diff > 17000000) { // Log.i("TAG", "Choreographer doFrame diff " + diff / 1000000) // } // } // } // }) } } private fun handlerStart() { val time = System.currentTimeMillis() val msg = Message.obtain() msg.obj = time msg.what = msgStart handler?.sendMessage(msg) } private fun handlerEnd() { val time = System.currentTimeMillis() handler?.removeMessages(msgCollectLog) val msg = Message.obtain() msg.obj = time msg.what = msgEnd handler?.sendMessage(msg) } }

在开始处理事件的时候,记录开始时间,开始收集主线程的栈信息,每间16毫秒收集一次。这样做是因为我们要知道处理消息过程中执行了哪些方法,如果在事件处理结束后,栈已经回退,这样就不知道这中间到底执行了哪个方法。

因为每16ms就获取一次栈信息,如果一个方法执行的时长有多个16ms,那就会被重复记录多次。我们只需要知道执行了哪些方法,以及方法执行了多少个16ms。这里用 logMap 来保存栈信息,countMap来保存多少个16ms,获取到栈信息后,计算它的hashCode,如果countMap没有这个hashCode的记录就保存到 logMap中,如果有就将它的次数加1。

在处理事件结束后,用结束时间与开始时间相减,判断是否大于 thresholdValue ,如果小于则表示没有耗时的方法,就清空countMap 和 hashCode 。如果大于,则将 hashCode 里面的内容写到文件中记录下来,完成后情况它们。

上面的这些操作都在HandlerThread子线程中,因为不能影响到主线程的工作,不然我们自己就成了那个导致卡顿的元凶了。

使用的话,只需要在Application中初始化就可以了。

这只是一个简单的监控,还有很多问题,比如无法监听到onKey,onClick方法里面的阻塞。(输入事件由native层调用InputEventSender的dispatchInputEventFinished方法开始处理,此时Looper阻塞在MessageQueue.nativePollOnce,也就是说MessageQueue此时没有消息可处理)

2022.11.14注:
该方法与协程不兼容,logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what) toString方法会导致协程里面不断的调用toString方法,出现栈溢出。

最后

以上就是洁净豆芽最近收集整理的关于Android卡顿监控一、概述二、代码实现的全部内容,更多相关Android卡顿监控一、概述二、代码实现内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部