博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android开发——监控造成UI卡顿的原因
阅读量:4045 次
发布时间:2019-05-24

本文共 4627 字,大约阅读时间需要 15 分钟。

0.  前言

Android只有主线程才能更新UI。如果界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感觉。

Android使用消息机制进行UI更新的,如果在主线程handlerdispatchMessage方法进行了耗时操作,就会发生UI卡顿。

本文原创,转载请注明出处:

 

1.  dispatchMessage方法在哪

dispatchMessage()是在Looper.loop()里调用,源码如下:

public static void loop() {    final Looper me = myLooper();    if (me == null) {        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");    }    final MessageQueue queue = me.mQueue;    // Make sure the identity of this thread is that of the local process,    // and keep track of what that identity token actually is.    Binder.clearCallingIdentity();    final long ident = Binder.clearCallingIdentity();    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        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);        }        // 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();    }}

所以说,第27行的代码就是可能发生UI卡顿的地方。注意这行代码的前后,有两个logging。也就是说在执行第27代码的前后,如果设置了logging,会分别打印出“>>>>> Dispatching to”和“<<<<< Finished to”这样的Log。这样就给我们监视两次Log之间的时间差,来判断是否发生了卡顿。

2.  设置logging

主要看一下21行的mLogging是什么,源码如下所示:

public final class Looper {    private Printer mLogging;    public void setMessageLogging(@Nullable Printer printer) {        mLogging = printer;    }}public interface Printer {    void println(String x);}

LoopermLogging是私有的,并且提供了setMessageLogging(@Nullable Printer printer)方法,所以我们可以自己实现一个Printer,在通过setMessageLogging()方法传入即可

public class AppContext extends Application {    @Override    public void onCreate() {        super.onCreate();	   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();                }            }        });    }}

当我们设置了mLogging之后,loop()方法中就会回调logging.println,并将带有“>>>>> Dispatching to”和“<<<<< Finished to”的字符串传入,我们就可以拿到这两条信息。

如果“>>>>> Dispatching to”信号发生了,我们就假定发生了卡顿(这里我们设定1秒钟的卡顿判定阈值),并且发送一个延迟1秒钟的任务,这个任务就用于在子线程打印出造成卡顿的UI线程里的堆栈信息。而如果没有卡顿,即在1秒钟之内我们检测到了“<<<<< Finished to”信号,就会移除这个延迟1秒的任务。

 

3.  LogMonitor的实现

public class LogMonitor {    private static LogMonitor sInstance = new LogMonitor();    private HandlerThread mHandlerThread = new HandlerThread("log");    private Handler mHandler;    private LogMonitor() {        mHandlerThread.start();        mHandler = new Handler(mHandlerThread.getLooper());    }    private static Runnable mRunnable = 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 void startMonitor() {        mHandler.postDelayed(mRunnable, 1000);    }    public void removeMonitor() {        mHandler.removeCallbacks(mRunnable);    }}

这里我们使用HandlerThread来构造一个HandlerHandlerThread继承自Thread,实际上就一个Thread,只不过它比普通的Thread多了一个Looper,对外提供自己这个Looper对象的get方法,然后创建Handler时将HandlerThread中的looper对象传入。这样我们的mHandler对象就是与HandlerThread这个非UI线程绑定的了,这样它处理耗时操作将不会阻塞UI

总之,如果UI线程阻塞超过1秒,就会在子线程中执行mRunnable,打印出UI线程当前的堆栈信息,如果处理消息没有超过1秒,则会实时的remove掉这个mRunnable

 

4.  测试

Activity中设置一个按钮,并且设置点击后睡3秒。便可以看见打印出的Log信息。帮助我们定位到耗时的地方。

你可能感兴趣的文章
yuv to rgb 转换失败呀。天呀。谁来帮帮我呀。
查看>>
yuv420 format
查看>>
yuv420 还原为RGB图像
查看>>
LED恒流驱动芯片
查看>>
驱动TFT要SDRAM做为显示缓存
查看>>
使用file查看可执行文件的平台性,x86 or arm ?
查看>>
qt5 everywhere 编译summary
查看>>
qt 创建异形窗体
查看>>
可重入函数与不可重入函数
查看>>
简单Linux C线程池
查看>>
内存池
查看>>
输入设备节点自动生成
查看>>
GNU hello代码分析
查看>>
Qt继电器控制板代码
查看>>
wpa_supplicant控制脚本
查看>>
gstreamer相关工具集合
查看>>
RS232 四入四出模块控制代码
查看>>
linux 驱动开发 头文件
查看>>
/etc/resolv.conf
查看>>
container_of()传入结构体中的成员,返回该结构体的首地址
查看>>