【Android性能优化】CPU

本文介绍了Android应用性能优化的方法论和实例
CPU优化
在 Android 应用中,CPU 是执行所有计算任务的核心硬件。无论是用户交互、数据处理,还是系统后台任务,几乎所有的操作都需要依赖 CPU 来完成。
UI 操作
UI 操作是用户直接感知的部分,虽然 Android 的 UI 渲染主要由 GPU 协助完成,但 CPU 仍然承担了大量的计算任务。
布局测量与布局(Measure/Layout)
- XML 布局解析:当加载一个 XML 布局文件时,系统需要解析 XML 并将其转换为视图树,这一过程由 CPU 完成。
- 视图测量与布局(Measure/Layout):在视图树构建完成后,系统需要计算每个视图的大小和位置,这一过程也由 CPU 执行。
- 视图绘制(Draw):虽然最终的像素填充由 GPU 完成,但绘制指令的生成(如 onDraw() 方法中的 Canvas 操作)是由 CPU 处理的。
CPU 占用高的原因可能有布局过于复杂(嵌套过深的视图树)。频繁调用 requestLayout() 或 invalidate(),导致视图反复测量和绘制。
动画效果
属性动画、补间动画和帧动画,都需要 CPU 参与计算每一帧的状态。如果动画复杂度较高(如大量视图的联动动画),CPU占用会显著增加。另外,在主线程中执行动画计算,也会导致主线程负担过重。
数据处理与计算
任何涉及数据处理的操作都需要 CPU 参与计算,尤其是在处理大量数据或复杂算法时,CPU 占用会显著增加。
- 数学运算:如加密解密、图像处理、视频编解码、物理模拟等。
- 排序与搜索:对大量数据进行排序(如 Collections.sort())或搜索(如二分查找、哈希表查询)。
- 数据转换:如图片缩放、颜色格式转换、音频采样率转换等。
数据解析
- JSON/XML 解析:从网络或本地文件中读取 JSON 或 XML 数据并解析为对象,这一过程需要 CPU 进行字符串处理和数据结构转换。
- Protobuf/FlatBuffer 解析:虽然这些格式的解析效率较高,但在数据量较大时仍然会占用 CPU。
- 网络数据的处理:在网络请求中,CPU 通常用于处理数据的序列化和反序列化。
多线程与并发操作
Android 应用中,开发者通常会使用多线程来执行耗时任务(如网络请求、文件读写、数据处理等),以避免阻塞主线程。然而,线程的创建、调度和同步也会占用 CPU 资源。
每个线程的创建和销毁都会消耗一定的 CPU 资源。如果创建了过多的线程(如没有使用线程池),线程调度会成为 CPU 的负担。
使用锁(如 synchronized、ReentrantLock)或其他同步机制(如 CountDownLatch、Semaphore)时,线程可能会因为等待锁而被挂起或唤醒,这一过程会占用 CPU。
后台任务与系统服务
Android 应用可能会在后台执行一些任务(如数据同步、日志上传、定时任务等),这些任务通常由系统服务或应用自带的线程池管理,但仍然需要 CPU 参与。
- 例如使用 AlarmManager、Handler 或 WorkManager 执行定时任务时,任务的执行逻辑会占用 CPU。
- 当应用接收到广播(如系统广播或自定义广播)时,注册的广播接收器会执行相应的回调逻辑,这一过程CPU可能会执行计算任务。
- 如果应用注册了传感器监听(如加速度传感器、陀螺仪),传感器的回调数据需要由 CPU 处理。传感器数据采样频率过高,会导致CPU处理负担过重。
图片与多媒体处理
图片和多媒体处理是 CPU 占用较高的场景之一,尤其是在处理高分辨率图片或高清视频时。
- 使用 BitmapFactory 解码图片时,CPU 需要将原始字节数据转换为位图对象。如果图片分辨率过高(如几 MB 的图片),解码过程会非常耗时。
- 对图片进行缩放、裁剪、旋转等操作时,CPU 需要进行大量的像素计算。
- 播放或录制音频/视频时,编解码过程通常由 CPU 完成(除非使用了硬件加速)。
优化 CPU 占用
针对上述场景,我们可以采取以下优化策略:
- 减少布局嵌套,使用 ConstraintLayout 等高效布局。避免频繁调用 requestLayout() 或 invalidate()。使用硬件加速(如开启
setLayerType(View.LAYER_TYPE_HARDWARE, null))来分担 CPU 的绘制压力。还有避免频繁计算复杂的自定义动画。 - 使用高效的算法和数据结构(如 HashMap 替代嵌套循环)。对大数据集进行分页加载,避免一次性处理过多数据。将数据处理任务移到子线程中执行,避免阻塞主线程。
- 避免频繁创建和销毁线程,合理使用线程池来管理线程,可以避免创建过多线程。减少锁的使用,避免线程竞争。使用无锁数据结构(如 ConcurrentHashMap)或异步编程模型(如 RxJava、Kotlin 协程)。
- 合并定时任务,减少任务执行的频率。使用 WorkManager 管理后台任务,避免重复执行。在广播接收器中只执行轻量级逻辑,耗时操作移到服务或线程中执行。
- 压缩图片分辨率,避免加载过大的图片。使用图片加载库(如 Glide、Picasso),它们会自动处理图片的解码和缓存。使用硬件加速的编解码器(如 MediaCodec)处理音视频。
GPU优化
在 Android 应用中,GPU(图形处理单元) 主要负责图形渲染相关的任务,即将 CPU 提交的绘制指令转化为屏幕上的像素。
虽然 GPU 的主要职责是图形渲染,但在现代 Android 应用中,GPU 的使用场景已经不仅限于传统的图形绘制,还涉及到一些与图形相关的计算任务(如图像处理、视频渲染等)。
以下是 Android 应用中常见的会使用到 GPU 的操作。
UI 渲染相关操作
UI 渲染是 GPU 最常见的使用场景,因为 Android 的界面是由大量的视图(View)组成的,而这些视图的绘制和显示需要 GPU 的参与。
Android 的视图系统通过 CPU 生成绘制指令(如 Canvas.drawXXX() 方法),然后将这些指令提交给 GPU 进行实际的像素填充。不管是绘制基本图形(如矩形、圆形、路径等),还是绘制文本,位图等。都是由 GPU 将 CPU 提交的绘制指令转化为屏幕上的像素。如果视图树过于复杂或绘制逻辑过于频繁,GPU 的负载会增加。
动画效果
动画的本质是每一帧的视图状态变化,而每一帧的状态变化需要通过 GPU 进行渲染。例如属性动画、补间动画、帧动画和自定义动画(如通过 ValueAnimator 实现的动画)。GPU 需要计算每一帧的像素变化并渲染到屏幕上。如果动画复杂度较高(如多个视图的联动动画)或帧率过高GPU 的负载会显著增加。
过度绘制(Overdraw)
过度绘制是指屏幕上的某些像素被多次绘制(如背景色、View 的背景、子 View 的背景等叠加绘制)。例如多层嵌套的背景色(如父布局和子布局都设置了背景色)。不可见的视图仍然被绘制(如 View.setVisibility(View.GONE) 的视图仍然被调用 draw() 方法)。过度绘制会增加 GPU 的负担,导致渲染性能下降。
图片与图像处理相关操作
图片加载、解码和显示是 Android 应用中常见的操作,这些操作通常需要 GPU 参与渲染,尤其是在处理高分辨率图片或复杂图像效果时。例如从网络或本地加载图片后,图片需要被解码为位图(Bitmap),然后通过 GPU 渲染到屏幕上。GPIU会将解码后的位图渲染到屏幕上。
如果图片分辨率过高(如几 MB 的图片),解码和渲染的负担会显著增加。
还有对图片进行缩放、裁剪、旋转等操作时,可能需要 GPU 参与像素计算。例如使用 Matrix 对图片进行变换(如缩放、旋转)。使用 Bitmap.createScaledBitmap() 对图片进行缩放。如果图片处理逻辑过于复杂,GPU 的负载会增加。
图像滤镜与特效
应用中可能还会使用一些图像滤镜或特效(如模糊、锐化、色彩调整等),这些操作通常需要对每个像素进行计算。
- 使用 OpenGL ES 或 Vulkan 实现自定义滤镜。
- 使用第三方库(如 GPUImage)实现图像特效。
图像滤镜和特效通常是计算密集型任务,会显著增加 GPU 的负载。
视频与多媒体相关操作
视频播放、录制和处理是 GPU 的重要使用场景,因为视频本质上是由大量的帧组成的,每一帧的解码、渲染和处理都需要 GPU 的参与。视频播放需要对每一帧进行解码和渲染,而 GPU 可以加速帧的渲染过程。 当我们使用使用 MediaPlayer 或 ExoPlayer 播放视频。使用 SurfaceView 或 TextureView 显示视频画面。GPU 会参与进来加速视频帧的渲染。
如果视频分辨率过高(如 4K 视频),或者播放过程中存在跳帧、卡顿,可能是 GPU 的负载过高。
视频录制需要对摄像头采集的每一帧进行处理和编码,而 GPU 可以加速帧的处理过程。例如使用 Camera2 API 或 CameraX 录制视频。或者使用 OpenGL ES 或 Vulkan 对视频帧进行实时处理(如滤镜、特效)。 GPU 会加速视频帧的处理和渲染。
游戏与高性能图形应用
游戏和高性能图形应用是 GPU 的主要使用场景,因为这些应用通常需要实时渲染大量的图形和动画。例如 2D 游戏需要实时渲染大量的精灵(Sprite)、背景、文字等图形元素。3D 游戏需要实时渲染复杂的三维模型、光影效果、粒子系统等。当我们使用 OpenGL ES 或 Vulkan 进行 3D 渲染。使用游戏引擎(如 Unity、Unreal Engine)进行 3D 游戏开发时,GPU 会加速三维模型的渲染、光影计算和粒子效果。
机器学习与图像处理
一些机器学习模型(如卷积神经网络)和图像处理算法(如目标检测、图像分割)可以利用 GPU 的并行计算能力加速。
- 使用 TensorFlow Lite 或 ML Kit 进行图像分类、目标检测等任务。
- 使用 GPU 加速的图像处理库(如 OpenCV + GPU 模块)。
GPU 主要用来加速矩阵运算和像素计算。机器学习和图像处理的计算量通常较大,对 GPU 的性能要求较高。
掉帧优化
Android 系统每隔 16.67ms 发出VSYNC信号,触发对UI进行渲染。如果某一帧的渲染时间超过 16.67ms,就会导致掉帧。例如,某一帧渲染耗时 33ms,就会导致掉 1 帧(因为 33ms > 16.67ms × 2)。
掉帧的表现形式包括:
- 界面卡顿、不流畅。
- 动画效果出现“跳帧”。
- 滑动列表时出现“拖影”或“延迟”。
掉帧可能是由于多种原因引起,CPU、GPU、内存等资源都可能成为瓶颈。
GPU 负责将 CPU 提交的绘制指令转化为屏幕上的像素。如果 GPU 的 负载过高 ,或者 渲染任务过于复杂 ,会导致帧渲染时间超过 16.67ms,从而引发掉帧。
内存问题可能间接导致掉帧,尤其是在内存不足时,系统会频繁进行内存回收,甚至触发 onTrimMemory() 回调,影响应用的性能。例如出现内存泄漏会导致应用的内存占用不断增加,最终触发频繁的垃圾回收(GC),从而影响主线程的执行。表现为界面卡顿,尤其是在长时间运行后。如果设备的内存不足,系统可能会频繁进行内存回收,甚至杀死后台进程以释放内存。这会导致应用的性能下降。应用启动变慢,滑动列表时出现卡顿。
系统层面
如果设备上有大量的后台任务(如其他应用的后台服务、系统更新等),会占用 CPU 和内存资源,影响当前应用的性能。尤其是在设备整体负载较高时,当前应用运行也会跟随变慢。
CPU 资源竞争也是一个因素,如果应用创建了过多的线程,或者线程调度不合理,会导致 CPU 资源竞争,影响主线程的执行。界面卡顿,尤其是在多线程任务较多的场景中。
出现掉帧问题的经典log:
"Skipped xx frames! The application may be doing too much work on its main thread"