前段时间在开发项目时搞了一下过渡绘制的问题,一个比较复杂的界面刚开发出来,过渡绘制95%红,调了半天后,基本只有5%的红,效果不错,准备找个时间总结一下,今天刚好看到网上以为大神发了一篇文章,总结的不错,顺手牵过来啦,哈哈哈~
用户对卡顿的感知, 主要来源于界面的刷新. 而界面的性能主要是依赖于设备的UI渲染性能. 如果我们的UI设计过于复杂, 或是实现不够好, 设备又不给力, 界面就会像卡住了一样, 给用户卡顿的感觉.
在剖析卡顿的原因之前, 我们先来了解下Android中著名的”16ms”原则:
这就意味着, 我们需要在16ms内完成下一次要刷新的界面的相关运算, 以便界面刷新更新. 然而, 如果我们无法在16ms内完成此次运算会怎样呢?
例如, 假设我们更新屏幕的背景图片, 需要24ms来做这次运算. 当系统在第一个16ms时刷新界面, 然而我们的运算还没有结束, 无法绘出图片. 当系统隔16ms再发一次VSYNC信息重绘界面时, 用户才会看到更新后的图片. 也就是说用户是32ms后看到了这次刷新(注意, 并不是24ms). 这就是传说中的丢帧(dropped frame):
丢帧给用户的感觉就是卡顿, 而且如果运算过于复杂, 丢帧会更多, 导致界面常常处于停滞状态, 卡到爆.
那么会有哪些常见的情况会导致运算超过16ms, 进而丢帧, 让用户觉得卡顿呢?
一般来说, 会有以下几种情况导致卡顿这种性能问题, 我们逐一看下:
上节有说, 界面性能取决于UI渲染性能. 我们可以理解为UI渲染的整个过程是由CPU和GPU两个部分协同完成的.
其中, CPU负责UI布局元素的Measure, Layout, Draw等相关运算执行. GPU负责栅格化(rasterization), 将UI元素绘制到屏幕上.
如果我们的UI布局层次太深, 或是自定义控件的onDraw中有复杂运算, CPU的相关运算就可能大于16ms, 导致卡顿.
这个时候, 我们需要借助Hierarchy Viewer这个工具来帮我们分析布局了. Hierarchy Viewer不仅可以以图形化树状结构的形式展示出UI层级, 还对每个节点给出了三个小圆点, 以指示该元素Measure, Layout, Draw的耗时及性能.
上节说的CPU方面的, 关于GPU的绘制, 如果我们的界面存在Overdraw, 也可能导致卡顿.
Overdraw: 用来描述一个像素在屏幕上多少次被重绘在一帧上.
通俗的说: 理想情况下, 每屏每帧上, 每个像素点应该只被绘制一次, 如果有多次绘制, 就是Overdraw, 过度绘制了.
Android系统提供了可视化的方案来让我们很方便的查看overdraw的现象:
在”系统设置”–>”开发者选项”–>”调试GPU过度绘制”中开启调试:
此时界面可能会有五种颜色标识:
上面有言, 所谓Overdraw, 就是在一个像素点上绘制了多次. 常见的就是:
绘制了多重背景.
绘制了不可见的UI元素.
打开应用, 展示是这样的:
可以看到是中间列表这块overdraw比较严重. 查看代码发现:
fragment_trending_container.xml中ViewPager设置了背景:
.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:background="@color/md_white_1000"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
而ViewPager中的fragment又设置了背景:
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
android:id="@+id/refresh_layout"
android:background="@color/md_white_1000"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
删除外层ViewPager的背景再看:
可以发现中间列表区域已经不再是红色了, 但是也没有达到蓝色这个可以接受的层级. 这是因为我们的Activity默认情况下, theme会给window设置一个纯色的背景. 因为我们这里不想使用这个默认的背景,故而给layout加了一层背景, 导致了多重绘制背景.
(当然我们也可以自定义主题, 将theme的window background设置成我们想要的, 而不在布局中设置.)
可以通过如下方式去掉window的背景.
设置主题:
<item name="android:windowBackground">@nullitem>
或是代码设置, 在onCreate中:
getWindow().setBackgroundDrawable(null);
此时我们看到的效果:
已基本达到优化水平.
StrictMode用来基于线程或VM设置一些策略, 一旦检测到策略违例, 控制台将输出一些警告,包含一个trace信息展示你的应用在何处出现问题.
通常用来检测主线程中的磁盘读写或网络访问等耗时操作.
在Application或是Activity的onCreate中开启StrictMode:
public void onCreate() {
if (BuildConfig.DEBUG) {
// 针对线程的相关策略
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
// 针对VM的相关策略
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
2.4 频繁的GC
上面说的都是处理上的, CPU, GPU相关的. 实际上内存原因也可能会造成应用不流畅, 卡顿的.
简而言之, 就是执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿.
导致频繁GC有两个原因:
这些GC操作可能会造成上面说到的丢帧, 如下:
一般来说瞬间大量产生对象一般是因为我们在代码的循环中new对象, 或是在onDraw中创建对象,或者是在ListView的getView中创建对象等. 所以说这些地方是我们尤其需要注意的…