关于UI性能优化

检测应用中的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进行部分绘制,避免重叠的图像整体绘制。

详情看这篇博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值