移动端对大批量图片加载的优化方法(三)Flutter
本篇主要从Flutter开发中可以使用到的对大批量图片加载的优化方法进行整理。
1.合适的图片格式
详情请参考移动端对大批量图片加载的优化方法(一)。
2.缓存机制
在Flutter中,处理大批量图片加载的缓存机制主要是依赖于第三方库和Flutter自身的架构;
a.缓存库
Glide和CacheableImage等可以自动处理图片的缓存和加载,使你能够更专注于应用的其他部分。
Future<void> _cacheImage() async { // 指定要缓存的图片URL或路径 String imageUrl = 'https://example.com/image.jpg'; // 使用Glide加载图片并缓存到磁盘中 await Glide.with(context) .load(imageUrl) .into(Image.network(imageUrl)); // 将图片设置到Image widget中 }
Future<void> _cacheImage() async { // 指定要缓存的图片URL或路径 String imageUrl = 'https://example.com/image.jpg'; // 使用CacheableImage加载图片并缓存到磁盘中 await CacheableImage.network(imageUrl).cache(); // 缓存图片到磁盘中 }
b.自定义缓存策略
不能使用第三方库,或者需要更精细的控制,可以创建一个自定义的缓存策略;
可以使用一个持久化的存储(如SQLite或SharedPreferences)来存储已经加载过的图片,并在需要时从缓存中检索它们;
Future<void> _cacheImage() async { // 指定要缓存的图片URL或路径 String imageUrl = 'https://example.com/image.jpg'; // 加载图片并将其转换为字节数组 final response = await http.get(imageUrl); final imageBytes = response.bodyBytes; // 将字节数组写入SharedPreferences中作为二进制数据 SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setInt('image_length', imageBytes.length); prefs.setInt('image_data', imageBytes.hashCode); // 使用哈希值作为键来存储二进制数据 }
注意:可以但不推荐,SharedPreferences一般存储小量数据。
c.Flutter的缓存机制
Flutter本身提供了一些机制来管理资源,包括图片;
可以使用Flutter的AssetBundle API来控制图片的加载和缓存;
这个API允许从资源文件中加载图片,并可以在应用重启后保持这些图片的持久化。
Future<Image> loadImageFromAssetBundle() async { final AssetBundle bundle = await AssetBundle.fromBundle(context); final Uint8List imageBytes = await bundle.loadBytes('assets/image.jpg'); final Image image = Image.memory(imageBytes); return image; }
3.异步加载
处理大量图片的异步加载是一个重要的任务,因为图片加载可能会阻塞UI线程,导致应用界面的卡顿;
a.FutureBuilder
FutureBuilder是一个构建器,用于处理Future。
FutureBuilder<List<dynamic>>( future: fetchImages(), // 假设fetchImages是一个返回Future<List<dynamic>>的函数 builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); // 加载中的UI } else if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return ListView(children: snapshot.data.map((image) => Image.network(image)).toList()); } },
);
b.异步函数
使用async和await关键字来编写异步函数,并在函数内部执行图片加载操作。
Future<void> loadImages() async { List<dynamic> images = await fetchImages(); // 假设fetchImages是一个返回Future<List<dynamic>>的函数 // 处理加载完成的图片列表
}
c.comet_engine
使用Comet引擎库来异步加载图片。
Future<Image> loadImage() async { String imageUrl = 'https://example.com/image.jpg'; // 替换为你要加载的图片URL CometTask task = _engine.getTask(CometRequestType.networkImage, imageUrl); // 创建网络图片请求任务 await task.waitForFinish(); // 等待任务完成并获取图片数据 return Image.memory(task.result.bodyBytes, imageUrl); // 创建Image对象并返回 }
4.懒加载
对于列表或滚动视图中的图片,可以使用懒加载技术,只在需要显示时才加载图片。
ListView( itemCount: imageUrls.length, itemBuilder: (context, index) { return Visibility( visible: imageLoaded[index], // 假设imageLoaded是一个bool类型的列表,用于记录图片是否已加载完成 child: Image.network(imageUrls[index]), // 加载图片的URL或路径 onLoaded: () { // 图片加载完成后的回调函数 setState(() { // 更新UI状态时需要调用setState函数,确保UI的重新构建和更新 imageLoaded[index] = true; // 将对应图片的状态设置为已加载完成 }); }, ); }
5.控制图片大小
根据需要显示的大小加载图片,避免加载过大的图片。
Image( image: AssetImage('assets/images/my_image.jpg'), // 加载图片资源 fit: BoxFit.cover, // 控制图片大小的方式,这里使用cover方式,即保持图片的纵横比并填充整个约束器
)
6.优化内存管理
a.清理不再需要的图片
当图片不再需要时,及时清理它们以释放内存;
可以在图片不再可见时销毁对应的组件或使用Flutter的垃圾回收机制来清理不再使用的对象;
Image image = Image.network('https://example.com/image.jpg');
// ... 显示图片 ...
// 当图片不再需要时
image.dispose();
// 清除所有缓存的图片
ImageCache.clear();
或者使用Offstage或Visibility来控制图片的显示,当图片不再需要显示时,可以将其移到Offstage或隐藏起来;
这不会立即释放资源,但可以避免在不需要时显示不必要的图片;
当用户导航离开某个页面时,可能需要清理该页面加载的图片资源。
b.使用低质量预览图
在加载高质量图片之前,可以先显示一个低质量的预览图;
可以快速看到图片的大致内容,同时避免了长时间的等待和内存占用;
以下是使用image_utils生成预览图的示例:
// 加载原始图片 Uint8List imageBytes = ...; // 获取图片的字节数据 Image originalImage = Image.fromBytes(imageBytes); // 降低图像质量 int quality = 50; // 设置质量等级,范围为0-100 Image lowQualityImage = originalImage.scale(quality);
c.限制同时加载的图片数量
限制同时加载的图片数量,例如使用队列或优先级队列来管理图片的加载顺序。
class ImageLoader { Queue<Future<void>> _queue = Queue<Future<void>>(); int _maxSimultaneousLoads = 3; // 设置并发加载的最大数量 Future<void> loadImage(String url) async { if (_queue.length < _maxSimultaneousLoads) { _queue.add(loadImageInternal(url)); } else { // 等待当前队列中的图片加载完成后再加载新图片 await Future.wait(_queue); _queue.add(loadImageInternal(url)); } } Future<void> loadImageInternal(String url) async { // 加载图片的逻辑... // 假设这里使用Image.network加载图片 await Image.network(url); }
}
7.预加载
在用户请求图片之前预先加载它们,从而提高图片加载速度和响应性。
class PreloadedImage extends StatefulWidget { final String imageUrl; final int fetchDelay; // 延迟时间(毫秒) final bool useCache; // 是否使用缓存 PreloadedImage({required this.imageUrl, this.fetchDelay = 500, this.useCache = true}); @override _PreloadedImageState createState() => _PreloadedImageState();
} class _PreloadedImageState extends State<PreloadedImage> { late Future<Uint8List> _futureImage; late Image _imageWidget; @override void initState() { super.initState(); _futureImage = Future<Uint8List>.delayed(Duration(milliseconds: widget.fetchDelay), () async { return await http.get(widget.imageUrl).then((response) => response.bodyBytes); }); } @override Widget build(BuildContext context) { if (_futureImage != null && !_futureImage.done) { return CircularProgressIndicator(); // 显示加载指示器 } else if (_futureImage == null) { return Text('Image not loaded'); // 初始状态 } else { final Uint8List imageBytes = _futureImage.result; final ImageProvider imageProvider = Image.memory(imageBytes); _imageWidget = Image(image: imageProvider,); // 使用图片数据构建图像Widget return _imageWidget; // 显示图像Widget } }
}
8.合适的View类型
ListView:当有大量可滚动的内容,并且每个项都是一个图片时,使用ListView是最佳选择;
它可以有效地重用子项,减少内存占用,并且可以水平或垂直滚动。
GridView:如果需要显示一个网格布局的图片,并且图片数量是动态的,可以使用GridView;
每个格子中可以放置一个小图片或缩略图。
Stack & Positioned:当需要动态创建大量浮动的图片层时,可以使用Stack和Positioned;
每个Positioned都可以放置一个图片,并且可以设置不同的位置和大小。
Image Widgets:直接使用Image小部件来显示单张大图或一组小图;
对于单张大图,可以使用Image.network或Image.asset来加载;
对于一组小图,可以使用ListView.builder结合Image.network或Image.asset来构建。
Cached Image:使用第三方库如cached_network_image来缓存网络图片,提高加载速度和性能;
这个库提供了预加载和缓存机制,非常适合加载大量图片。
FittedBox or Container with Fit.xxx:当需要精确控制图片的尺寸或者进行一些自定义布局时,可以使用FittedBox或Container的fit属性;
这样可以确保图片不会超出预期尺寸,从而优化内存使用。
9.优化网络请求
详情请参考移动端对大批量图片加载的优化方法(二)。