跟我学企业级flutter项目:如何重新定制cached_network_image的缓存管理与Dio网络请求

351 阅读3分钟

前言

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;

  @override
  int get statusCode => _response.statusCode!;


  @override
  Stream<List<int>> get content => _response.data!.stream;

  @override
  int? 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;
  }


  @override
  DateTime get validTill {
    // Without a cache-control header we keep the file for a week

    var 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);
  }

  @override
  String? get eTag => _header(HttpHeaders.etagHeader);

  @override
  String 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);

  @override
  Future<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网络请求