4、图片资源管理:Flutter中加载图片(AssetImage、NetworkImage)、Android中加载图片(BitmapFactory、Glide)
图片加载,几乎是每个App都绕不开的坎。我见过不少项目,功能逻辑写得挺漂亮,结果一打开页面,图片加载慢、内存爆、列表卡顿,用户体验直接崩盘。说白了,图片管理做不好,其他都是白搭。
今天咱们就聊聊Flutter和Android两边怎么管好图片资源。我会从本地资源和网络资源两个维度切入,把常用的API和最佳实践掰开揉碎讲清楚。
4.1 Flutter中的图片加载
Flutter的图片加载机制,我个人觉得设计得挺优雅。它把图片抽象成ImageProvider,你只需要告诉它「图片在哪儿」,剩下的解码、缓存、渲染,框架帮你搞定。
4.1.1 AssetImage:加载本地打包资源
先说说最常用的AssetImage。这个家伙负责加载项目pubspec.yaml里声明过的本地资源文件。
配置方式很简单,在pubspec.yaml里这样写:
flutter:
assets:
- assets/images/
- assets/images/logo.png
然后代码里直接引用:
Image(
image: AssetImage('assets/images/logo.png'),
width: 200,
height: 200,
)
嗯,这里有个坑。我记得刚用Flutter时,把图片路径写成了images/logo.png,结果死活加载不出来。后来才发现,AssetImage的路径必须和pubspec.yaml里声明的路径完全一致,包括前面的assets/前缀。
assets/images/icons/、assets/images/banners/。这样后期维护起来,脑子不会乱。
4.1.2 NetworkImage:加载网络图片
网络图片加载,Flutter提供了NetworkImage。用法同样简洁:
Image(
image: NetworkImage('https://example.com/image.jpg'),
width: 200,
height: 200,
)
但说实话,生产环境我很少直接用NetworkImage。为什么?因为它没有缓存策略。每次页面重建,它都会重新发起网络请求。你想想看,用户滑动列表时,每张图都要重新下载,那体验能好吗?
我个人习惯用cached_network_image这个第三方库。它底层封装了磁盘缓存和内存缓存,图片加载过一次后,下次直接从本地读,速度飞快。
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
AssetImage用于本地资源,NetworkImage用于网络资源。但网络图片建议配合缓存库使用,别裸奔。
4.2 Android中的图片加载
Android这边,图片加载的玩法就多了。从原生的BitmapFactory到流行的Glide,各有各的适用场景。
4.2.1 BitmapFactory:原生硬核加载
BitmapFactory是Android SDK自带的图片解码工具。它可以从文件、资源、流等来源加载图片,生成Bitmap对象。
加载本地资源:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_image);
imageView.setImageBitmap(bitmap);
加载文件路径:
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/image.jpg");
imageView.setImageBitmap(bitmap);
但这里有个大坑——OOM(内存溢出)。我曾经在一个项目中,直接加载了一张4K分辨率的图片,结果低端手机直接闪退。为什么会这样?因为BitmapFactory默认会以原始尺寸解码图片,一张几MB的图片解码后可能占用几十MB内存。
解决方案是使用BitmapFactory.Options进行采样压缩:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 宽高各缩小为1/2
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);
BitmapFactory加载大图,极易引发OOM。务必根据ImageView的实际尺寸计算采样率。别偷懒,否则线上事故等着你。
4.2.2 Glide:现代图片加载库
说实话,现在Android开发中,很少有人手撸BitmapFactory了。Glide几乎是行业标配。它封装了图片加载的各个环节:网络请求、内存缓存、磁盘缓存、图片变换、生命周期管理……
基本用法:
Glide.with(this)
.load("https://example.com/image.jpg")
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(imageView);
Glide最让我欣赏的一点是,它会自动绑定Activity或Fragment的生命周期。当页面销毁时,Glide会自动取消正在进行的图片加载请求,避免内存泄漏。这一点,BitmapFactory可做不到。
另外,Glide的缓存策略也很聪明。它默认使用LRU算法管理内存缓存,同时支持磁盘缓存。图片加载过一次后,下次几乎是瞬间显示。
// 自定义缓存策略
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存原图和变换后的图
.skipMemoryCache(false) // 使用内存缓存
.into(imageView);
BitmapFactory适合简单场景或对图片尺寸有精确控制的场景。生产环境推荐Glide,它帮你处理了缓存、生命周期、内存优化等脏活累活。
4.3 知识体系总览
下面这张图,是我梳理的图片资源管理核心逻辑。你可以把它当作一个速查表。
4.4 避坑指南与最佳实践
最后,分享几个我踩过的坑,希望能帮你少走弯路。
- 图片格式选择: 我个人建议,图标类用SVG或WebP,照片类用JPEG或WebP。WebP的压缩率比PNG高30%左右,而且支持透明通道。
- 占位图与错误图: 永远不要省略
placeholder和error参数。用户看到空白区域会困惑,看到加载失败图标也比什么都不显示强。 - 列表中的图片: 在
ListView或RecyclerView中加载图片,一定要复用ViewHolder,并且取消旧item的图片加载请求。Glide和cached_network_image默认做了这件事,但如果你用BitmapFactory,就得自己处理。 - 图片预加载: 如果某个页面确定会显示多张图片,可以在进入页面之前提前加载。Glide提供了
preload方法,Flutter的precacheImage也能达到类似效果。
好了,图片资源管理这块,核心就是「选对工具、管好缓存、控制内存」。Flutter和Android虽然API不同,但底层逻辑是相通的。你只要理解了这些本质,换什么平台都不怕。