Android UI性能优化 检测应用中的UI卡顿
本文已在我的公众号hongyangAndroid首发。
转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/58626355
本文出自张鸿洋的博客
一、概述
在做app性能优化的时候,大家都希望能够写出丝滑的UI界面,以前写过一篇博客,主要是基于Google当时发布的性能优化典范,主要提供一些UI优化性能示例:
- Android UI性能优化实战 识别绘制中的性能问题
实际上,由于各种机型的配置不同、代码迭代历史悠久,代码中可能会存在很多在UI线程耗时的操作,所以我们希望有一套简单检测机制,帮助我们定位耗时发生的位置。
本篇博客主要描述如何检测应用在UI线程的卡顿,目前已经有两种比较典型方式来检测了:
- 利用UI线程Looper打印的日志
- 利用Choreographer
两种方式都有一些开源项目,例如:
- https://github.com/markzhai/AndroidPerformanceMonitor [方式1]
- https://github.com/wasabeef/Takt [方式2]
- https://github.com/friendlyrobotnyc/TinyDancer [方式2]
其实编写本篇文章,主要是因为发现一个还比较有意思的方案,该方法的灵感来源于一篇给我微信投稿的文章:
- https://github.com/android-notes/Cockroach
该项目主要用于捕获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 loggerPrinter logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}// focusmsg.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";@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void doFrame(long l) {if (LogMonitor.getInstance().isMonitor()) {LogMonitor.getInstance().removeMonitor(); } LogMonitor.getInstance().startMonitor();Choreographer.getInstance().postFrameCallback(this);}});}
}
第一次的时候开始检测,如果大于阈值则输出相关堆栈信息,否则则移除。
使用方式和上述一致。
四、 利用Looper机制
先看一段代码:
new Handler(Looper.getMainLooper()).post(new Runnable() {@Overridepublic void run() {}}
该代码在UI线程中的MessageQueue中插入一个Message,最终会在loop()方法中取出并执行。
假设,我在run方法中,拿到MessageQueue,自己执行原本的Looper.loop()
方法逻辑,那么后续的UI线程的Message就会将直接让我们处理,这样我们就可以做一些事情:
public class BlockDetectByLooper {private static final String FIELD_mQueue = "mQueue";private static final String METHOD_next = "next";public static void start() {new Handler(Looper.getMainLooper()).post(new Runnable() {@Overridepublic void run() {try {Looper mainLooper = Looper.getMainLooper();final Looper me = mainLooper;final MessageQueue queue;Field fieldQueue = me.getClass().getDeclaredField(FIELD_mQueue);fieldQueue.setAccessible(true);queue = (MessageQueue) fieldQueue.get(me);Method methodNext = queue.getClass().getDeclaredMethod(METHOD_next);methodNext.setAccessible(true);Binder.clearCallingIdentity();for (; ; ) {Message msg = (Message) methodNext.invoke(queue);if (msg == null) {return;}LogMonitor.getInstance().startMonitor();msg.getTarget().dispatchMessage(msg);msg.recycle();LogMonitor.getInstance().removeMonitor();}} catch (Exception e) {e.printStackTrace();}}});}
}
其实很简单,将Looper.loop里面本身的代码直接copy来了这里。当这个消息被处理后,后续的消息都将会在这里进行处理。
中间有变量和方法需要反射来调用,不过不影响查看
msg.getTarget().dispatchMessage(msg);
执行时间,但是就不要在线上使用这种方式了。
不过该方式和以上两个方案对比,并无优势,不过这个思路挺有意思的。
使用方式和上述一致。
最后,可以考虑将卡顿日志输出到文件,慢慢分析;可以结合上述原理以及自己需求开发做一个合适的方案,也可以参考已有开源方案。
参考
- https://github.com/markzhai/AndroidPerformanceMonitor
- https://github.com/wasabeef/Takt
- https://github.com/friendlyrobotnyc/TinyDancer
我的微信公众号:hongyangAndroid
(可以给我留言你想学习的文章,支持投稿)
Android UI性能优化 检测应用中的UI卡顿相关推荐
- android的UI性能优化
设计师,开发人员,需求研究和测试都会影响到一个app最后的UI展示,所有人都很乐于去建议app应该怎么去展示UI.UI也是app和用户打交道的部分,直接对用户形成品牌意识,需要仔细的设计.无论你的ap ...
- android app性能优化大汇总(UI渲染性能优化)
UI性能测试 性能优化都需要有一个目标,UI的性能优化也是一样.你可能会觉得"我的app加载很快"很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交 ...
- Android UI性能优化
对于Android的UI性能优化,我一般从5个途径来分析: 1.Debug GPU overdraw; 2.Android CPU Profile: 3.dumpsys gfxinfo: 4.Prof ...
- Android UI性能优化详解
此文来自于MrPeak杂货铺,由于没法转载,只能贴这了,妄作者见谅:http://mrpeak.cn/android/2016/01/11/android-performance-ui 设计师,开发人 ...
- Android APP性能优化
转载自:https://www.cnblogs.com/qwangxiao/p/8727229.html Android APP性能优化(最新总结) 导语 安卓大军浩浩荡荡,发展已近十个年头,技术优化 ...
- Android APP性能优化(一)
Android APP性能优化(最新总结) 安卓大军浩浩荡荡,发展已近十个年头,技术优化日异月新,如今Android 8.0 Oreo 都发布了,Android系统性能已经非常流畅了.但是,到了各大厂 ...
- android 应用性能优化1
1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...
- Android界面性能优化最全总结、原理剖析
界面是 Android 应用中直接影响用户体验最关键的部分.如果代码实现得不好,界面容易发生卡顿且导致应用占用大量内存. 我司这类做 ROM 的公司更不一样,预装的应用一定要非常流畅,这样给客户 ...
- Android客户端性能优化(魅族资深工程师毫无保留奉献)
Android客户端性能优化(魅族资深工程师毫无保留奉献) 转载学习:http://blog.tingyun.com/web/article/detail/155?from=groupmessage& ...
最新文章
- C和指针之数组之编程练习2
- kubernetes之五:资源管理
- hdu---2087---剪花布条
- (C++)String的用法
- MySQL-第十二篇管理结果集
- virtualbox虚拟机下的cdlinux找不到无线网卡的解决方法
- 上古计算机语言,微软开源其上古编程语言GW-BASIC
- Internet上的Linux资源
- vue创建项目自定义配置
- dux修改index.php,DUX主题修改首页轮播图为通栏模式
- java 页眉页脚_Java 修改Word页眉页脚
- 内容页的链接设置seo技巧
- wps合并重复项并求和_Excel中重复项求和的方法
- 【Linux】/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
- 正确选择餐具,健康美好生活
- Python设置断点breakpoint(免IDE)
- 计算机单片机实训报告,单片机实训总结
- Max GCD(暴力)
- 暗黑破坏神快速RR详解
- 最优化理论c语言代码,《统计学习导论基于R应用》PDF代码导图+《最优化理论与算法第2版》PDF习题指导...