博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
五、Android性能优化之UI卡顿分析之内存抖动和计算性能优化
阅读量:5739 次
发布时间:2019-06-18

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

#####渲染刷新机制 VSYNC(垂直刷新/绘制)

60HZ是屏幕刷新理想的频率。60fps---一秒内绘制的帧数。 24帧/秒 电源胶卷时代

在60fps内,系统会得到发送的VSYNC(垂直刷新)信号qu去进行渲染,就会正常地绘制。 60fps要求:每一帧只能停留16ms.

VSYNC:有两个概念 1)Refresh Rate:屏幕在一秒时间内刷新屏幕的次数----有硬件的参数决定,比如60HZ. 2)Frame Rate:GPU在一秒内绘制操作的帧数,比如:60fps。

GPU刷新:GPU帮助我们将UI组件等计算成纹理Texture和三维图形Polygons 同时会使用OpenGL---会将纹理和Polygons缓存在GPU内存里面。

GPU会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。

不幸的是,刷新频率和帧率并不是总能够保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现Tearing的现象(画面上下两部分显示内容发生断裂,来自不同的两帧数据发生重叠)。

理解图像渲染里面的双重与三重缓存机制,这个概念比较复杂,请移步查看这里:, 还有这里。 通常来说,帧率超过刷新频率只是一种理想的状况,在超过60fps的情况下,GPU所产生的帧数据会因为等待VSYNC的刷新信息而被Hold住,这样能够保持每次刷新都有实际的新的数据可以显示。但是我们遇到更多的情况是帧率小于刷新频率。

在这种情况下,某些帧显示的画面内容就会与上一帧的画面相同。糟糕的事情是,帧率从超过60fps突然掉到60fps以下,这样就会发生LAG,JANK,HITCHING等卡顿掉帧的不顺滑的情况。这也是用户感受不好的原因所在。

#####UI卡顿分析

#####UI卡顿的根本原因

Android每个16ms就会绘制一次Activity,通过上述的结论我们知道,如果由于一些原因导致了我们的逻辑、CPU耗时、GPU耗时大于16ms,UI就无法完成一次绘制,那么就会造成卡顿。简单的一句话就是:卡主线程了。

比如说,在16ms内,发生了频繁的GC:

当这些GC所用时间超过一般值,或者一大堆一起执行会耗费庞大的帧象时间,这是很麻烦的事情。

#####1.外部引起的 比如:Activity里面直接进行网络访问/大文件的IO操作

######外部因素之--内存抖动的问题引起卡顿分析 为了模拟UI卡顿,我们利用了WebView加载一张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的卡顿情况来模拟UI卡顿。

为了模拟内存抖动,我们在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 += ", ";                }            }            Log.i("ricky", "Row " + i + ": " + rowAsStr);        }   public int[] getSorted(int[] input){        int[] clone = input.clone();        Arrays.sort(clone);        return clone;    }复制代码

这段代码主要是模拟大量的堆内存分配与释放String对象,频繁触发GC,导致UI卡顿。通过Memory Monitor可以看出:

内存方面是发生了抖动,但是CPU的占用几乎不动。

为了分析内存的情况,我们结合之前的文章,使用一些工具来分析,因为实际情况是,我们不知道哪里的代码导致UI卡顿。

首先我们使用Android Studio自带的Allocation Tracking工具来跟踪内存分配情况。我们在UI卡顿的过程中收集内存分配的信息如下:

Total allocation:13099(内存分配次数:13039次) Total size:208.62k (内存分配大小:208.62k)

######我们看到MemoryChurnActivity.java 这个类所占内存资源为19.9%,这是很大的,也是很不正常的。

#####解决办法

解决办法,这个Demo中,为了解决GC频繁的问题,我们可以利用StringBudiler代替String:

/**     * 排序后打印二维数组,一行行打印     */    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 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++) {                rowAsStr += sorted[j];                sb.append(sorted[j]);                if(j < (lotsOfInts[i].length - 1)){                    sb.append(", ");                }            }            rowAsStr = sb.toString();            Log.e("main", "Row " + i + ": " + rowAsStr);        }    }    public int[] getSorted(int[] input){        int[] clone = input.clone();        Arrays.sort(clone);        return clone;    }复制代码

######我们发现内存抖动现象大幅减弱 ######注意,GC是无法避免的,我们要避免的是频繁的GC,因此这里的优化实质上是内存优化。

#####使用Android Device Monitor工具分析 ######点击Tools选择Android Device Monitor

######外部因素之--方法耗时(CPU占用)的问题引起卡顿分析

同理,我们利用斐波那契数列来模拟,我们计算到第40个:

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_caching_exercise);        Button theButtonThatDoesFibonacciStuff = (Button) findViewById(R.id.caching_do_fib_stuff);        theButtonThatDoesFibonacciStuff.setText("计算斐波那契数列");        theButtonThatDoesFibonacciStuff.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.i(LOG_TAG, String.valueOf(computeFibonacci(40)));            }        });        WebView webView = (WebView) findViewById(R.id.webview);        webView.getSettings().setUseWideViewPort(true);        webView.getSettings().setLoadWithOverviewMode(true);        webView.loadUrl("file:///android_asset/shiver_me_timbers.gif");    }    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);        }    }复制代码

点击Button,我们粗略地通过Monitor进行分析:

可以看到CPU的占用突然提高了,但是内存的使用几乎不动。

我们也可以通过TraceView来进行分析方法的耗时:

可以看到,黑乎乎一篇的就是一些耗时的“重灾区”。我们点击放大重灾区:

这里可以看到调用了我自己Activity方法。

往往实际情况比较复杂,我们如果要知道是哪个类的问题,一般需要不断追溯父方法,也就是找到谁调用了这个方法,最终可以分析出是哪个类有问题。

如果我们要看哪个方法耗时,可以根据右边的一些参数来进行分析。其中,Incl的意思是该方法包括其所调用的其他方法的时间,Excl的意思是不包含其所调用的其他方法的时间(纯粹是本身调用的时间)。Recursive是递归调用的意思。CPU Time就是占用CPU的时间,Real Time的意思就是实际时间,包括内存分配、回收等其他的时间,Real Time比CPU Time大。后面还是一些平均调用时间,一个方法可能本身耗时很少,但是可能会被频繁(递归)调用,这时候就需要分析平均调用时间。

分析耗时的时候,我们要不断追溯子方法的耗时情况:

一路跟踪下来,发现到了第11层以后,耗时百分比就变成0.2%了,那么我们可以暂时确定耗时的根源就是第10层的相关方法。如果你发现,Incl百分比很大,但是该方法本身的Excl百分比很小,那么改方法就不是耗时的根源,如下图所示,读者可以自行分析:

最终我们确定是我们自己的Activity的斐波那契计算的那个方法的耗时导致UI卡顿的。

Profile Panel是Traceview的核心界面,其内涵非常丰富。它主要展示了某个线程(先在Timeline Panel中选择线程)中各个函数调用的情况,包括CPU使用时间、调用次数等信息。而这些信息正是查找hotspot的关键依据。所以,对开发者而言,一定要了解Profile Panel中各列的含义。笔者总结了其中几个重要列的作用,如表1-1所示: 表1-1 Profile Panel各列作用说明 列名 描述

另外,每一个Time列还对应有一个用时间百分比来统计的列(如Incl Cpu Time列对应还有一个列名为Incl Cpu Time %的列,表示以时间百分比来统计的Incl Cpu Time)。

列名 描述
Name 该线程运行过程中所调用的函数名
Incl Cpu Time 某函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time 某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间
Incl Real Time 某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time 某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间
Call+Recur Calls/Total 某函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call 某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间
Real Time/Call 同CPU Time/Call类似,只不过统计单位换成了真实时间

另外,每一个Time列还对应有一个用时间百分比来统计的列(如Incl Cpu Time列对应还有一个列名为Incl Cpu Time %的列,表示以时间百分比来统计的Incl Cpu Time)。

#####解决办法

修改方法(算法),使得方法不耗时。 放到子线程中,例如网络访问、大文件操作等,防止ANR。 ######例如上述例子中,我们可以使用循环代( caching缓存+批处理思想)替递归实现斐波那契数列的计算:

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

######发现优化后的斐波那契数列的CPU占用情况,没有出现异常抖动现象。

特别感谢:

转载地址:http://wtfzx.baihongyu.com/

你可能感兴趣的文章
NPM教程
查看>>
Java not support java EE1.3
查看>>
LAMP环境搭建1-mysql5.5
查看>>
第三课 Linux目录及文件管理、用户组管理及bash重定向
查看>>
shell 脚本攻略--小试牛刀
查看>>
spring boot view override
查看>>
bzoj 2282: [Sdoi2011]消防
查看>>
我的友情链接
查看>>
centos5.9使用RPM包搭建lamp平台
查看>>
关于C#面向对象2
查看>>
Javascript String类的属性及方法
查看>>
[LeetCode] Merge Intervals
查看>>
SharePoint 读取 Site Columns 的数据并绑定到DropdownList
查看>>
使用 axios 详解
查看>>
iOS - Regex 正则表达式
查看>>
第 68 章 Logical Volume Manager (LVM)
查看>>
膝盖中了一箭之康复篇-第八个月暨2月份目标总结
查看>>
IPA提交APPStore问题记录(一)
查看>>
有利于seo优化的网站地图不能取巧
查看>>
快照产品体验优化
查看>>