Android界面卡顿

本文探讨了Android应用中常见的卡顿问题,分析了包括复杂的UI布局、过度绘制、主线程中执行耗时操作以及频繁的垃圾回收等可能导致卡顿的原因,并提供了解决这些问题的具体策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总要说两句

前段时间在开发项目时搞了一下过渡绘制的问题,一个比较复杂的界面刚开发出来,过渡绘制95%红,调了半天后,基本只有5%的红,效果不错,准备找个时间总结一下,今天刚好看到网上以为大神发了一篇文章,总结的不错,顺手牵过来啦,哈哈哈~

1、感知卡顿

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

1.1 16ms原则

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

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

这里写图片描述

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

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

2.2 过度绘制(Overdraw)

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

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

2.2.1 调试Overdraw

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

这里写图片描述

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

这里写图片描述

  • 原色: 没有overdraw
  • 蓝色: 1次overdraw
  • 绿色: 2次overdraw
  • 粉色: 3次overdraw
  • 红色: 4次及4次以上的overdraw
    (一般来说, 蓝色是可接受的, 是性能优的)

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

绘制了多重背景.
绘制了不可见的UI元素.
打开应用, 展示是这样的:

这里写图片描述

可以看到是中间列表这块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

删除外层ViewPager的背景再看:

这里写图片描述

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

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

设置主题:

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

或是代码设置, 在onCreate中:

getWindow().setBackgroundDrawable(null);

此时我们看到的效果:

这里写图片描述

已基本达到优化水平.

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

2.4 频繁的GC

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

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

导致频繁GC有两个原因:

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

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

这里写图片描述

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

### Android 界面卡顿分析工具与性能优化 在 Android 开发中,界面卡顿是一个常见的性能问题。为了有效分析和优化卡顿,开发者可以使用多种工具和技术来检测和解决问题。 #### 1. 卡顿检测工具 以下是一些常用的卡顿检测工具: - **BlockCanary**:这是一个专门用于检测主线程阻塞的开源工具。它可以捕获主线程耗时操作,并生成详细的日志信息,帮助开发者快速定位问题[^1]。 - **StrictMode**:严格模式(StrictMode)可以帮助开发者检测潜在的性能问题,例如磁盘访问、网络请求等在主线程中的不当操作。通过配置 StrictMode,开发者可以捕获这些违规行为并进行优化[^4]。 - **TraceView**:TraceView 是一个强大的性能分析工具,能够详细记录方法调用的时间消耗。通过插入 `Debug.startMethodTracing("myTrace");` 和 `Debug.stopMethodTracing();`,开发者可以生成性能追踪文件并进行分析。 - **Systrace**:Systrace 是一个系统级的性能分析工具,可以用来分析应用的整体性能表现,包括 UI 渲染、线程调度等。它能够提供高精度的时间线视图,帮助开发者发现卡顿的根本原因[^2]。 - **Android Studio Profiler**:这是 Android Studio 内置的性能分析工具,支持实时监控 CPU、内存、网络和电池使用情况。通过 Profiler,开发者可以直观地查看主线程的工作负载,并识别出可能导致卡顿的操作[^3]。 #### 2. GPU 性能分析 除了 CPU 的性能分析,GPU 的表现也对界面流畅性至关重要。以下是一些 GPU 相关的调试工具: - **GPU Profile 工具**:该工具可以帮助开发者了解 GPU 的渲染性能,包括帧率、绘制时间等指标。如果 GPU 渲染时间过长,可能需要优化图形处理逻辑[^2]。 - **过度绘制检查**:Android 提供了过度绘制检查功能,可以通过开发者选项开启。该功能会以颜色标记屏幕上的过度绘制区域,帮助开发者减少不必要的重叠绘制操作[^2]。 #### 3. 原生代码性能分析 对于涉及原生代码的应用,可以使用以下工具进行性能分析: - **Simpleperf**:这是一个轻量级的性能分析工具,适用于分析原生代码的性能瓶颈。它利用 CPU 的性能监控单元(PMU)提供的硬件事件数据,能够精确地定位原生函数的执行时间。 #### 4. 卡顿原理与优化策略 卡顿的核心原因是主线程工作负载过重,导致无法按时完成每帧的渲染任务。根据 Android 的渲染机制,CPU 和 GPU 需要在每 16.66ms 内完成一帧的处理。如果超过这个时间,就会发生跳帧,多次跳帧会导致视觉上的卡顿现象[^5]。 为了优化卡顿问题,开发者可以采取以下策略: - 将耗时操作移出主线程,例如网络请求、数据库操作等。 - 减少布局层次深度,避免复杂的嵌套布局。 - 使用高效的动画实现方式,例如硬件加速的属性动画。 - 定期使用性能分析工具进行监控和优化。 ```python # 示例代码:使用 BlockCanary 检测主线程卡顿 import blockcanary.BlockCanary; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); BlockCanary.install(this, new AppBlockCanaryContext()).start(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值