分享

Android UI性能优化 检测应用中的UI卡顿

 LOVE酸奶 2021-11-21
  1. 利用Choreographer

两种方式都有一些开源项目,例如:

其实编写本篇文章,主要是因为发现一个还比较有意思的方案,该方法的灵感来源于一篇给我微信投稿的文章:

该项目主要用于捕获UI线程的crash,当我看完该项目原理的时候,也可以用来作为检测卡段方案,可能还可以做一些别的事情。

所以,本文出现了3种检测UI卡顿的方案,3种方案原理都比较简单,接下来将逐个介绍。

二、利用loop()中打印的日志


(1)原理

大家都知道在Android UI线程中有个Looper,在其loop方法中会不断取出Message,调用其绑定的Handler在UI线程进行执行。

大致代码如下:

public static void loop() {

final Looper me = myLooper();

final MessageQueue queue = me.mQueue;

// ...

for (;?? {

Message msg = queue.next(); // might block

// This must be in a local variable, in case a UI event sets the logger

Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

// focus

msg.target.dispatchMessage(msg);

if (logging != null) {

logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

}

// ...

}

msg.recycleUnchecked();

}

}

所以很多时候,我们只要有办法检测:

msg.target.dispatchMessage(msg);

此行代码的执行时间,就能够检测到部分UI线程是否有耗时操作了。可以看到在执行此代码前后,如果设置了logging,会分别打印出>>>>> Dispatching to<<<<< Finished to这样的log。

我们可以通过计算两次log之间的时间差值,大致代码如下:

public class BlockDetectByPrinter {

public static void start() {

Looper.getMainLooper().setMessageLogging(new Printer() {

private static final String START = ">>>>> Dispatching";

private static final String END = "<<<<< Finished";

@Override

public void println(String x) {

if (x.startsWith(START)) {

LogMonitor.getInstance().startMonitor();

}

if (x.startsWith(END)) {

LogMonitor.getInstance().removeMonitor();

}

}

});

}

}

假设我们的阈值是1000ms,当我在匹配到>>>>> Dispatching时,我会在1000ms毫秒后执行一个任务(打印出UI线程的堆栈信息,会在非UI线程中进行);正常情况下,肯定是低于1000ms执行完成的,所以当我匹配到<<<<< Finished,会移除该任务。

大概代码如下:

public class LogMonitor {

private static LogMonitor sInstance = new LogMonitor();

private HandlerThread mLogThread = new HandlerThread("log");

private Handler mIoHandler;

private static final long TIME_BLOCK = 1000L;

private LogMonitor() {

mLogThread.start();

mIoHandler = new Handler(mLogThread.getLooper());

}

private static Runnable mLogRunnable = new Runnable() {

@Override

public void run() {

StringBuilder sb = new StringBuilder();

StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();

for (StackTraceElement s : stackTrace) {

sb.append(s.toString() + "\n");

}

Log.e("TAG", sb.toString());

}

};

public static LogMonitor getInstance() {

return sInstance;

}

public boolean isMonitor() {

return mIoHandler.hasCallbacks(mLogRunnable);

}

public void startMonitor() {

mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK);

}

public void removeMonitor() {

mIoHandler.removeCallbacks(mLogRunnable);

}

}

我们利用了HandlerThread这个类,同样利用了Looper机制,只不过在非UI线程中,如果执行耗时达到我们设置的阈值,则会执行mLogRunnable,打印出UI线程当前的堆栈信息;如果你阈值时间之内完成,则会remove掉该runnable。

(2)测试

用法很简单,在Application的onCreate中调用:

BlockDetectByPrinter.start();

即可。

然后我们在Activity里面,点击一个按钮,让睡眠2s,测试下:

findViewById(R.id.id_btn02)

.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

}

}

});

运行点击时,会打印出log:

02-21 00:26:26.408 2999-3014/com.zhy.testlp E/TAG:

java.lang.VMThread.sleep(Native Method)

java.lang.Thread.sleep(Thread.java:1013)

java.lang.Thread.sleep(Thread.java:995)

com.zhy.testlp.MainActivity$2.onClick(MainActivity.java:70)

android.view.View.performClick(View.java:4438)

android.view.View$PerformClick.run(View.java:18422)

android.os.Handler.handleCallback(Handler.java:733)

android.os.Handler.dispatchMessage(Handler.java:95)

会打印出耗时相关代码的信息,然后可以通过该log定位到耗时的地方。

三、 利用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);

}

});

}

}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多