Flutter 實現圖片緩存到本地


網上可能有很多實現的插件,有些動不動就上千行代碼, 其實很簡單 只需要在源碼的基礎上加一個本地緩存就行,

畢竟源碼是最可靠的 

https://github.com/dikeboy/flutter-cache-image-local

Flutter  自帶的有2個 圖片的Widget ,Image 和FadeInImage, 從字面上就能理解 FadeIn是多了個預加載的圖,

FadeInImage 有兩個 方法 ,memoryNetwork 和assertNetwork  預加載圖來源於二進制 或者 assert,如果是動態生成的 就需要用memoryNetwork

網絡圖片是通過NetworkImage來實現加載, 

文件  assert  memory  networkImage 都是繼承自Image_provider,

Image_provider本身已經帶了一個內存緩存,大概就類似安卓的Lrucache

 

 ImageStream resolve(ImageConfiguration configuration) {
    assert(configuration != null);
    final ImageStream stream = ImageStream();
    T obtainedKey;
    obtainKey(configuration).then<void>((T key) {
      obtainedKey = key;
      stream.setCompleter(PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key)));

 

這里的imageCache就是Flutter 自帶的緩存

 

我們要實現緩存到本地   肯定需要 在內存緩存之后    網絡請求之前


先來看下NetworkImage的源碼

class NetworkImage extends ImageProvider<NetworkImage> {
  /// Creates an object that fetches the image at the given URL.
  ///
  /// The arguments must not be null.
  const NetworkImage(this.url, { this.scale = 1.0 , this.headers })
      : assert(url != null),
        assert(scale != null);

  /// The URL from which the image will be fetched.
  final String url;

  /// The scale to place in the [ImageInfo] object of the image.
  final double scale;

  /// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
  final Map<String, String> headers;

  @override
  Future<NetworkImage> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture<NetworkImage>(this);
  }

  @override
  ImageStreamCompleter load(NetworkImage key) {
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(key), //這個就是主要的圖片網絡加載方法 , 我們主要是需要修改這個方法
      scale: key.scale,
      informationCollector: (StringBuffer information) {
        information.writeln('Image provider: $this');
        information.write('Image key: $key');
      }
    );
  }

  static final HttpClient _httpClient = HttpClient();
  方法很簡單,拿回數據 轉換為二進制返回/
  Future<ui.Codec> _loadAsync(NetworkImage key) async {
    assert(key == this);

    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)
      throw Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved');

    final Uint8List bytes = await consolidateHttpClientResponseBytes(response);
    if (bytes.lengthInBytes == 0)
      throw Exception('NetworkImage is an empty file: $resolved');

    return await PaintingBinding.instance.instantiateImageCodec(bytes);
  }

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final NetworkImage typedOther = other;
    return url == typedOther.url
        && scale == typedOther.scale;
  }

  @override
  int get hashCode => hashValues(url, scale);

  @override
  String toString() => '$runtimeType("$url", scale: $scale)';
}

 

這里需要引入 幾個插件   其實這幾個就算不修改這里 項目中肯定也是必須的

http: ^0.12.0   網絡請求 這個毫無疑問
transparent_image: ^0.1.0 //這是一個預加載的透明圖 ,如果需要用assert圖或者是自定義可以不需要 這里為了方便
path_provider: ^0.4.1 //這是獲取 安卓 IOS 文件緩存路徑
crypto: ^2.0.6 //這個主要是MD5 base64之類 轉碼 我們根據圖片的md5(url)做為key來緩存

下面直接貼修改后的 NetworkImage
class MyNetworkImage extends ImageProvider<MyNetworkImage> {
  /// Creates an object that fetches the image at the given URL.
  ///
  /// The arguments must not be null.
  const MyNetworkImage(this.url, { this.scale = 1.0 , this.headers,this.sdCache })
      : assert(url != null),
        assert(scale != null);

  /// The URL from which the image will be fetched.
  final String url;

  final bool sdCache;  //加一個標志為 是否需要緩存到sd卡

  /// The scale to place in the [ImageInfo] object of the image.
  final double scale;

  /// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
  final Map<String, String> headers;

  @override
  Future<MyNetworkImage> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture<MyNetworkImage>(this);
  }

  @override
  ImageStreamCompleter load(MyNetworkImage key) {
    return MultiFrameImageStreamCompleter(
        codec: _loadAsync(key),
        scale: key.scale,
        informationCollector: (StringBuffer information) {
          information.writeln('Image provider: $this');
          information.write('Image key: $key');
        }
    );
  }


  Future<Codec> _loadAsync(MyNetworkImage key) async {
    assert(key == this);
//本地已經緩存過就直接返回圖片
if(sdCache!=null){ final Uint8List bytes =await _getFromSdcard(key.url); if (bytes!=null&&bytes.lengthInBytes!=null&&bytes.lengthInBytes!= 0) { print("success"); return await PaintingBinding.instance.instantiateImageCodec(bytes); } } final Uri resolved = Uri.base.resolve(key.url); http.Response response = await http.get(resolved); if (response.statusCode != HttpStatus.ok) throw Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved'); final Uint8List bytes = await response.bodyBytes;
//網絡請求結束后緩存圖片到本地
if(sdCache!=null&&bytes.lengthInBytes != 0){ _saveToImage(bytes, key.url); } if (bytes.lengthInBytes == 0) throw Exception('MyNetworkImage is an empty file: $resolved'); return await PaintingBinding.instance.instantiateImageCodec(bytes); } @override bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final MyNetworkImage typedOther = other; return url == typedOther.url && scale == typedOther.scale; } @override int get hashCode => hashValues(url, scale); @override String toString() => '$runtimeType("$url", scale: $scale)'; 圖片路徑MD5一下 緩存到本地 void _saveToImage(Uint8List mUint8List,String name) async { name = md5.convert(convert.utf8.encode(name)).toString(); Directory dir = await getTemporaryDirectory(); String path = dir.path +"/"+name; var file = File(path); bool exist = await file.exists(); print("path =${path}"); if(!exist) File(path).writeAsBytesSync(mUint8List); } _getFromSdcard(String name) async{ name = md5.convert(convert.utf8.encode(name)).toString(); Directory dir = await getTemporaryDirectory(); String path = dir.path +"/"+name; var file = File(path); bool exist = await file.exists(); if(exist){ final Uint8List bytes = await file.readAsBytes(); return bytes; } return null; } }

 

如果需要直接修改FadeInImage 或者Image 直接把代碼烤到一個新的dart文件  把NetworkImage 全部改成MyNetworkImage就行了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM