Android 卡顿优化 1 卡顿解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

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需FQ

导致频繁GC有两个原因:

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

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

gc dropped frame

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

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



    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8480153.html,如需转载请自行联系原作者

相关文章
|
6天前
|
SQL 关系型数据库 MySQL
深入解析MySQL的EXPLAIN:指标详解与索引优化
MySQL 中的 `EXPLAIN` 语句用于分析和优化 SQL 查询,帮助你了解查询优化器的执行计划。本文详细介绍了 `EXPLAIN` 输出的各项指标,如 `id`、`select_type`、`table`、`type`、`key` 等,并提供了如何利用这些指标优化索引结构和 SQL 语句的具体方法。通过实战案例,展示了如何通过创建合适索引和调整查询语句来提升查询性能。
55 9
|
20天前
|
机器学习/深度学习 人工智能 PyTorch
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
本文探讨了Transformer模型中变长输入序列的优化策略,旨在解决深度学习中常见的计算效率问题。文章首先介绍了批处理变长输入的技术挑战,特别是填充方法导致的资源浪费。随后,提出了多种优化技术,包括动态填充、PyTorch NestedTensors、FlashAttention2和XFormers的memory_efficient_attention。这些技术通过减少冗余计算、优化内存管理和改进计算模式,显著提升了模型的性能。实验结果显示,使用FlashAttention2和无填充策略的组合可以将步骤时间减少至323毫秒,相比未优化版本提升了约2.5倍。
35 3
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
|
21天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
17天前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
43 8
|
23天前
|
缓存 NoSQL Java
千万级电商线上无阻塞双buffer缓冲优化ID生成机制深度解析
【11月更文挑战第30天】在千万级电商系统中,ID生成机制是核心基础设施之一。一个高效、可靠的ID生成系统对于保障系统的稳定性和性能至关重要。本文将深入探讨一种在千万级电商线上广泛应用的ID生成机制——无阻塞双buffer缓冲优化方案。本文从概述、功能点、背景、业务点、底层原理等多个维度进行解析,并通过Java语言实现多个示例,指出各自实践的优缺点。希望给需要的同学提供一些参考。
42 7
|
21天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
20天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
24天前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
29 2
|
24天前
|
前端开发 Android开发 UED
移动应用与系统:从开发到优化的全面解析####
本文深入探讨了移动应用开发的全过程,从最初的构思到最终的发布,并详细阐述了移动操作系统对应用性能和用户体验的影响。通过分析当前主流移动操作系统的特性及差异,本文旨在为开发者提供一套全面的开发与优化指南,确保应用在不同平台上均能实现最佳表现。 ####
24 0
|
28天前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。

推荐镜像

更多