cache_image.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /*
  2. * @Author : WuWei
  3. * @LastEditors : WuWei
  4. * @Date : 2022-02-24 17:05:18
  5. * @LastEditTime : 2023-11-02 14:20:44
  6. * @Description : Do not edit
  7. */
  8. import 'dart:async';
  9. import 'dart:convert' as convert;
  10. import 'dart:io';
  11. import 'dart:ui' as ui;
  12. import 'package:crypto/crypto.dart';
  13. import 'package:flutter/foundation.dart';
  14. import 'package:flutter/material.dart';
  15. import 'package:path_provider/path_provider.dart';
  16. /// The dart:io implementation of [image_provider.NetworkImage].
  17. @immutable
  18. class WisLocalCacheNetworkImage extends ImageProvider<NetworkImage>
  19. implements NetworkImage {
  20. /// Creates an object that fetches the image at the given URL.
  21. ///
  22. /// The arguments [url] and [scale] must not be null.
  23. const WisLocalCacheNetworkImage(
  24. this.url, {
  25. this.scale = 1.0,
  26. this.headers,
  27. // 缓存标识
  28. this.isLocalCache = true,
  29. });
  30. @override
  31. final String url;
  32. @override
  33. final double scale;
  34. @override
  35. final Map<String, String>? headers;
  36. final bool isLocalCache;
  37. @override
  38. Future<NetworkImage> obtainKey(ImageConfiguration configuration) {
  39. return SynchronousFuture<NetworkImage>(this);
  40. }
  41. ImageStreamCompleter load(NetworkImage key) {
  42. // Ownership of this controller is handed off to [_loadAsync]; it is that
  43. // method's responsibility to close the controller's stream when the image
  44. // has been loaded or an error is thrown.
  45. final StreamController<ImageChunkEvent> chunkEvents =
  46. StreamController<ImageChunkEvent>();
  47. return MultiFrameImageStreamCompleter(
  48. codec: _loadAsync(key, chunkEvents),
  49. chunkEvents: chunkEvents.stream,
  50. scale: key.scale,
  51. debugLabel: key.url,
  52. informationCollector: () {
  53. return <DiagnosticsNode>[
  54. DiagnosticsProperty<ImageProvider>('Image provider', this),
  55. DiagnosticsProperty<NetworkImage>('Image key', key),
  56. ];
  57. },
  58. );
  59. }
  60. // Do not access this field directly; use [_httpClient] instead.
  61. // We set `autoUncompress` to false to ensure that we can trust the value of
  62. // the `Content-Length` HTTP header. We automatically uncompress the content
  63. // in our call to [consolidateHttpClientResponseBytes].
  64. static final HttpClient _sharedHttpClient = HttpClient()
  65. ..autoUncompress = false;
  66. static HttpClient get _httpClient {
  67. HttpClient client = _sharedHttpClient;
  68. assert(() {
  69. if (debugNetworkImageHttpClientProvider != null)
  70. client = debugNetworkImageHttpClientProvider!();
  71. return true;
  72. }());
  73. return client;
  74. }
  75. Future<ui.Codec> _loadAsync(
  76. NetworkImage key,
  77. StreamController<ImageChunkEvent> chunkEvents,
  78. ) async {
  79. try {
  80. assert(key == this);
  81. /// 如果本地缓存过图片,直接返回图片
  82. if (isLocalCache == true) {
  83. final Uint8List? bytes = await _getImageFromLocal(key.url);
  84. if (bytes != null && bytes.lengthInBytes != 0) {
  85. var data = await ui.ImmutableBuffer.fromUint8List(bytes);
  86. return await PaintingBinding.instance
  87. .instantiateImageCodecWithSize(data);
  88. }
  89. }
  90. final Uri resolved = Uri.base.resolve(key.url);
  91. final HttpClientRequest request = await _httpClient.getUrl(resolved);
  92. headers?.forEach((String name, String value) {
  93. request.headers.add(name, value);
  94. });
  95. final HttpClientResponse response = await request.close();
  96. if (response.statusCode != HttpStatus.ok) {
  97. // The network may be only temporarily unavailable, or the file will be
  98. // added on the server later. Avoid having future calls to resolve
  99. // fail to check the network again.
  100. throw NetworkImageLoadException(
  101. statusCode: response.statusCode, uri: resolved);
  102. }
  103. final Uint8List bytes = await consolidateHttpClientResponseBytes(
  104. response,
  105. onBytesReceived: (int cumulative, int? total) {
  106. chunkEvents.add(ImageChunkEvent(
  107. cumulativeBytesLoaded: cumulative,
  108. expectedTotalBytes: total,
  109. ));
  110. },
  111. );
  112. /// 网络请求结束后,将图片缓存到本地
  113. if (isLocalCache == true && bytes.lengthInBytes != 0) {
  114. _saveImageToLocal(bytes, key.url);
  115. }
  116. if (bytes.lengthInBytes == 0)
  117. throw Exception('NetworkImage is an empty file: $resolved');
  118. var data = await ui.ImmutableBuffer.fromUint8List(bytes);
  119. return await PaintingBinding.instance.instantiateImageCodecWithSize(data);
  120. } catch (e) {
  121. // Depending on where the exception was thrown, the image cache may not
  122. // have had a chance to track the key in the cache at all.
  123. // Schedule a microtask to give the cache a chance to add the key.
  124. scheduleMicrotask(() {
  125. PaintingBinding.instance.imageCache.evict(key);
  126. });
  127. rethrow;
  128. } finally {
  129. chunkEvents.close();
  130. }
  131. }
  132. /// 图片路径通过MD5处理,然后缓存到本地
  133. void _saveImageToLocal(Uint8List mUInt8List, String name) async {
  134. String path = await _getCachePathString(name);
  135. var file = File(path);
  136. bool exist = await file.exists();
  137. if (!exist) {
  138. File(path).writeAsBytesSync(mUInt8List);
  139. }
  140. }
  141. /// 从本地拿图片
  142. Future<Uint8List?> _getImageFromLocal(String name) async {
  143. String path = await _getCachePathString(name);
  144. var file = File(path);
  145. bool exist = await file.exists();
  146. if (exist) {
  147. final Uint8List bytes = await file.readAsBytes();
  148. return bytes;
  149. }
  150. return null;
  151. }
  152. /// 获取图片的缓存路径并创建
  153. Future<String> _getCachePathString(String name) async {
  154. // 获取图片的名称
  155. String filePathFileName = md5.convert(convert.utf8.encode(name)).toString();
  156. String extensionName = name.split('/').last.split('.').last;
  157. // print('图片url:$name');
  158. // print('filePathFileName:$filePathFileName');
  159. // print('extensionName:$extensionName');
  160. // 生成、获取结果存储路径
  161. final tempDic = await getTemporaryDirectory();
  162. Directory directory = Directory(tempDic.path + '/CacheImage/');
  163. bool isFoldExist = await directory.exists();
  164. if (!isFoldExist) {
  165. await directory.create();
  166. }
  167. return directory.path + filePathFileName + '.$extensionName';
  168. }
  169. @override
  170. bool operator ==(Object other) {
  171. if (other.runtimeType != runtimeType) return false;
  172. return other is NetworkImage && other.url == url && other.scale == scale;
  173. }
  174. @override
  175. int get hashCode => Object.hash(url, scale);
  176. @override
  177. String toString() =>
  178. '${objectRuntimeType(this, 'NetworkImage')}("$url", scale: $scale)';
  179. }