如果不小心,使用图片时可能会快速引入性能问题。即使是采用 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 调整图片在屏幕上的位置。或者,您也可以在包含图片的可组合项或视图中添加内边距。
选择合适的像素格式
选择合适的像素格式,在内存和质量之间取得平衡。如果不需要透明度,请使用 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) {
}
为你推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- ImageBitmap 与 ImageVector {:#bitmap-vs-vector}
- 在 Compose 中保存界面状态
- Jetpack Compose 的阶段