优化位图图像

如果不小心,使用图片时可能会快速引入性能问题。即使是 JPG 或 PNG 等压缩格式的小图形,在解码后也会变成大位图。如果您未能高效地使用图形,可能会遇到内存问题,这可能会影响您的应用以及设备上其他应用的性能。 遵循以下最佳实践可以确保您的应用达到最佳性能。

使用图像加载库

您可以使用 Coil(适用于 Kotlin 优先项目)或 Glide(适用于 Java 项目)等图片加载库来提高应用的效率。这些库通过缓存图片、在需要时对图形进行降采样以及回收图形对象等操作,可减少应用的内存用量。

对图片进行降采样

请务必根据您的需求使用合适的图片尺寸。您应避免将大型高分辨率图片加载到小型容器(例如缩略图)中。而是应使用降采样来缩减图片大小,然后再将其解码到内存中。

客户端降采样

Coil 和 Glide 等图片加载库会自动为您处理降采样。您可以使用 ImageLoader(对于 Coil)或 DownsampleStrategy(对于 Glide)配置其降采样策略。如果您要手动管理位图,可以使用 inSampleSize 解码较小的版本。为了安全地执行此操作,您应首先将 inJustDecodeBounds 设置为 true,以便在不分配内存的情况下读取图片尺寸,计算样本大小,将 inSampleSize 设置为该值,将 inJustDecodeBounds 设置为 false,然后解码图片。

首选服务器端调整大小

尽可能直接从后端服务器请求所需的精确图片尺寸。这样可以减少网络用量和磁盘缓存占用空间,同时避免在设备上调整图片大小产生的内存开销,从而降低内存用量。

您可以将库配置为动态地将目标视图大小附加到图片网址。例如,Coil 允许使用自定义拦截器来实现此目的,而 Glide 则支持使用自定义模型加载器(例如 BaseGlideUrlLoader)来实现此目的。

避免使用不受约束的布局大小

为了让图片加载器有效地进行降采样(客户端或服务器端),它们必须在执行请求之前知道目标大小。

避免使用 wrapContentSize 或在加载远程图片的可组合项上不限制尺寸。如果这些库无法推断目标边界,则会回退到加载原始全尺寸图片。 这可能会导致加载比所需图片大得多的图片,从而增加内存用量和延迟时间。

而应在图片可组合项上设置明确的尺寸(例如,使用 Modifier.size)或定义宽高比。这样,布局引擎就可以预先计算出确切的像素目标,然后图片加载器可以使用该目标来请求和解码尺寸正确的资源。

针对不同屏幕尺寸提供备用资源

如果您要在应用中提供图片,请考虑为不同的设备分辨率提供不同大小的资源。这有助于缩减应用在设备端的下载大小,并提高性能,因为这会在分辨率较低的设备上加载分辨率较低的图片。如需详细了解如何针对不同设备尺寸提供备用位图,请查看备用位图文档。

请勿直接应用内边距

有时,您可能需要为图片添加内边距。例如,您可能希望图片周围有透明边框,以便进行黑边处理。在这种情况下,请勿直接向图片添加内边距,从而更改图片的尺寸。相反,请保持图片尺寸不变,并使用 InsetDrawable 调整图片在屏幕上的位置。或者,您也可以在包含图片的 Composable 或 View 中添加内边距。

选择合适的像素格式

选择合适的像素格式,在内存和画质之间取得平衡。当您不需要透明度时,请使用 RGB_565;此格式使用的内存是默认 ARGB_8888 格式的一半。

在 Glide 中,您可以使用 DecodeFormat 进行配置。在 Coil 中,您可以使用 bitmapConfig 属性。

尽可能使用矢量

对于由几何形状组成的图片,矢量图形比位图小得多,并且可以针对任何显示密度平滑缩放。在适当情况下,使用 ShapeDrawable 等元素来表示图形。

尽可能释放和重复使用位图

大型图形文件可能会占用大量内存。为了减少其影响,您应尽可能释放或重复使用图形对象。

如果您使用图片加载库,请务必在不再需要位图时将其释放到库的托管池中。该库可以在需要时重复使用这些对象,并保留一个内存缓冲区以供将来使用。

如果您要手动管理图形,则应在用完位图后通过调用 Bitmap.recycle 释放位图,并立即舍弃 Bitmap 引用,而不是依赖于垃圾回收。

其他提示和技巧

本部分列出了在处理图形时提高应用性能的其他几种方法。

请勿将大型图片与 AAB/APK 文件打包在一起

导致应用下载大小较大的一个主要原因就是在 AAB 或 APK 文件中打包了图形。使用 APK 分析器工具确保打包的文件大小未超过所需的图片文件大小。请缩减图片大小或考虑将图片放置在服务器上,并仅在需要时下载这些图片。

查找冗余位图

如果您有同一张图片的多个副本,就会浪费内存。您可以使用 Android Studio 性能分析器来识别冗余图形。使用堆转储分析器捕获堆转储,并通过选择重复的位图设置来过滤结果。

使用 ImageBitmap 时,在绘制前调用 prepareToDraw

使用 ImageBitmap 时,如需开始将纹理上传至 GPU,请先调用 ImageBitmap#prepareToDraw(),然后再实际开始绘制。这有助于 GPU 准备纹理并提升在屏幕上显示视觉效果的性能。大多数图片加载库都已经执行此项优化,但如果您要自行使用 ImageBitmap 类,请谨记这一要点。

最好将 Int DrawableRes 或网址作为参数(而不是 Painter)传递给可组合项

由于处理图片的过程非常复杂(例如,为 Bitmaps 编写 equals 函数的计算开销非常大),因此 Painter API 未明确使用 @Stable 注解标记为稳定。不稳定的类可能会导致不必要的重组,这是因为编译器无法轻松推断数据是否发生了更改。

因此,我们建议将网址或可绘制资源 ID 作为参数传递给可组合函数,而不是传递 Painter 作为参数。

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}