1, 感知卡顿

用户对卡顿的感知, 主要来源于界面的刷新. 而界面的性能主要是依赖于设备的UI渲染性能. 如果我们的UI设计过于复杂, 或是实现不够好, 设备又不给力, 界面就会像卡住了一样, 给用户卡顿的感觉.

1.1 16ms原则

在剖析卡顿的原因之前, 我们先来了解下Android中著名的"16ms"原则:

Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).
为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.

就像是这样的:

16ms

这就意味着, 我们需要在16ms内完成下一次要刷新的界面的相关运算, 以便界面刷新更新. 然而, 如果我们无法在16ms内完成此次运算会怎样呢?

例如, 假设我们更新屏幕的背景图片, 需要24ms来做这次运算. 当系统在第一个16ms时刷新界面, 然而我们的运算还没有结束, 无法绘出图片. 当系统隔16ms再发一次VSYNC信息重绘界面时, 用户才会看到更新后的图片. 也就是说用户是32ms后看到了这次刷新(注意, 并不是24ms). 这就是传说中的丢帧(dropped frame):

dropped frame

丢帧给用户的感觉就是卡顿, 而且如果运算过于复杂, 丢帧会更多, 导致界面常常处于停滞状态, 卡到爆.

那么会有哪些常见的情况会导致运算超过16ms, 进而丢帧, 让用户觉得卡顿呢?

2, 卡顿原因分析及处理

一般来说, 会有以下几种情况导致卡顿这种性能问题, 我们逐一看下:

2.1 过于复杂的布局

上节有说, 界面性能取决于UI渲染性能. 我们可以理解为UI渲染的整个过程是由CPU和GPU两个部分协同完成的.

其中, CPU负责UI布局元素的Measure, Layout, Draw等相关运算执行. GPU负责栅格化(rasterization), 将UI元素绘制到屏幕上.

如果我们的UI布局层次太深, 或是自定义控件的onDraw中有复杂运算, CPU的相关运算就可能大于16ms, 导致卡顿.

这个时候, 我们需要借助Hierarchy Viewer这个工具来帮我们分析布局了. Hierarchy Viewer不仅可以以图形化树状结构的形式展示出UI层级, 还对每个节点给出了三个小圆点, 以指示该元素Measure, Layout, Draw的耗时及性能.

具体请参考App优化之Layout怎么摆.

2.2 过度绘制(Overdraw)

上节说的CPU方面的, 关于GPU的绘制, 如果我们的界面存在Overdraw, 也可能导致卡顿.

Overdraw: 用来描述一个像素在屏幕上多少次被重绘在一帧上.
通俗的说: 理想情况下, 每屏每帧上, 每个像素点应该只被绘制一次, 如果有多次绘制, 就是Overdraw, 过度绘制了.

2.2.1 调试Overdraw

Android系统提供了可视化的方案来让我们很方便的查看overdraw的现象:
在"系统设置"-->"开发者选项"-->"调试GPU过度绘制"中开启调试:

toggle GPU overdraw

此时界面可能会有五种颜色标识:

overdraw indicator
  • 原色: 没有overdraw
  • 蓝色: 1次overdraw
  • 绿色: 2次overdraw
  • 粉色: 3次overdraw
  • 红色: 4次及4次以上的overdraw

一般来说, 蓝色是可接受的, 是性能优的.

2.2.2 Overdraw的分析处理

上面有言, 所谓Overdraw, 就是在一个像素点上绘制了多次. 常见的就是:

  1. 绘制了多重背景.
  2. 绘制了不可见的UI元素.

还是以GithubApp这个App的代码为例调试, 打开应用, 展示是这样的:

example-1

可以看到是中间列表这块overdraw比较严重. 查看代码发现:

fragment_trending_container.xml中ViewPager设置了背景:

<android.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又设置了背景:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="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"> ...
</android.support.v4.widget.SwipeRefreshLayout

完整代码请查看Github上源码, 本文分析时commit截止到b01b5793.

删除外层ViewPager的背景再看:

example-2

可以发现中间列表区域已经不再是红色了, 但是也没有达到蓝色这个可以接受的层级. 这是因为我们的Activity默认情况下, theme会给window设置一个纯色的背景. 因为我们这里不想使用这个默认的背景,故而给layout加了一层背景, 导致了多重绘制背景.

当然我们也可以自定义主题, 将theme的window background设置成我们想要的, 而不在布局中设置.

可以通过如下方式去掉window的背景.

设置主题:

<item name="android:windowBackground">@null</item>

或是代码设置, 在onCreate中:

getWindow().setBackgroundDrawable(null);

此时我们看到的效果:

example-3

已基本达到优化水平.

以上旨在提供分析方法和思路.
Overdraw主要原因是背景的多重绘制, 或是不可见的View在背后绘制等, 但不仅限于此.

2.3 UI线程的复杂运算

上文ANR相关分析中就说到UI线程的复杂运算会造成UI无响应, 当然更多的是造成UI响应停滞, 卡顿.

产生ANR已经是卡顿的极致了, 具体分析可以参看App优化之ANR详解一文.

关于运算阻塞导致的卡顿的分析, 可以使用Traceview这个工具.
具体Traceview的介绍, 以及实战分析, 可以参考App优化之提升你的App启动速度之理论基础App优化之提升你的App启动速度之实例挑战.

在这里需要提下我们在性能分析工具中提到的StrictMode.

2.3.1 StrictMode的使用

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

如果你的线程出了问题, 控制台会有警告输出, 可以定位到代码.
相对简单, 在此就不多废话了.
解决UI线程的耗时操作方案, 可以参考ANR详解里面说到的那些线程模式.

2.4 频繁的GC

上面说的都是处理上的, CPU, GPU相关的. 实际上内存原因也可能会造成应用不流畅, 卡顿的.

说到这, 想起当年配台式机的三大件(CPU, 内存, 显示器)了. 貌似分析App性能也是这几大件啊 :)

为什么说频繁的GC会导致卡顿呢?
简而言之, 就是执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿.

以下内容参考自Android Performance Patterns:Memory Churn and Performance. 需翻墙

导致频繁GC有两个原因:

  • 内存抖动(Memory Churn), 即大量的对象被创建又在短时间内马上被释放.
  • 瞬间产生大量的对象会严重占用Young Generation的内存区域, 当达到阀值, 剩余空间不够的时候, 也会触发GC. 即使每次分配的对象需要占用很少的内存,但是他们叠加在一起会增加Heap的压力, 从而触发更多的GC.

这些GC操作可能会造成上面说到的丢帧, 如下:

gc dropped frame

就会让用户感知到卡顿了.

一般来说瞬间大量产生对象一般是因为我们在代码的循环中new对象, 或是在onDraw中创建对象等. 所以说这些地方是我们尤其需要注意的...

Android 卡顿优化 1 卡顿解析的更多相关文章

  1. android问题及其解决-优化listView卡顿和怎样禁用ListView的fling

    问题解决-优化listView卡顿和怎样禁用ListView的fling 前戏非常长,转载请保留出处:http://blog.csdn.net/u012123160/article/details/4 ...

  2. Android app 性能优化的思考--性能卡顿不好的原因在哪?

    说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才 ...

  3. android中app卡顿优化问题

     所谓app卡顿原因就是在运行时出现了丢帧,还可能是UI线程被阻塞.首先来一下丢帧现象,android每16ms会对界面进行一次渲染,如果app的绘制.计算等超过了16ms那么只能等下一个16ms才能 ...

  4. Android性能优化----卡顿优化

    前言 无论是启动,内存,布局等等这些优化,最终的目的就是为了应用不卡顿.应用的体验性好坏,最直观的表现就是应用的流畅程度,用户不知道什么启动优化,内存不足,等等,应用卡顿,那么这个应用就不行,被卸载的 ...

  5. Android 布局渲染流程与卡顿优化

    文章内容概要 一.手机界面UI渲染显示流程 二.16ms原则 三.造成卡顿的原因 四.过度绘制介绍.检测工具.如何避免造成过度绘制造成的卡顿 一.手机界面UI渲染显示流程 大家都知道CPU(中央处理器 ...

  6. Android 卡顿优化 4 布局优化实际技巧

    今天分享一些layout布局书写中的一些技巧,希望看过之后你也一样可以写出性价比高的布局.我个人的目标是用最少的View写出一样效果的布局.因为我相信View的数量减少伴随着的就是层级的减少.从而达到 ...

  7. iOS应用千万级架构:性能优化与卡顿监控

    CPU和GPU 在屏幕成像的过程中,CPU和GPU起着至关重要的作用 CPU(Central Processing Unit,中央处理器) 对象的创建和销毁.对象属性的调整.布局计算.文本的计算和排版 ...

  8. Android内存优化(二)解析Memory Monitor、Allocation Tracker和Heap Dump

    前言 要想做好内存优化工作,就要掌握两大部分的知识,一部分是知道并理解内存优化相关的原理,另一部分就是善于运用内存分析的工具.本篇就来介绍内存分析工具:Memory Monitor.Allocatio ...

  9. Android内存优化(四)解析Memory Monitor、Allocation Tracker和Heap Dump

    相关文章 Android性能优化系列 Java虚拟机系列 前言 要想做好内存优化工作,就要掌握两大部分的知识,一部分是知道并理解内存优化相关的原理,另一部分就是善于运用内存分析的工具.本篇就来介绍内存 ...

随机推荐

  1. 实现点击页面其他地方,隐藏div(vue)

    方法一: 通过监听事件 document.addEventListener('click',function(e){ if(e.target.className!='usermessage'){ th ...

  2. 虚拟机安装苹果系统 VMware 12安装Mac OS X 10.10

    工具/原料 VMware Workstation Pro 12 (这个可以自己下载,并激活,你懂得) Unlocker 207 (链接:http://pan.baidu.com/s/1i43obDb ...

  3. Django REST Framework JWT提供的登录签发的视图

    Django REST Framework JWT提供了一个视图.在我们登录的时候,会校验用户名.密码是否正确.如果信息无误,可以返回一个JWT token.就可以简单地实现了记录用户登录状态. 用法 ...

  4. Centos7 环境准备

    Centos7 环境准备 #关闭防火墙 systemctl stop firewalld systemctl disable firewalld #关闭selinux sed -i 's/SELINU ...

  5. python运行原理/python解释器

    先Mark一下这个主题,内容待添加... 参考文章: [1]http://www.cnblogs.com/restran/p/4903056.html [2]https://blog.hakril.n ...

  6. SEO页面标题Title的优化

    我在一个月前改过页面标题(Title),随后表现是:百度网页快照4天不更新,Google正常.而我仅仅是改了两个词组而已.在建博初期,修改Title的最频繁的时期,下面卢松松就我经历的修改Title过 ...

  7. 在SpringMVC中 /* 和 / 的区别

    <url-pattern> / </url-pattern>:会匹配到 /springmvc 这样的路径型url,而不会匹配到像 .jsp 这样的后缀型的url. <ur ...

  8. mysql source 乱码

    mysql -u root -p --default-character-set=utf8 use dbname source /root/newsdata.sql

  9. 洛谷——P1123 取数游戏

    P1123 取数游戏 题目描述 一个N×M的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻8个格子中的一个即认为这两个数字相邻),求取 ...

  10. Section One

    1.1.1 #include <iostream> using namespace std; int main() { int a,b,N; cin >> N; while ( ...