1. 内存管理策略
- 图片缓存:
- 使用
CacheManager
类实现本地缓存,比如flutter_cache_manager
库。它能自动管理图片缓存,在内存和磁盘中存储图片,下次加载相同图片时优先从缓存读取。例如:
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
Future<Uint8List?> getImageBytes(String url) async {
final file = await DefaultCacheManager().getSingleFile(url);
return file.readAsBytes();
}
- 利用Flutter的
ImageCache
,它是Flutter内部的图片缓存机制。通过precacheImage
方法提前缓存图片,在需要显示时直接从缓存获取。如:
Future<void> precacheImages(List<String> imageUrls) async {
for (String url in imageUrls) {
await precacheImage(NetworkImage(url), context);
}
}
- 图片尺寸优化:
- 根据显示区域的大小,加载合适尺寸的图片。比如使用图片CDN,通过在URL中指定图片的宽高参数,获取对应尺寸的图片。例如:
https://example.com/image.jpg?width=200&height=200
。
- 在本地对图片进行缩放处理,使用
ImageDecoder
和ImageEncoder
类来调整图片大小,减少内存占用。如:
import 'dart:ui' as ui;
import 'dart:io';
import 'dart:typed_data';
Future<Uint8List> resizeImage(Uint8List imageData, int targetWidth, int targetHeight) async {
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromList(imageData, (ui.Image img) => completer.complete(img));
final ui.Image image = await completer.future;
final ByteData? resizedData = await ui.PictureRecorder().endRecording().toImage(targetWidth, targetHeight).toByteData(format: ui.ImageByteFormat.png);
return resizedData!.buffer.asUint8List();
}
- 及时释放内存:
- 当图片不再显示在屏幕上时,使用
ImageStreamListener
监听图片的ImageEvent
,当图片从屏幕移除时,手动从ImageCache
中移除对应的图片缓存。如:
ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
// 监听图片加载完成
},
onImageEnd: (ImageInfo image, bool synchronousCall) {
// 图片不再显示时,移除缓存
PaintingBinding.instance.imageCache.remove(image.image);
},
);
2. 网络请求优先级处理
- 使用
http
库结合优先级队列:
- 自定义一个请求优先级队列,将不同优先级的网络请求加入队列。例如,当前屏幕即将显示区域的图片请求优先级设为高,后续分页的图片请求优先级设为低。
- 在发送网络请求时,优先处理高优先级的请求。可以使用
http
库发送请求,如:
import 'package:http/http.dart' as http;
Future<http.Response> sendRequestWithPriority(String url, int priority) async {
// 将请求加入优先级队列
// 从队列中取出高优先级请求发送
return http.get(Uri.parse(url));
}
- 利用
Stream
和StreamController
:
- 创建一个
StreamController
来管理网络请求的数据流。根据用户的操作(如滑动屏幕),向StreamController
添加不同优先级的请求。
- 使用
Stream
的listen
方法按照优先级顺序处理请求。如:
final StreamController<NetworkRequest> requestController = StreamController();
class NetworkRequest {
final String url;
final int priority;
NetworkRequest(this.url, this.priority);
}
void addRequest(String url, int priority) {
requestController.add(NetworkRequest(url, priority));
}
requestController.stream
.sorted((a, b) => b.priority.compareTo(a.priority))
.listen((request) async {
final response = await http.get(Uri.parse(request.url));
// 处理响应
});
3. 滑动过程中平滑加载,减少卡顿
- 分页加载与预加载:
- 实现分页加载,每次加载固定数量的图片,比如每页加载10张图片。当用户滑动到列表底部一定距离时,触发下一页图片的加载。
- 预加载即将显示区域的图片,当用户滑动屏幕时,提前加载接下来可能显示的图片。可以通过计算屏幕可见区域和列表滚动位置来确定预加载的图片范围。例如:
// 假设ListView的item高度固定为200
const itemHeight = 200;
// 预加载屏幕高度2倍范围内的图片
const preloadFactor = 2;
final int preloadStartIndex = (scrollPosition.pixels ~/ itemHeight) - (MediaQuery.of(context).size.height ~/ itemHeight * preloadFactor).toInt();
final int preloadEndIndex = preloadStartIndex + (MediaQuery.of(context).size.height ~/ itemHeight * preloadFactor * 2).toInt();
// 预加载范围内的图片
for (int i = preloadStartIndex; i < preloadEndIndex; i++) {
if (i >= 0 && i < imageUrls.length) {
precacheImage(NetworkImage(imageUrls[i]), context);
}
}
- 使用
ListView.builder
:
ListView.builder
采用按需构建的方式,只会构建当前屏幕显示及附近的Widget,不会一次性构建所有图片,有效减少内存占用和渲染压力。如:
ListView.builder(
itemCount: imageUrls.length,
itemBuilder: (context, index) {
return Image.network(imageUrls[index]);
},
);
- 优化渲染性能:
- 避免在
build
方法中进行复杂的计算,将图片加载等异步操作放在initState
或didChangeDependencies
等生命周期方法中处理。
- 使用
RepaintBoundary
包裹图片,防止不必要的重绘。如:
RepaintBoundary(
child: Image.network(imageUrls[index]),
)