前言
flutter中需要展示网络图片时候,不建议使用flutter原本Image.network(),建议最好还是采用cached_network_image这个三方库。那么我今天就按照它来展开说明,我再做企业级项目时如何重新定制cached_network_image。
由于我的项目网络请求采用Dio库,所以我希望我的图片库也采用Dio来网络请求,也是为了方便请求日志打印(在做APM监控时候可以看到网络请求状态,方便定位问题)。
前期准备
准备好mime_converter类,由于cached_network_image中的manager这个文件不是export的状态,那么我们需要准备好该类,以便我们自己实现网络请求修改。
实现mime_converter
创建mime_converter 类,代码如下:
import 'dart:io';///将最常见的MIME类型转换为最期望的文件扩展名。
extension ContentTypeConverter on ContentType {String get fileExtension => mimeTypes[mimeType] ?? '.$subType';
}///MIME类型的来源:
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
///2020年3月20日时更新
const mimeTypes = {'application/vnd.android.package-archive': '.apk','application/epub+zip': '.epub','application/gzip': '.gz','application/java-archive': '.jar','application/json': '.json','application/ld+json': '.jsonld','application/msword': '.doc','application/octet-stream': '.bin','application/ogg': '.ogx','application/pdf': '.pdf','application/php': '.php','application/rtf': '.rtf','application/vnd.amazon.ebook': '.azw','application/vnd.apple.installer+xml': '.mpkg','application/vnd.mozilla.xul+xml': '.xul','application/vnd.ms-excel': '.xls','application/vnd.ms-fontobject': '.eot','application/vnd.ms-powerpoint': '.ppt','application/vnd.oasis.opendocument.presentation': '.odp','application/vnd.oasis.opendocument.spreadsheet': '.ods','application/vnd.oasis.opendocument.text': '.odt','application/vnd.openxmlformats-officedocument.presentationml.presentation':'.pptx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx','application/vnd.openxmlformats-officedocument.wordprocessingml.document':'.docx','application/vnd.rar': '.rar','application/vnd.visio': '.vsd','application/x-7z-compressed': '.7z','application/x-abiword': '.abw','application/x-bzip': '.bz','application/x-bzip2': '.bz2','application/x-csh': '.csh','application/x-freearc': '.arc','application/x-sh': '.sh','application/x-shockwave-flash': '.swf','application/x-tar': '.tar','application/xhtml+xml': '.xhtml','application/xml': '.xml','application/zip': '.zip','audio/3gpp': '.3gp','audio/3gpp2': '.3g2','audio/aac': '.aac','audio/x-aac': '.aac','audio/midi audio/x-midi': '.midi','audio/mpeg': '.mp3','audio/ogg': '.oga','audio/opus': '.opus','audio/wav': '.wav','audio/webm': '.weba','font/otf': '.otf','font/ttf': '.ttf','font/woff': '.woff','font/woff2': '.woff2','image/bmp': '.bmp','image/gif': '.gif','image/jpeg': '.jpg','image/png': '.png','image/svg+xml': '.svg','image/tiff': '.tiff','image/vnd.microsoft.icon': '.ico','image/webp': '.webp','text/calendar': '.ics','text/css': '.css','text/csv': '.csv','text/html': '.html','text/javascript': '.js','text/plain': '.txt','text/xml': '.xml','video/3gpp': '.3gp','video/3gpp2': '.3g2','video/mp2t': '.ts','video/mpeg': '.mpeg','video/ogg': '.ogv','video/webm': '.webm','video/x-msvideo': '.avi','video/quicktime': '.mov'
};
实现FileServiceResponse
FileServiceResponse是数据处理的关键,那么我们来实现该类
class DioGetResponse implements FileServiceResponse {DioGetResponse(this._response);final DateTime _receivedTime = clock.now();final Response<ResponseBody> _response;@overrideint get statusCode => _response.statusCode!;@overrideStream<List<int>> get content => _response.data!.stream;@overrideint? get contentLength => _getContentLength();int _getContentLength() {try {return int.parse(_header(HttpHeaders.contentLengthHeader) ?? '-1');} catch (e) {return -1;}}String? _header(String name) {return _response.headers[name]?.first;}@overrideDateTime get validTill {// Without a cache-control header we keep the file for a weekvar ageDuration = const Duration(days: 7);final controlHeader = _header(HttpHeaders.cacheControlHeader);if (controlHeader != null) {final controlSettings = controlHeader.split(',');for (final setting in controlSettings) {final sanitizedSetting = setting.trim().toLowerCase();if (sanitizedSetting == 'no-cache') {ageDuration = const Duration();}if (sanitizedSetting.startsWith('max-age=')) {var validSeconds = int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;if (validSeconds > 0) {ageDuration = Duration(seconds: validSeconds);}}}}return _receivedTime.add(ageDuration);}@overrideString? get eTag => _header(HttpHeaders.etagHeader);@overrideString get fileExtension {var fileExtension = '';final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);if (contentTypeHeader != null) {final contentType = ContentType.parse(contentTypeHeader);fileExtension = contentType.fileExtension;}return fileExtension;}
}
实现FileService
实现FileService 参数为dio
class DioHttpFileService extends FileService {final Dio _dio;DioHttpFileService(this._dio);@overrideFuture<FileServiceResponse> get(String url, {Map<String, String>? headers}) async {Options options = Options(headers: headers ?? {}, responseType: ResponseType.stream);Response<ResponseBody> httpResponse = await _dio.get<ResponseBody>(url, options: options);return DioGetResponse(httpResponse);}
}
制定框架缓存管理器
我在项目中,设定了缓存配置最多缓存 100 个文件,并且每个文件只应缓存 7天,如果需要使用日志拦截器的话,就在拦截器中增加日志拦截:
class LibCacheManager {static const key = 'libCacheKey';///缓存配置 {最多缓存 100 个文件,并且每个文件只应缓存 7天}static CacheManager instance = CacheManager(Config(key,stalePeriod: const Duration(days: 7),maxNrOfCacheObjects: 100,fileService : DioHttpFileService(Dio()))),);}
项目中使用
使用如下
CachedNetworkImage(imageUrl: "https://t8.baidu.com/it/u=3845489932,4046458829&fm=74&app=80&size=f256,256&n=0&f=JPEG&fmt=auto?sec=1654102800&t=f6de842e1e7086ffc73536795d37fd2c",cacheManager: LibCacheManager.instance,width: 100,height: 100,placeholder: (context, url) => ImgPlaceHolder(),errorWidget: (context, url, error) => ImgError(),
);
如上便是 如何重新定制cached_network_image的缓存管理与Dio网络请求