性能监控|青训营笔记

67 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第4天

应用卡顿的原因

在Android应用程序中,运行着一个主线程,也被称为UI线程,它处理界面交互的相关的逻辑。 Activiyt、Service、Broadcast、ContentProvider四大基本组件以及各种 View 控件都运行在这个线程中,如果在这个线程中做耗时的操作,就容易引起页面卡顿,也就是掉帧,甚至引起用户最不想看到的ANR。界面呈现是指从应用生成帧并将其显示在屏幕上的动作。要确保用户能够流畅的与应用互动,应用呈现每帧的时间不应该超过16ms,以达到每秒60帧的速度,这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。如果应用存在界面呈现缓慢的问题,系统会不得不跳过一些帧,这会导致用户感觉应用不流畅,也就是卡顿。

消息机制

在Android系统中,提供了一种消息机制,既Handler,Looper,MessageQueue,Message一起组成的消息机制,它们的作用分别是:

  • Handler:将一个任务切换到某个指定的线程中去执行,负责发送和处理Message。
  • Looper:负责创建MessageQueue,并从MessageQueue取出Message。
  • MessageQueue:管理Message。
  • Message:消息实体,携带消息数据。

性能监控耗时原理:

先看看loop()方法中的for循环:

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);
 }

由此可知,上面会在分发和处理消息开始前和结束后会执行mLogging的 println 方法 ,因此,就可以通过这个println 方法来计算执行消息时间。

性能监控耗时

设置 Printer 回调监听:

import android.os.Looper
import android.util.Printer

class BlockDetectByPrinter {
    companion object {
        fun start() {
            Looper.getMainLooper().setMessageLogging(object : Printer {
                //分发和处理消息开始前的log
                private val START = ">>>>> Dispatching"
                //分发和处理消息结束后的log
                private val END = "<<<<< Finished"
                override fun println(x: String) {
                    if (x.startsWith(START)) {
                        //开始计时
                        LogMonitor.startMonitor()
                    }
                    if (x.startsWith(END)) {
                        //结束计时,并计算出方法执行时间
                        LogMonitor.removeMonitor()
                    }
                }
            })
        }
    }
}

通过回调方法println计算执行消息的时间:

import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.util.Log

public object LogMonitor {
    private val TAG = "LogMonitor"
    private var mIoHandler: Handler
    //方法耗时的卡口,300毫秒
    private val TIME_BLOCK = 300L

    private val mLogRunnable = Runnable() {
        //打印出执行的耗时方法的栈消息
        val sb = StringBuilder()
        val stackTrace = Looper.getMainLooper().thread.stackTrace
        for (s in stackTrace) {
            sb.append(s.toString())
            sb.append("\n")
        }
        Log.e(TAG,sb.toString())
    }
    /**
     * 开始计时
     */
    fun startMonitor() {
        mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK)
    }

    /**
     * 停止计时
     */
    fun removeMonitor() {
        mIoHandler.removeCallbacks(mLogRunnable)
    }
    init {
        val logThread = HandlerThread("log")
        logThread.start()
        mIoHandler = Handler(logThread.looper)
    }
}