Android UI卡顿优化的套路

      在开发调试app时, 总是碰到各种卡段/跳帧的问题。 Android系统每隔16ms会刷新一帧图像,两帧图像的刷新周期超过16ms就会感觉卡顿。

     深度解析图像绘制过程: GPU 三缓存和垂直同步 

https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/


     下面就聊一下我的经验。

      卡顿的原因包括 1、 主线程执行了耗时操作(PS:UI渲染是在主线程执行的, 所以主线程的耗时操作会影响UI绘制)。  2、 app占用内存过高(大多数情况因为图片没回收、文件没关闭、数据库cursor没关闭等。) 3、手机CPU/内存使用率过高(打开很多app)。

     一、 最笨的方法(当然也是最有效的方法), 逐段注释你怀疑的代码, 然后运行观察效果, 并逐渐缩小范围直到找出原因。


      二、点击app各个按钮并执行adb shell cat /proc/meminfo或adb shell procrank命令并配合图形工具实时观测内存变化, 尤其关注是内存抖动的情况。 技巧:使用StrickMode判断Cursor泄漏。



    三、 每个android进程至少有1个looper在循环处理Message, 我们可以看看Message的执行时间,判断UI线程是否有耗时操作。

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
            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.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();
        }
    }


每个Message都会打印“Dispatching to”和“Finished to” , 我们只要计算二者的时间差是否超过阈值就可以了。 因为mLoggin默认是空的, 所以我们要先设置一下。

      Looper.getMainLooper().setMessageLogging(new Printer() {
            private final String START = ">>>>> Dispatching";
            private final String END = "<<<<< Finished";
            private long startTime = 0;
            private final int PERIOD = 1000;  //单位:毫秒。判断耗时操作的阈值

            @Override
            public void println(String x) {
                if (x.startsWith(START)) {
                    startTime = System.currentTimeMillis();
                }
                if (x.startsWith(END) && System.currentTimeMillis()-startTime>PERIOD ) {
                    //打印堆栈
                    StringBuilder sb = new StringBuilder();
                    StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
                    for (StackTraceElement s : stackTrace) {
                        sb.append(s.toString() + "\n");
                    }
                    Log.e("TAG", sb.toString());
                }
            }
        });


    四、使用merge、ViewStub、uiautomakeviewer和hierarchyviewer减少布局层级和渲染控件, 尽量不使用layout_weight(measure了2次),使用LinearLayout替换RelativeLayout。 如果确定了宽高,尽量不实用wrap_content和match_parent。


   五、使用JakeWharton开发的hugo三方库(基于AOP思想的AspectJ实现的)分析函数耗时, 这位大神是square公司的,还开发了OkHttp,ButterKnife。

 github地址:https://github.com/JakeWharton/hugo

Simply add @DebugLog to your methods and you will automatically get all of the things listed above logged for free.

@DebugLog
public String getName(String first, String last) {
  SystemClock.sleep(15); // Don't ever really do this!
  return first + " " + last;
}
V/Example: ⇢ getName(first="Jake", last="Wharton")
V/Example: ⇠ getName [16ms] = "Jake Wharton"

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值