新建network_image.dart文件, 这里是重写了image的原生调用方式
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'utils/cache_file_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/src/painting/binding.dart';
import 'package:flutter/src/painting/debug.dart';
import 'package:flutter/src/painting/image_provider.dart' as image_provider;
import 'package:flutter/src/painting/image_stream.dart';
/// The dart:io implementation of [image_provider.NetworkImage].
@immutable
class NetworkImage
extends image_provider.ImageProvider<image_provider.NetworkImage>
implements image_provider.NetworkImage {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
const NetworkImage(this.url, {this.scale = 1.0, this.headers})
: assert(url != null),
assert(scale != null);
@override
final String url;
@override
final double scale;
@override
final Map<String, String>? headers;
@override
Future<NetworkImage> obtainKey(
image_provider.ImageConfiguration configuration) {
return SynchronousFuture<NetworkImage>(this);
}
@override
ImageStreamCompleter load(
image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<ImageChunkEvent> chunkEvents =
StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
debugLabel: key.url,
informationCollector: () {
return <DiagnosticsNode>[
DiagnosticsProperty<image_provider.ImageProvider>(
'Image provider', this),
DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
];
},
);
}
// Do not access this field directly; use [_httpClient] instead.
// We set `autoUncompress` to false to ensure that we can trust the value of
// the `Content-Length` HTTP header. We automatically uncompress the content
// in our call to [consolidateHttpClientResponseBytes].
static final HttpClient _sharedHttpClient = HttpClient()
..autoUncompress = false;
static HttpClient get _httpClient {
HttpClient client = _sharedHttpClient;
assert(() {
if (debugNetworkImageHttpClientProvider != null)
client = debugNetworkImageHttpClientProvider!();
return true;
}());
return client;
}
static final CacheFileImage _cacheFileImage = CacheFileImage();
Future<ui.Codec> _loadAsync(
NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents,
image_provider.DecoderCallback decode,
) async {
try {
assert(key == this);
/// 新增代码块start
/// 从缓存目录中查找图片是否存在
final Uint8List? cacheBytes = await _cacheFileImage.getFileBytes(key.url);
if (cacheBytes != null) {
return PaintingBinding.instance!.instantiateImageCodec(cacheBytes);
}
/// 新增代码块end
final Uri resolved = Uri.base.resolve(key.url);
final HttpClientRequest request = await _httpClient.getUrl(resolved);
headers?.forEach((String name, String value) {
request.headers.add(name, value);
});
final HttpClientResponse response = await request.close();
if (response.statusCode != HttpStatus.ok) {
// The network may be only temporarily unavailable, or the file will be
// added on the server later. Avoid having future calls to resolve
// fail to check the network again.
await response.drain<List<int>>();
throw image_provider.NetworkImageLoadException(
statusCode: response.statusCode, uri: resolved);
}
final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (int cumulative, int? total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
));
},
);
/// 新增代码块start
/// 将下载的图片数据保存到指定缓存文件中
await _cacheFileImage.saveBytesToFile(key.url, bytes);
/// 新增代码块end
if (bytes.lengthInBytes == 0)
throw Exception('NetworkImage is an empty file: $resolved');
return decode(bytes);
} catch (e) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is NetworkImage && other.url == url && other.scale == scale;
}
@override
int get hashCode => ui.hashValues(url, scale);
@override
String toString() =>
'${objectRuntimeType(this, 'NetworkImage')}("$url", scale: $scale)';
}
新建工具类:cache_file_image
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:path_provider/path_provider.dart';
class CacheFileImage {
/// 获取url字符串的MD5值
static String getUrlMd5(String url) {
var content = new Utf8Encoder().convert(url);
var digest = md5.convert(content);
return digest.toString();
}
/// 获取图片缓存路径
Future<String> getCachePath() async {
Directory dir = await getApplicationDocumentsDirectory();
Directory cachePath = Directory("${dir.path}");
if (!cachePath.existsSync()) {
cachePath.createSync();
}
return cachePath.path;
}
/// 判断是否有对应图片缓存文件存在
Future<Uint8List?> getFileBytes(String url) async {
String cacheDirPath = await getCachePath();
String urlMd5 = getUrlMd5(url);
File file = File("$cacheDirPath/$urlMd5");
print("读取文件:${file.path}");
if (file.existsSync()) {
return await file.readAsBytes();
}
return null;
}
/// 将下载的图片数据缓存到指定文件
Future saveBytesToFile(String url, Uint8List bytes) async {
String cacheDirPath = await getCachePath();
String urlMd5 = getUrlMd5(url);
File file = File("$cacheDirPath/$urlMd5");
if (!file.existsSync()) {
file.createSync();
await file.writeAsBytes(bytes);
}
}
}