图片是几乎每个应用程序都会使用的核心功能。然而,由于一些简单的错误,有几种常见的方法会影响应用性能。下面我们来看一些常见的错误。
1 大图资源这会占用大量的内存和处理资源。解码后的位图大小直接与解码分辨率相关,这将显著影响应用程序的运行效率。
简化捆绑图像文件大多数智能手机的屏幕宽度不超过1200像素,因此相应地调整资源文件大小是有意义的。我们以一张7500x5000像素的照片为例。存储这样大小的位图需要112MB的内存,这超出了Flutter中的默认图片缓存大小。一个简单的经验法则是,在一次应用会话中,位图的总大小不应超过100MB。否则,图片将被重新解码,这会使用户体验变得不那么顺畅。
将图像分辨率调整为1200x800后,位图大小降至2.8兆字节。可以使用this tool来进行计算。
然而,我们只有在掌控这些文件时才能做到这一点,但如果图片是从远程来源来的,我们又该怎么办?或者同一张图片可以在不同布局中以不同尺寸使用,这种情况下我们又该如何处理?
设置缓存宽度和高度通过提供这些参数,我们可以指定图像的解码尺寸。别忘了在计算中加入 MediaQuery.of(context).devicePixelRatio
。另外,具有不同 cacheHeight
和 cacheWidth
的同一图像资源会被视为缓存中的不同图像。
图片(
"assets/6392956.jpg", // 文件路径
高度: 100,
宽度: 300,
缓存高度: (100 * MediaQuery.of(context).devicePixelRatio).toInt(), // 缓存高度计算
);
为了测试这会对缓存产生什么影响,我们来做一些测量。当前的缓存大小可以通过 PaintingBinding
类访问 PaintingBinding
类。
PaintingBinding 实例的 imageCache 当前占用的字节数
每次测试应用时都进行了彻底重启,以清除缓存里的无关数据
如预期的,较大的图片会增加缓存大小。然而,原始图片非常大,以至于完全未被缓存,这就意味着每次显示时都需要重新解码图片。让我们看看效果。
在这个视频中,每当打开包含图片的屏幕时,超出缓存大小限制的图片都会延迟显示。而对于调整大小后的图像,仅在第一次打开屏幕时显示延迟。
2. 不使用 WebP 图像另一种优化捆绑资产的方法是使用WebP格式。它可以显著减少图片的文件大小。有很多免费的在线转换工具,并且Flutter本身就支持WebP。
3. 不要在不需要时使用 Opacity 控件Opacity
组件非常有用且方便,但不应在任何情况下随意使用,因为它每次使用时都会新增一层渲染。让我们看看如果这个组件在屏幕上多次出现会发生什么:
Opacity(
opacity: 0.5, // 尽量不要这样做
child: Image.asset(
"assets/6392956.jpg", // 图片路径:assets/6392956.jpg
height: 100, // 高度:100
width: 300, // 宽度:300
),
);
然后我们打开开发者工具面板,看看渲染图层。
每一层都独立渲染,这导致了大量的多余计算。我们可以将颜色与图像混合来替代使用 Opacity
小部件,正如文档中建议的那样。
Image.asset(
"assets/6392956.jpg",
height: 100,
width: 300,
color: Colors.white.withOpacity(0.5), // <- 就是这里
colorBlendMode: BlendMode.modulate, // <- 这里也是
cacheHeight: (100 * MediaQuery.of(context).devicePixelRatio).toInt(),
),
这样我们就把所有的图片放在同一层,进行统一渲染:
在使用ColorFiltered
小部件时,最好使用color
和colorBlendMode
的组合,因为这样可以避免创建新的合成图层。
Flutter 允许我们手动将图片推送到 ImageCache
。我们用之前的一张图片试试看。
TextButton(
child: const Text("预缓存图片"),
onPressed: () async {
var cacheSize = PaintingBinding.instance.imageCache.currentSizeBytes.toString();
print('缓存大小: ' + cacheSize);
final asset = Image.asset(
"assets/6392956.jpg",
height: 100,
width: 300,
cacheHeight: (100 * MediaQuery.of(context).devicePixelRatio).toInt(),
);
await precacheImage(asset.image, context); // <- 预加载图片
print('缓存大小: ' + cacheSize);
},
)
结果是:
因此,当我们使用相同的解码尺寸打开该界面时,它将立即加载,除非被移出缓存。使用这项技术时要小心,因为缓存空间有限。
5. 不缓存网络图片资源。如果你的应用从网络获取图片,每次加载这些图片是没有意义的。相反,我们可以使用cached_network_image库或其他任何替代库。该库的文档非常易于理解。
希望这篇文章对你有帮助。我将在找到有用的技巧时不断更新本文。你可以通过关注我的Twitter来了解最新动态。如果你想查看完整的代码,可以查看仓库。