检测应用中的UI卡顿
检测UI中的卡顿可以有效地定位UI中的耗时操作,主要有以下方法:
1.利用Looper检测
先来看一下Looper中的loop()方法:
public static void loop() {
......
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 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);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
其中第12行和第29行分别对logging做了判断,如果不为空调用了println方法。在这两个判断中间则是调用了msg.target.dispatchMessage(msg);也就是UI操作。那么我们来看一下这个logging对象。
public interface Printer {
/**
* Write a line of text to the output. There is no need to terminate
* the given string with a newline.
*/
void println(String x);
}
只是一个接口的对象,因此可以理解为在msg.target.dispatchMessage(msg);的前后loop()方法已经给我们监听UI操作提供了接口。只要我们事先给Looper设置logging,那么就可以在pringln方法中进行一些监听UI的操作。
public class BlockDetectByPrinter {
private static String START = ">>>>> Dispatching";
private static String END = "<<<<< Finished";
public static void start(){
// Looper.loop();
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
if(x.startsWith(START)){
LogMonitor.getInstance().startMonitor();
}else if(x.startsWith(END)){
LogMonitor.getInstance().removeMonitor();
}
}
});
}
}
public class LogMonitor {
private static LogMonitor logMonitor;
private HandlerThread handlerThread = new HandlerThread("hs");
private Handler mHandler;
private Runnable task;
private static long DELAY = 1000L;
private LogMonitor(){
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
task = new Runnable() {
@Override
public void run() {
StringBuilder stringBuilder = new StringBuilder();
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
stringBuilder.append(stackTraceElement.toString());
stringBuilder.append("\n");
}
Log.e("-MainThreadStackTrace-",stringBuilder.toString());
}
};
}
public static LogMonitor getInstance(){
if(logMonitor == null){
synchronized (LogMonitor.class){
if(logMonitor==null){
logMonitor = new LogMonitor();
}
return logMonitor;
}
}
return logMonitor;
}
public void startMonitor(){
mHandler.postDelayed(task,DELAY);
}
public void removeMonitor(){
mHandler.removeCallbacks(task);
}
}
在BlockDetectByPrinter 中给主线程的looper设置了logging对象,在LogMonitor 中用handlerThread和handler在子线程对UI操作进行监听,一旦时间超过阈值,则postrunnable对象到子线程执行,否则取消post。而runnable中则是读取当前主线程方法栈中的信息,以此来确定UI卡顿的位置。
2.利用Choreographer
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染。SDK中包含了一个相关类,以及相关回调。理论上来说两次回调的时间周期应该在16ms,如果超过了16ms我们则认为发生了卡顿:
public class BlockDetectByChoreographer {
public static void start() {
Choreographer.getInstance()
.postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long l) {
if (LogMonitor.getInstance().isMonitor()) {
LogMonitor.getInstance().removeMonitor();
}
LogMonitor.getInstance().startMonitor();
Choreographer.getInstance().postFrameCallback(this);
}
});
}
}
原理和上面利用looper判断相同。
参考自这篇博客
绘制中的性能问题
安卓系统每隔16ms(1000ms/60fps)会发送VSYNC信号,触发对UI的渲染。如果VSYNC信号来的时候应用还在做别的操作无法渲染,则会出现丢帧的情况。
避免丢帧的办法:
1.避免不必要的布局嵌套。
2.避免overdraw。
overdraw的处理方案
1.移除不必要的background。通过手机的GPU Overdraw开关来查看布局是否overdraw。
2.利用clipRect进行部分绘制,避免重叠的图像整体绘制。
详情看这篇博客