• 性能优化-渲染机制及优化


    在手机上显示图片,播放视频,这是很常见的手机操作,也就是屏幕的绘制在软件开发中几乎是每个应用都会打交道的,这篇文章记录了渲染机制以及如何做优化

    卡顿产生的原因

    • 在Activity中直接进行网络访问/大文件的IO操作还有就是自定义的View没有优化好,以上的情况都有可能造成卡顿,甚至是无响应

    • 当产生大量的垃圾时,GC回收大量垃圾的时候,也会造成卡顿

    • Android每隔16ms就会重绘一次Activity,也就是说必须在16ms以内完成屏幕刷新所要求的参数设置,之所以是16ms,那是因为大多说手机的刷新率是60Hz,那么也就是说1000ms/60=16.66ms,如果在这个时间内,没有完成参数设置,那么就会产生丢帧现象,在视觉上体现出来就是卡顿

    内存抖动造成卡顿分析及解决办法

    短时间内分配大量内存,会造成UI线程短时间阻塞,此时如果在绘制屏幕,那么此时就会造成卡顿
    例如在绘制屏幕的时候有如下计算执行。那么此时会造成卡顿现象

    /**
     * 排序后打印二维数组,一行行打印
     */
    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 += ", ";
                }
            }
            Log.i(TAG, "Row " + i + ": " + rowAsStr);
        }
    }
    
    public int[] getSorted(int[] input){
    	int[] clone = input.clone();
    	Arrays.sort(clone);
    	return clone;
    }
    

    上述代码在第二个for循环中,大量创建对象并弃用,产生了大量的垃圾,此时GC回收垃圾,占用主线程,直接就导致了GC回收占用时间加上屏幕绘制时间高于16ms,此时就发生了卡顿,其卡顿时间取决于GC的工作时间,故在主线程计算这种数据的时候(这种情况放在子线程最好),要尽量减少内存的分配,防止内存抖动,从而有效避免卡顿现象的发生
    其改进方法就是使用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();
    	Log.i(TAG, "Row " + i + ": " + rowAsStr);
    }
    

    计算性能占用CPU造成卡顿及解决办法

    有时候在执行函数的时候,会占用大量CPU资源,尤其是递归函数的执行过程,会占用CPU大量时间,那么此时也有可能造成卡顿
    例如在主线程执行斐波那契梳理的递归实现时,如果此时在执行屏幕绘制,那么也会造成卡顿

    public int computeFibonacci(int pos) {
        if (pos <= 2) {
            return 1;
        } else {
            return computeFibonacci(pos - 1) + computeFibonacci(pos - 2);
        }
    }
    

    使用TraceView观察到,这个函数在执行的时候占用了CPU大量的时间,造成了卡顿
    这种情况采用批处理和缓存思想,储存运算结果,更新数据来得到最终结果,避免递归调用造成的大量CPU时间占用

    public int computeFibonacci(int pos) {
        int prev = 0;
        int current = 1;
        int newValue;
        for (int i = 1; i < pos; i++) {
            newValue = current + prev;
            prev = current;
            current = newValue;
        }
        return current;
    }
    

    渲染机制简单介绍

    • Android系统的渲染由两个关键组件构成:CPU和GPU
      在CPU方面,最常见的性能问题是不必要的布局和失效,这些内容必须在视图层次结构中进行测量、清除并重新创建,引发这种问题通常有两个原因:一是重建显示列表的次数太多,二是花费太多时间作废视图层次并进行不必要的重绘,这两个原因在更新显示列表或者其他缓存GPU资源时导致CPU工作过度
      在GPU方面,最常见的问题是我们所说的过度绘制(overdraw),通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间
    • 图像的显示是通过栅格化来完成的,以下的图片说明了栅格化的过程
      栅格化过程
    • CPU首先将布局文件转化为多边形或者纹理,然后交由GPU去负责栅格化
      CPU2GPU
    • 渲染性能的优化就是尽可能地上传数据到GPU,然后尽可能长地在不修改的情况下保存数据,因为每次上传资源到GPU时,我们都会浪费宝贵的处理时间

    GPU存在的主要问题

    • GPU性能强大,然后其常见的一个问题就是过度绘制
      过度绘制:指屏幕上的某个像素点在同一帧的时间内被绘制了多次
    • 那么此时用户在界面上只能看到最上面一层,下面绘制的就属于过度绘制了
      要查看过度绘制很简单,在开发者选项里面打开Show GPU overdraw即可
      然后就能在屏幕上看到多种颜色,这些颜色代表过度绘制的倍数
      过度绘制
      最理想的情况就是全部都是蓝色,因此可以通过这个现象来优化我们的app

    过度绘制的解决办法

    1. 清除不必要的背景和图片
      材料主题会绘制一遍屏幕,我们如果这时候再设置背景颜色,那么会产生2倍过度绘制,此时就需要取消其默认的背景图片,也就是将Activity的背景图片设为null,其方法如下:
    getWindow().setBackgroundDrawable(null);
    
    1. 清除XML文件中,不必要的背景声明

    CPU的工作部分

    • CPU将布局文件转化为GPU能够识别的对象,通过GPU的栅格化,从而显示在屏幕上,这是在DisplayList帮助下完成的,DisplayList将数据传递给GPU,GPU通过OpenGL将屏幕绘制出来
    • 任何时候View的绘制内容发生变化,都需要重新创建DisplayList并重新执行指令更新到屏幕
      CPU的view转化检测
      工具:Hierarchy Viewer检测(在Android Monitor里面)
      选中应用 -> 点击Load the view hierarchy into the tree view
      然后再点击Obtain layout times for tree rooted at selected node
      就会看到如下界面
      布局视图优化前
      三个圆点分别代表:测量、布局、绘制三个阶段的性能表现
      1)绿色:渲染的管道阶段,这个视图的渲染速度快于至少一半的其他的视图
      2)黄色:渲染速度比较慢的50%
      3)红色:渲染速度非常慢
      观察其分布,自己写的布局杂乱无章,深层嵌套,而且红点很多,这样会降低CPU的解析效率
      优化策略:
      当我们的布局是用的FrameLayout的时候,我们可以把它改成merge,可以避免自己的帧布局和系统的ContentFrameLayout帧布局重叠造成重复计算(measure和layout)
      ViewStub:当加载的时候才会占用。不加载的时候就是隐藏的,仅仅占用位置
      优化思想:查看自己的布局,层次是否很深以及渲染比较耗时,然后想办法能否减少层级以及优化每一个View的渲染时间
      布局视图优化后

    总结

    减少CPU计算时间

    CPU的优化,从减轻加工View对象成Polygons和Texture来下手
    View Hierarchy中包涵了太多的没有用的view,这些view根本就不会显示在屏幕上面,一旦触发测量和布局操作,就会拖累应用的性能表现。

    减少CPU将计算好的Polygons和Texture传递到GPU的时间

    OpenGL ES API允许数据上传到GPU后可以对数据进行保存,做了缓存

    减少GPU进行格栅化

    优化:尽量避免过度绘制(overdraw)
    GPU如何优化:

    1. 背景经常容易造成过度绘制
      由于布局设置了背景,同时用到的MaterialDesign的主题会默认给一个背景
      解决的办法:将主题添加的背景去掉

    2. 自定义控件如何处理过度绘制(多张图片有重叠)
      可以通过裁剪来处理canvas.clipRect()
      前n-1张

    private void drawDroidCard(Canvas canvas,List<DroidCard> mDroidCards,int i) {
        DroidCard c = mDroidCards.get(i);
        canvas.save();
        canvas.clipRect((float)c.x,0f,(float)(mDroidCards.get(i+1).x),(float)c.height);
        canvas.drawBitmap(c.bitmap,c.x,0f,paint);
        canvas.restore();
    }
    

    第n张

    private void drawLastDroidCard(Canvas canvas,DroidCard c) {
        canvas.drawBitmap(c.bitmap,c.x,0f,paint);
    }
    
  • 相关阅读:
    ASP.NET编程的十大技巧
    C#学习心得(转)
    POJ 1177 Picture (线段树)
    POJ 3067 Japan (树状数组)
    POJ 2828 Buy Tickets (线段树)
    POJ 1195 Mobile phones (二维树状数组)
    HDU 4235 Flowers (线段树)
    POJ 2886 Who Gets the Most Candies? (线段树)
    POJ 2418 Cows (树状数组)
    HDU 4339 Query (线段树)
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664639.html
Copyright © 2020-2023  润新知