这是我参与「第四届青训营 」笔记创作活动的第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)
}
}