ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Android应用性能优化实战:GPU呈现模式与过度绘制深度解析

Android应用性能优化实战:GPU呈现模式与过度绘制深度解析 1. 为什么你的Android应用会“卡”从GPU呈现模式与过度绘制说起如果你是一名Android开发者或者对应用性能优化感兴趣那你一定遇到过这样的场景自己开发的应用功能逻辑都没问题但滑动列表时总觉得不够“跟手”页面切换时偶尔会“掉帧”甚至在一些中低端设备上体验会变得非常糟糕。用户反馈里可能不会直接说“GPU渲染有问题”他们只会说“这App有点卡”。这种“卡顿”的体验很大程度上与两个核心的性能指标息息相关GPU呈现模式和过度绘制。简单来说你的手机屏幕就像一块画布应用里的每一个界面Activity、每一个按钮、每一段文字都需要系统更具体是GPU一笔一笔地画上去这个过程就叫“渲染”。GPU呈现模式就是观察GPU画每一帧画面花了多少时间的“仪表盘”而过度绘制则是检查有没有在同一个像素点上用不同颜色反复涂抹了太多层做了大量无用功的“显微镜”。一个流畅的应用必然是GPU画得又快呈现时间短又画得聪明过度绘制少。很多开发者尤其是刚入行的朋友可能会一头扎进代码逻辑的优化却忽略了视觉渲染这个“隐形杀手”。结果就是代码跑得飞快但界面依然卡顿。今天我们就来彻底搞懂这两个概念并手把手带你使用Android Studio自带的工具进行实战分析和优化。这不是一篇枯燥的理论文档而是我多年踩坑后总结出的、能直接上手的排查与修复指南。2. GPU呈现模式分析读懂渲染性能的“心电图”当你感觉应用卡顿时第一反应应该是“渲染一帧画面到底花了多长时间” Android系统提供了一个非常强大的可视化工具来回答这个问题它就是GPU呈现模式分析Profile GPU Rendering 在较新的系统中也叫“GPU渲染模式分析”或“在屏幕上显示为条形图”。2.1 如何开启并理解这个“仪表盘”开启这个功能不需要写一行代码它是系统级的调试工具。开启步骤进入手机的“开发者选项”。如果你的手机没有这个选项需要到“关于手机”里连续点击“版本号”7次来激活它。在开发者选项中找到“GPU渲染模式分析”或“Profile GPU Rendering”。将其设置为“在屏幕上显示为条形图”。开启后你的屏幕边缘通常是顶部会出现一组不断滚动的彩色竖条。每一根竖条就代表屏幕绘制的一帧画面。条形图颜色详解关键每一根竖条都被分成了不同颜色的段它们分别代表了渲染一帧所经历的不同阶段及其耗时蓝色 (Draw/List) 表示测量和绘制视图列表所需要的时间。通俗讲就是Android系统遍历你的视图树View Hierarchy决定每个View的大小和位置并调用它们的onDraw方法生成绘制指令列表所花的时间。如果这个部分很高通常意味着你的视图结构太复杂或者onDraw方法里做了耗时操作比如创建了新的Paint或Bitmap。紫色 (Prepare) 表示准备阶段的时间。在Android 5.0 (Lollipop) 之后这个阶段主要与将资源如Bitmaps上传到GPU有关。如果这部分很高可能意味着你在主线程频繁解码或处理大型图片。红色 (Process) 表示Android 2D渲染引擎执行显示列表所花的时间。这是GPU实际执行绘制命令的时间。视图越多、越复杂红色部分就越长。橙色 (Execute) 表示将一帧数据交换到屏幕显示所花的时间。这部分通常比较稳定但如果出现峰值可能与屏幕的垂直同步VSync或缓冲区交换有关。最重要的那条线——绿色横线在条形图区域你会看到一条绿色的横线。这条线代表16.67毫秒的阈值。为什么是16.67ms因为对于大多数60Hz刷新率的屏幕来说每秒需要刷新60帧每帧的预算时间就是 1000ms / 60 ≈ 16.67ms。如果你的每一帧的条形图高度都低于这条绿线那么你的应用理论上可以达到60fps的流畅体验。任何一帧的条形图高度超过了绿线就意味着这一帧“掉帧”了用户可能会感知到卡顿。注意在更高刷新率如90Hz, 120Hz的设备上每帧的预算时间会更短例如120Hz下约为8.33ms。但系统工具通常仍以60Hz的16.67ms为基准线你需要结合设备实际刷新率来理解。如果所有条形图都远低于绿线但依然感觉卡可能需要检查是否触发了设备的更高刷新率模式。2.2 实战诊断从条形图中发现性能瓶颈现在我们打开自己开发的应用滑动一个复杂的列表页面观察顶部的条形图。场景一蓝色部分异常高现象 条形图的底部蓝色段非常长几乎占满了整根柱子。可能原因视图层级过深 你的布局文件可能嵌套了太多层LinearLayout或RelativeLayout。系统需要递归遍历所有子View来计算位置和大小这非常耗时。自定义View的onDraw方法过于复杂 在onDraw中进行了内存分配如new Paint()、复杂的计算或频繁的Canvas.drawText等操作。排查思路使用Android Studio的Layout Inspector或“调试GPU过度绘制”工具下文会讲查看当前页面的视图树深度。检查自定义View确保onDraw方法中只做绘制操作所有初始化如Paint对象的创建、Path的构建都应在构造函数或onSizeChanged中完成。场景二红色部分异常高现象 条形图的红色段占据主导。可能原因过度绘制严重 GPU需要绘制大量重叠的、不透明的区域。这是下文“过度绘制”部分要解决的核心问题。大量复杂的图形效果 如圆角 (clipPath)、阴影 (elevation)、遮罩等这些都会增加GPU的片段着色器计算负担。排查思路 直接转到“过度绘制”分析这是降低红色耗时最有效的手段。场景三整根柱子频繁超过绿线现象 在快速滑动时连续多帧的条形图高度都超过了16.67ms的绿线。可能原因 这通常是综合问题。可能是复杂的布局高蓝色加上严重的过度绘制高红色共同导致的。也可能是因为在主线程执行了非UI工作如网络请求、数据库读写阻塞了UI线程导致测量、绘制流程被延迟。排查思路 结合Systrace或Android Studio Profiler的CPU记录器查看在掉帧的时刻主线程到底在执行什么方法是否有阻塞。通过GPU呈现模式分析我们能够快速定位渲染瓶颈发生在哪个阶段从而进行有针对性的优化。它是一个宏观的、定性的性能指示器。3. 过度绘制分析揪出那些“看不见”的性能浪费如果说GPU呈现模式告诉你“画得慢”那么过度绘制分析则告诉你“画得多余”。过度绘制Overdraw指的是在屏幕的同一个像素点上被绘制了多次。例如一个不透明的蓝色背景上又绘制了一个不透明的红色按钮。那么蓝色背景的绘制就是完全浪费的因为最终用户根本看不到它。GPU需要为每一个被覆盖的像素执行绘制命令即使它最终被覆盖。过多的过度绘制会直接增加GPU的负载对应GPU呈现模式中的红色部分导致帧率下降和功耗增加。3.1 开启“透视眼”可视化过度绘制Android同样提供了系统级的工具来可视化过度绘制。开启步骤同样在“开发者选项”中。找到“调试GPU过度绘制”。将其设置为“显示过度绘制区域”。开启后你的整个手机屏幕会覆盖上一层半透明的颜色。不同的颜色代表该区域被绘制的次数。颜色含义从好到坏原色无颜色 该像素只被绘制了1次。这是最理想的情况。蓝色 该像素被绘制了2次。可以接受在简单界面中很常见。绿色 该像素被绘制了3次。需要开始关注了。粉色 该像素被绘制了4次。问题比较严重应当优化。红色 该像素被绘制了5次及以上。性能“重灾区”必须优化。你的优化目标是让屏幕上尽可能多的区域显示为原色或蓝色尽量减少绿色区域并消灭粉色和红色区域。3.2 常见过度绘制场景与优化“手术”让我们看看哪些代码和布局习惯会导致过度绘制以及如何“动手术”修复。场景一默认窗口背景与布局背景重叠这是新手最容易犯也最容易修复的问题。问题代码!-- Theme中设置了全局窗口背景 -- style nameAppTheme parentTheme.AppCompat.Light.DarkActionBar item nameandroid:windowBackgroundcolor/white/item /style !-- Activity的根布局又设置了一次背景 -- LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:backgroundcolor/white android:orientationvertical !-- 子View -- /LinearLayout问题分析 系统先绘制了窗口的白色背景然后你的根布局LinearLayout又绘制了一次完全覆盖屏幕的白色背景。这导致了整个屏幕区域2x的过度绘制全屏蓝色。优化方案方案A推荐 移除根布局的背景。既然窗口已经有背景了就没必要再画一次。LinearLayout ... android:backgroundnull方案B 在主题中将窗口背景设为null只在需要的地方如根布局设置背景。style nameAppTheme.NoWindowBackground parentAppTheme item nameandroid:windowBackgroundnull/item /style在AndroidManifest.xml中为该Activity应用此主题。场景二不必要的布局嵌套与背景问题布局LinearLayout android:backgroundcolor/gray LinearLayout android:backgroundcolor/white TextView ... / /LinearLayout /LinearLayout问题分析 父布局的灰色背景会被子布局的白色背景完全覆盖。灰色背景的绘制是浪费的。优化方案如果子布局的背景是为了实现“留白”或“间距”效果考虑使用android:padding代替嵌套布局和背景。如果父布局的背景只是为了子布局周围的装饰考虑使用android:foreground或android:clipToPaddingfalse等更高效的方案。终极武器使用ConstraintLayout替代多层嵌套的LinearLayout和RelativeLayout可以极大地扁平化视图层级从根本上减少过度绘制的源头。场景三列表项RecyclerView Item的过度绘制列表是过度绘制的重灾区因为每个Item都可能存在上述问题并且会被多次复用和绘制。问题 Item布局复杂包含多层嵌套和重叠的背景。快速滑动时GPU压力巨大。优化方案简化Item布局 使用ConstraintLayout设计Item确保层级最浅。移除冗余背景 仔细检查Item布局中每一层的背景确保没有完全被覆盖的背景。使用android:clipToPadding和android:clipChildren 合理使用这两个属性可以防止子View绘制到边界之外有时能避免不必要的绘制。为ImageView设置合适的scaleType 错误的scaleType可能导致图片绘制到View边界之外造成过度绘制。场景四自定义View中的过度绘制在onDraw(Canvas)方法中如果你绘制了多个重叠且不透明的区域就会造成过度绘制。示例 先画一个填充的矩形作为背景再在同样位置画一个带边框的矩形。优化方案合并绘制操作 如果可能将多个绘制命令合并。例如使用一个Paint对象同时设置填充色和边框色通过drawRect的Paint.Style.FILL_AND_STROKE一次完成。使用Canvas.clipRect() 这是对付过度绘制的“神器”。如果你知道只需要在某个矩形区域内绘制可以先调用canvas.clipRect()设置裁剪区域。这样GPU会忽略这个区域之外的任何绘制命令即使你的代码调用了draw方法。避免在onDraw中分配对象 这虽然不直接减少过度绘制但能减少蓝色部分的耗时间接提升性能。通过系统地排查和修复过度绘制你能显著降低GPU的负载让应用滑动起来更加丝滑尤其是在低端设备上效果立竿见影。4. 高级工具联动Systrace与Layout Inspector深度剖析GPU呈现模式条形图和过度绘制可视化给了我们直观的“症状”诊断。但要找到确切的“病因”哪行代码、哪个布局导致了问题我们需要更强大的工具。4.1 Systrace性能分析的“核磁共振”Systrace是一个系统级的性能分析工具它能捕捉短时间内的系统活动并以时间线的形式展示出来。你可以看到每一帧期间CPU的每一个核心在做什么系统线程在做什么你的应用主线程、渲染线程在做什么。如何使用Systrace命令行/Android Studio准备 确保手机通过USB连接并已开启USB调试。捕获命令行python systrace.py gfx view res sched freq -a your.package.name -o trace.htmlAndroid Studio 点击Profiler标签 - 选择你的设备和应用 - 点击“”号 - 选择“System Trace”- 点击Record操作你的应用然后点击Stop。分析 打开生成的trace.html文件。重点关注Frames行。绿色圆圈代表流畅的帧16.67ms黄色或红色圆圈代表掉帧的帧。点击一个红色/黄色的帧下方会显示该帧的详细信息。在Systrace中诊断渲染问题查找主线程阻塞 观察你的应用进程的主线程通常叫主线程或包名。如果在一个VSync周期16.67ms内主线程上出现了大段的白色空白表示空闲或执行了非UI任务如Binder调用、JNI方法而在该周期末尾才密集执行Choreographer#doFrame测量、布局、绘制那么很可能是主线程被其他任务阻塞导致渲染开始得太晚。查找渲染线程RenderThread问题 渲染线程负责执行DisplayList的录制和GPU命令的提交。如果渲染线程的执行时间很长对应GPU呈现模式的红色部分并且内部是密集的DrawFrame或flush commands操作这通常指向复杂的视图或严重的过度绘制。检查VSync对齐 观察VSYNC-app和VSYNC-sf信号。如果应用的帧提交总是错过VSync信号就会导致掉帧。Systrace提供了微观的、定量的数据能将卡顿问题精确地定位到具体的系统方法或应用方法调用上。4.2 Layout Inspector视图层级的“X光片”当GPU呈现模式提示蓝色部分测量/绘制过高时你需要检查视图层级。Android Studio的Layout Inspector就是干这个的。使用步骤在Android Studio中运行你的应用到目标设备。点击菜单栏Tools - Layout Inspector。选择你的设备和应用进程。分析视图树查看层级深度 在组件树窗口中观察从根节点到最深叶子节点的层数。一个简单的页面层级最好控制在10层以内。复杂的页面也应尽可能扁平。识别冗余布局 寻找那些没有设置任何属性如background,margin,padding仅仅为了分组而存在的ViewGroup如LinearLayout。这些往往是优化的重点。结合过度绘制 你可以同时开启手机的“调试GPU过度绘制”和Layout Inspector的实时连接直观地看到屏幕上哪个UI组件对应着过度绘制的颜色从而精准定位问题View。优化策略使用merge标签 在自定义布局或include标签中如果根布局与被引入位置的父布局类型相同可以使用merge作为根标签它能避免增加一层额外的视图。使用ViewStub标签 对于那些初始化时不立刻显示但布局比较复杂的视图如错误提示页、加载页使用ViewStub可以延迟加载减少初始布局的测量和绘制时间。拥抱ConstraintLayout 这是Android官方推荐的、用于创建复杂扁平化布局的首选工具。它几乎可以替代所有LinearLayout和RelativeLayout的嵌套场景通过约束关系直接定位子View能极大减少视图层级。通过Systrace和Layout Inspector的联合分析你可以形成一个完整的性能优化闭环从宏观症状卡顿到微观定位哪一帧、哪个线程、哪个方法再到具体代码和布局的修复。5. 从理论到实践一个列表页面的完整优化案例让我们假设一个真实的场景一个电商App的商品列表页在快速滑动时明显卡顿。第1步症状观察开启GPU呈现模式分析快速滑动列表。观察到大量条形图超过绿线且红色部分占主导蓝色部分也较高。开启调试GPU过度绘制。列表区域大面积显示为绿色和粉色Item之间的分隔线附近甚至出现红色。第2步问题定位使用Layout Inspector检查一个列表项RecyclerView Item的布局。发现其根布局是一个LinearLayout内部又嵌套了两个RelativeLayout总层级达到8层。并且根LinearLayout、内层的RelativeLayout以及一个ImageView都设置了背景色。在Systrace中记录滑动操作。发现主线程在dispatchTouchEvent和onTouchEvent处理滑动事件后有较长的空白然后才密集执行performTraversals测量、布局、绘制。同时渲染线程的DrawFrame时间很长。第3步实施优化布局扁平化 将Item的布局用ConstraintLayout重写。将原来的8层嵌套减少到3层ConstraintLayout-ImageViewTextViewTextView。消除冗余背景移除根布局和内层布局的背景只保留真正需要背景的组件如一个表示折扣的标签View。将Item之间的分隔线从“一个具有背景色的View”改为RecyclerView.ItemDecoration来绘制这比增加一个View层级高效得多。图片优化确保ImageView的scaleType设置为centerCrop或fitXY避免matrix导致的绘制溢出。使用Glide或Coil等图片库并配置合适的尺寸避免加载过大的图片到内存中。自定义View优化如果Item内有检查自定义的评分星星View在其onDraw方法中使用canvas.clipRect限制绘制区域只绘制可见的部分。将Paint等对象在构造函数中初始化并缓存。第4步效果验证再次开启GPU呈现模式分析。滑动列表观察到条形图高度显著下降大部分帧都能保持在绿线以下。红色和蓝色部分都明显缩短。开启过度绘制调试。列表区域大部分变为原色或蓝色绿色区域大幅减少粉色和红色区域基本消失。主观体验滑动列表的跟手感和流畅度有了质的提升。这个案例表明性能优化是一个系统工程需要综合运用多种工具进行诊断并从布局、绘制、代码等多个层面进行协同优化。没有一劳永逸的银弹但掌握了这些工具和方法论你就能有条不紊地解决绝大多数UI性能问题。记住流畅的体验是留住用户的基石而这些工具就是你打造流畅体验的得力助手。
返回列表