如果不小心,使用图片时可能会快速引入性能问题。即使是采用 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 属性。
尽可能使用矢量
对于由几何形状组成的图片,矢量图形比位图小得多,并且可以针对任何显示密度平滑缩放。在合适的情况下,请使用元素
like 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 的阶段