Android 流畅性三板斧之卡顿监控
Android 流畅性监控的三板斧,帧率的监控、卡顿监控、ANR的监控。之所以讲这三者放在一起是他们的联络比较密切。帧率的下降往往伴随着有卡顿,【过火卡顿】往往就会发生ANR。
谨慎的讲,帧率下降不一定会有卡顿(这儿对卡顿是从技术角度界说在主线程履行了耗时使命),卡顿发生的原因还有其他因素导致,比方系统负载、CPU繁忙等。
【过火的卡顿】也不一定发生ANR,卡顿但未触发ANR发生的条件就不会发生ANR。关于ANR的详细内容咱们放在三板斧系列文章的第三篇。
Android 流畅性三板斧之帧率监控(/post/721780…)
Android 流畅性三板斧之卡顿监控(/post/721877…)
Android 流畅性三板斧之ANR监控 (/post/722004…)
温馨提示,本文触及的完成的代码以上传至github github.com/drummor/Get…,结合代码食用更佳
1 WatchDog 计划
1.1 基本原理
思路:
开启一个子线程不断往主线程Looper 发音讯。这个音讯内便是修正咱们重置一个值。子线程使命间隔一段时刻check 音讯是否被处理,假如没有被处理,则以为主线程出现了卡顿状况。
代码:
final Runnable _ticker = new Runnable() {
@Override public void run() {
_tick = 0;
_reported = false;
}
};
@Override
public void run() {
setName("|ANR-WatchDog|");
long interval = _timeoutInterval;
while (!isInterrupted()) {
boolean needPost = _tick == 0;
_tick += interval;
if (needPost) {
_uiHandler.post(_ticker);
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
if (_tick != 0 && !_reported) {
//以为发生了卡顿
}
}
1.2 计划存在的问题
1.2.1 问题1
不断的往message 轮训,对性能和功耗有损伤,不行高雅;一起设置的IdelHandler 使命永久得不到履行。
1.2.2 问题2
上面这个问题还好,更严重的问题是漏报。如下图所示。
-
500ms检测一次有一个耗时600ms的使命,履行履行的开端是在检测第一个检测周期的第150ms处结束。在第一个周期的0-50m和第二个周期的150-500ms都有一段不卡顿的时刻,这段时刻检测音讯能够被处理掉。那么该耗时使命能被检测到的概率会只要有20%。
只要当卡顿时刻大于两倍的轮训间隔,卡顿才能完好的被检测到。
-
乃至咱们能够计算出一个漏报概率公式。
x/a -1
(代表卡顿时刻,a代表轮训时刻)。也便是说假如咱们要检测一个500ms的卡顿,轮训间隔时刻要小于等于250ms。 -
使用这种办法用来检测ANR也有很多坏处和不科学之处,这点咱们在三板斧的第三篇文章里讲。
2 AndroidPerformanceMonitor 计划
2.1 基本原理
能查到到的最早的计划来源于开源:AndroidPerformanceMonitor(BlockCanary)其中心的原理在
#Looper
private static boolean loopOnce(final Looper me,..) {
Message msg = me.mQueue.next(); // might block
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what);
}
...
msg.target.dispatchMessage(msg);
...
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
msg.recycleUnchecked();
return true;
}
Looper 在分发履行每个Message 前后都会打印调用logging
打印日志,且可喜的是咱们设置该logging
。依次能够计算出主线程的使命履行的耗时,超出【阈值】就以为出现了卡顿,从而搜集信息。
咱们在三板斧的第一篇里hook Choreographer
计划监控帧率计划里也运用了这一手法。
别的,Android api10 以上Looper 里增加了Observer
,经过这一api咱们也能够监控Message
的调度耗时。只不过该api是@hide的咱们需求经过元反射的办法处理,对此这儿不再打开。
2.2 计划监控盲区
该计划咱们能监控到大部分的主线程耗时办法,还有少量的履行使命监控不到。
- idleHandler的耗时监控不到
- native层面建议的主线程使命监控不到。在java层面上咱们拿到的仓库是从 MessageQueue.java 的
nativePollOnce(ptr, nextPollTimeoutMillis)
处建议,对应的native层面会履行然后回到java层,如inputEvent事情。 - SyncBarrier走漏问题导致的耗时。Looper中post的一个SyncBarrier,此后MessageQueue 里会跳过同步音讯,只履行异步音讯。当本该remove掉的SyncBarrier没被remvoe 掉的时候,同步音讯永久得不到履行形成同步音讯等待耗时。
下面咱们打开针对这三个问题的处理计划。
3 进化完善计划
针对AndroidPerformanceMonitor 监控的三个首要盲区,逐个补充完善计划。
3.1 idleHandler 耗时监控
#Looper
public static void loop() {
for (;;) {
Message msg = queue.next(); //内部或许处理了IdleHandler
...
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
msg.target.dispatchMessage(msg);
...
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
# MessageQueue
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (; ; ) {
nativePollOnce(ptr, nextPollTimeoutMillis);
...//取出message
for (int i = 0; i < pendingIdleHandlerCount; i++) {
//处理idleHandler
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
keep = idler.queueIdle();
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
}
}
如上代码,IdleHandler处理是在MessageQueue 里的next办法里。咱们运用Looper 的printer 计划是监控不到的。
针对这个问题的处理计划,反射设置MessagQuueue 的private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
成员变量,完成一个咱们自身ArrayList,当往里增加IdleHandler
时,讲起包装在咱们完成的署理IdleHandlerWrapper
里,IdleHandlerWrapper
首要的作业便是在queueIdle();
办法前后加入监控代码,监控queueIdle
的履行耗时。
class IdleHandlerWrapper(
val orgIdleHandler: MessageQueue.IdleHandler,
private val callback: (Long) -> Unit
) : MessageQueue.IdleHandler {
override fun queueIdle(): Boolean {
val startTime = System.currentTimeMillis()
val ret = orgIdleHandler.queueIdle()
val costTime = System.currentTimeMillis() - startTime
if (costTime > 2_000) {
callback.invoke(costTime)
}
return ret
}
}
3.2 Native 层面建议的主线程使命耗时监控
先看一个张图
如上图是一个Activity onTouchEvent()
办法的履行栈,由此能够看出,本次的履行时从MesssageQueue
的nativePollOnce
处开端履行;并不是咱们惯常的Handler的dispatchMessage(msg)
处履行而来。
究其原因是,native层把epoll唤醒一起调用了ViewRootImpl里的InputReceiver.onInputEvent()
办法
Looper#loop()
-> MessageQueue#next()
-> MessageQueue#nativePollOnce()
-> NativeMessageQueue#pollOnce() //留意,进入 Native 层
-> Looper.cpp#pollOnce()
-> Looper.cpp#pollInner()
-> epoll_wait()
了解Handler机制中epoll机制的同学知道,当MessageQueue
没有履行的时候主线程会堵塞在nativePollOnce()
处,被唤醒的时候会从此处履行。留意: 被唤醒并不是一定在java层往MessgeQueue里刺进音讯时,native层也能唤醒,且native层在唤醒时也或许会有使命要履行。
/system/core/libutils/Lopper.cpp
int pollInner(int timeoutMillis){
int result = POLL_WAKE;
// step 1,epoll_wait 办法回来
int eventCount = epoll_wait(mEpollFd, eventItems, timeoutMillis);
if (eventCount == 0) { // 事情数量为0表明,达到设定的超时时刻
result = POLL_TIMEOUT;
}
for (int i = 0; i < eventCount; i++) {
if (seq == WAKE_EVENT_FD_SEQ)) {
// step 2 ,清空 eventfd,使之重新变为可读监听的 fd
awoken();
} else {
// step 3 ,保存自界说fd触发的事情调集
mResponses.push(eventItems[i]);
}
}
// step 4 ,履行 native 音讯分发
while (mMessageEnvelopes.size() != 0) {
if (messageEnvelope.uptime <= now) { // 检查音讯是否到期
messageEnvelope.handler->handleMessage(message);
}
}
// step 5 ,履行 自界说 fd 回调
for (size_t i = 0; i < mResponses.size(); i++) {
response.request.callback->handleEvent(fd, events, data);
}
return result;
}
native第4步和第5步的都会有使命履行。这些使命履行有或许会回调到java层,如TouchEvent
事情,传感器事情。
计划
针对这类事情的监控,Matrix 的计划是:经过PLT Hook,hook libinput.so中的recvfrom和sendto办法。
鉴于此类事情接触到的比较多的便是TouchEvent
和传感器事情,特别是TouchEvent
事情,我这儿供给一种低成本的监控 TouchEvent
耗时的计划。
设置Window.Callback
署理然后监控
class TouchCostWindowCallback(private val windowCallback: Window.Callback) :
Window.Callback by windowCallback {
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
val start = System.currentTimeMillis()
val rst = windowCallback.dispatchTouchEvent(event)
val cost = System.currentTimeMillis() - start
// 监控
return rst
}
}
应用层一切的input事情都是经过InputEventReceriver
接纳,然后再经ViewRootImpl分发,分发的时候要经过 Window.Callback
,使用这一特性完成TouchEvent
的监控。
3.3 SyncBarrier 走漏监控
SyncBarrier是一种特别的Message,其特点是target为空,反射拿到MessageQueue的成员变量mMessages
,经过message的when和当时时刻比照,超过了咱们的阈值。
然后再配合发送一个异步音讯和同步音讯,假如同步音讯能够履行,异步音讯一直得不到履行,能够断定Message的调度没问题,SyncBarrier发生了走漏。
处理的办法:能够直接把SyncBarrier移除掉。
4 结
-
本文的卡顿监控其实是从主线程耗时角度从一下四个方面进行监控,覆盖主线程里使命的耗时。
- Java层Message调度耗时,经过设置Looper 的 Printer 监控java层Message调度履行耗时。
- IdleHanlder履行耗时,hook MessageQueue监控Idlehandler的耗时。
- native层主线程使命耗时。
- SyncBarrier 走漏监控
-
这个信息是让咱们有数可依量化【卡顿】这一片面指标变得更为【客观】。
-
一起,这个信息关于咱们三板斧的【ANR监控】有非常重要的参阅含义。
-
本文触及的完成的代码以上传至github github.com/drummor/Get…
流程性三板斧 APM必备
Android 流畅性三板斧之帧率监控(/post/721780…)
Android 流畅性三板斧之卡顿监控(/post/721877…)
Android 流畅性三板斧之ANR监控 (/post/722004…)
关注 点赞 鼓励,感谢 !!!
本文正在参与「金石计划」