性能优化(7.4)-卡顿优化实例解析

作者: ZJ_Rocky | 来源:发表于2017-11-15 13:07 被阅读108次

    主目录见:Android高级进阶知识(这是总目录索引)
     卡顿优化这应该是写代码时候要注意的,我们上一篇文章布局优化(扁平化,Merge的使用,ViewStub的使用)里面总结了一些解决UI卡顿的方法,今天我这里重新贴一下:

    • 在拼接字符串时候尽量使用StringBuilder避免大量的GC导致卡顿;
    • 避免在主线程做大量的计算任务,比如递归的操作导致CPU时间占用长导致卡顿;
    • 去掉window的背景,因为DecorView默认会有一个纯色的背景,在我们布局设置了背景的话,那么这个背景对我们来说是多余的;
    • 去掉不必要的背景,因为在我们布局有嵌套的情况下,如果都设置了背景的话有可能存在不必要的背景导致重绘;
    • 利用clipRect的方式来减少绘制层数,一个典型的例子就是扑克牌重叠导致重绘;
    • 利用include,merge,ViewStub等标签来减少嵌套层数。

    今天这里我主要来说说第一个使用StringBuilder和第二个在主线程做大量的计算任务导致的卡顿,这里的例子也是看到别人当初写的一个我感觉很好的例子,这里用来给大家分享一下。

    一.目标

    我们这篇文章主要是为了给大家分享两个例子然后让大家学会看工具的报告情况,所以我们目标就是:
    1.学会懂得优化例子;
    2.学会通过报告来看例子中出现的问题。

    二.例子分析

    1.拼接字符串例子

     我们都知道,String型的变量是不可改变的对象,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多低。而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些额外的对象进行操作了,当然速度就快了。
     例子这里利用WebView加载一张gif图片,然后在gif运动的时候,主线程去执行我们的大量拼接字符串操作来模拟卡顿情况。我们这里的加载gif图片代码如下:

            WebView webView = (WebView) findViewById(R.id.webview);
            webView.getSettings().setUseWideViewPort(true);
            webView.getSettings().setLoadWithOverviewMode(true);
            webView.loadUrl("file:///android_asset/shiver_me_timbers.gif");
    

    gif图片如下图所示:


    gif动图

    然后我们执行我们的业务代码,代码详细如下:

      public void imPrettySureSortingIsFree() {
            int dimension = 300;
            int[][] lotsOfInts = new int[dimension][dimension];
            Random randomGenerator = new Random();
            for(int i = 0; i < lotsOfInts.length; i++) {
                for (int j = 0; j < lotsOfInts[i].length; j++) {
                    lotsOfInts[i][j] = randomGenerator.nextInt();
                }
            }
    
            for(int i = 0; i < lotsOfInts.length; i++) {
                String rowAsStr = "";
                //排序
                int[] sorted = getSorted(lotsOfInts[i]);
                //拼接打印
                for (int j = 0; j < lotsOfInts[i].length; j++) {
                    rowAsStr += sorted[j];
                    if(j < (lotsOfInts[i].length - 1)){
                        rowAsStr += ", ";
                    }
                }
            }
    }
    
    public int[] getSorted(int[] input){
            int[] clone = input.clone();
            Arrays.sort(clone);
            return clone;
        }
    

    我们看到上面一段代码在for循环时候forwAsStr是个String类型的变量,然后在循环过程中会不断地拼接字符串,这样会造成大量的对象创建,导致gc进行工作,造成内存的抖动,我们下面看下Android Profiler中cpu与memory的情况:


    内存抖动

    我们看到,memory在代码执行时间内出现了一段时间的陡增,然后在gc之后又回归到正常范围,且出现了界面的卡顿。同时我们可以看下traceView的报告情况:


    内存抖动traceView
    从这个报告我们可以看出,在代码执行的时间内,有GC的后台进程进行垃圾回收,可以看出确实是出现了内存的陡增回收情况。那么针对以上的代码我们进行优化如下:
      public void imPrettySureSortingIsFree() {
            int dimension = 300;
            int[][] lotsOfInts = new int[dimension][dimension];
            Random randomGenerator = new Random();
            for(int i = 0; i < lotsOfInts.length; i++) {
                for (int j = 0; j < lotsOfInts[i].length; j++) {
                    lotsOfInts[i][j] = randomGenerator.nextInt();
                }
            }
    
            // 使用StringBuilder完成输出,我们只需要创建一个字符串即可,不需要浪费过多的内存
            StringBuilder sb = new StringBuilder();
            String rowAsStr = "";
            for(int i = 0; i < lotsOfInts.length; i++) {
                // 清除上一行
                sb.delete(0, rowAsStr.length());
                //排序
                int[] sorted = getSorted(lotsOfInts[i]);
                //拼接打印
                for (int j = 0; j < lotsOfInts[i].length; j++) {
                    sb.append(sorted[j]);
                    if(j < (lotsOfInts[i].length - 1)){
                        sb.append(", ");
                    }
                }
                rowAsStr = sb.toString();
            }
        }
    
        public int[] getSorted(int[] input){
            int[] clone = input.clone();
            Arrays.sort(clone);
            return clone;
        }
    

    我们看到代码优化后,我们使用了StringBuilder来进行字符串的拼接,StringBuiler和StringBuffer的区别就是前面是非线程安全的,后面是线程安全的,因为我们这里不涉及多线程情况,所以我们用前一个效率相对会高点,我们这里来看看同样的Android Profiler的报告:


    内存抖动优化

    我们看到这里没有出现内存的突然增加,只能看到非常小幅度地增加。说明我们这里的优化起作用了。同样我们来看下traceView的报告:


    内存抖动优化traceView

    我们看到这段代码执行的过程中没有出现GC回收的情况了,也进一步说明了我们优化起作用了。

    2.斐波那契数列递归

    同样,这里来讲我们的第二个例子,我们知道大量地递归操作会占用我们cpu大量的时间,如果在主线程占用大量的cpu时间我们的ui可能会出现卡顿问题,我们这里看下我们的斐波那契例子:

      public int computeFibonacci(int positionInFibSequence) {
            //0 1 1 2 3 5 8
            if (positionInFibSequence <= 2) {
                return 1;
            } else {
                return computeFibonacci(positionInFibSequence - 1)
                        + computeFibonacci(positionInFibSequence - 2);
            }
        }
    

    我们看到这是个典型的斐波那契数列递归的例子,我们来看看Android Profiler的情况:


    斐波那契

    positionInFibSequence这里传进去是100,我们发现结果非常明显,cpu出现了锐利地抖动,而且一下子增加到百分百,可想而知,cpu的时间完全被占用了,导致了动画直接卡死。我们同时来看traceView的结果:


    斐波那契traceView

    我们看到函数的执行是一整片过去也就是说这个函数的执行时间非常长,同时我们看到cpu占用达到了96%而且calls为1,递归调用达到3700,明显地说明了递归对cpu的负荷是非常大的,那么我们来优化一下这个算法:

     //优化后的斐波那契数列的非递归算法 caching缓存+批处理思想
        public int computeFibonacci(int positionInFibSequence) {
            int prev = 0;
            int current = 1;
            int newValue;
            for (int i=1; i<positionInFibSequence; i++) {
                newValue = current + prev;
                prev = current;
                current = newValue;
            }
            return current;
        }
    

    我们看到这里没有用递归来做了,而是用一个prev和current,newValue来存储,我们来看优化后的Android Profiler报告:


    斐波那契优化

    可以看到我们优化之后,cpu和memory没有出现异常地状况,而是正常地波动,说明我们的优化是有效果的,同时我们来看下traceView的结果:


    斐波那契优化traceView.png

    同时我们看到,方法的执行呈现了有序地间隔,同时cpu占用变小,递归变为0.说明我们的优化成功了,我们的页面也没有出现卡顿现象。

    总结:到这里我们的例子已经分享完成,如果大家有什么好的例子可以分享出来哈,大家共同进步,如果有必要,之后我会详细讲下这些工具的使用和详细的参数,希望大家有所收获。

    相关文章

      网友评论

        本文标题:性能优化(7.4)-卡顿优化实例解析

        本文链接:https://www.haomeiwen.com/subject/swdcvxtx.html