最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Flutter中網(wǎng)絡(luò)圖片加載和緩存的實現(xiàn)

 更新時間:2019年03月19日 15:11:00   作者:Flutter編程指南  
這篇文章主要介紹了Flutter中網(wǎng)絡(luò)圖片加載和緩存的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

應(yīng)用開發(fā)中經(jīng)常會碰到網(wǎng)絡(luò)圖片的加載,通常我們會對圖片進行緩存,以便下次加載同一張圖片時不用再重新下載,在包含有大量圖片的應(yīng)用中,會大幅提高圖片展現(xiàn)速度、提升用戶體驗且為用戶節(jié)省流量。Flutter本身提供的Image Widget已經(jīng)實現(xiàn)了加載網(wǎng)絡(luò)圖片的功能,且具備內(nèi)存緩存的機制,接下來一起看一下Image的網(wǎng)絡(luò)圖片加載的實現(xiàn)。

重溫小部件Image

常用小部件Image中實現(xiàn)了幾種構(gòu)造函數(shù),已經(jīng)足夠我們?nèi)粘i_發(fā)中各種場景下創(chuàng)建Image對象使用了。

有參構(gòu)造函數(shù):

Image(Key key, @required this.image, ...)

開發(fā)者可根據(jù)自定義的ImageProvider來創(chuàng)建Image。

命名構(gòu)造函數(shù):

Image.network(String src, ...)

src即是根據(jù)網(wǎng)絡(luò)獲取的圖片url地址。

Image.file(File file, ...)

file指本地一個圖片文件對象,安卓中需要android.permission.READ_EXTERNAL_STORAGE權(quán)限。

Image.asset(String name, ...)

name指項目中添加的圖片資源名,事先在pubspec.yaml文件中有聲明。

Image.memory(Uint8List bytes, ...)

bytes指內(nèi)存中的圖片數(shù)據(jù),將其轉(zhuǎn)化為圖片對象。

其中Image.network就是我們本篇分享的重點 -- 加載網(wǎng)絡(luò)圖片。

Image.network源碼分析

下面通過源碼我們來看下Image.network加載網(wǎng)絡(luò)圖片的具體實現(xiàn)。

 Image.network(String src, {
  Key key,
  double scale = 1.0,
  .
  .
 }) : image = NetworkImage(src, scale: scale, headers: headers),
    assert(alignment != null),
    assert(repeat != null),
    assert(matchTextDirection != null),
    super(key: key);

 /// The image to display.
 final ImageProvider image;

首先,使用Image.network命名構(gòu)造函數(shù)創(chuàng)建Image對象時,會同時初始化實例變量image,image是一個ImageProvider對象,該ImageProvider就是我們所需要的圖片的提供者,它本身是一個抽象類,子類包括NetworkImage、FileImage、ExactAssetImage、AssetImage、MemoryImage等,網(wǎng)絡(luò)加載圖片使用的就是NetworkImage。

Image作為一個StatefulWidget其狀態(tài)由_ImageState控制,_ImageState繼承自State類,其生命周期方法包括initState()、didChangeDependencies()、build()、deactivate()、dispose()、didUpdateWidget()等。我們重點來_ImageState中函數(shù)的執(zhí)行。

由于插入渲染樹時會先調(diào)用initState()函數(shù),然后調(diào)用didChangeDependencies()函數(shù),_ImageState中并沒有重寫initState()函數(shù),所以didChangeDependencies()函數(shù)會執(zhí)行,看下didChangeDependencies()里的內(nèi)容

@override
 void didChangeDependencies() {
  _invertColors = MediaQuery.of(context, nullOk: true)?.invertColors
   ?? SemanticsBinding.instance.accessibilityFeatures.invertColors;
  _resolveImage();

  if (TickerMode.of(context))
   _listenToStream();
  else
   _stopListeningToStream();

  super.didChangeDependencies();
 }

_resolveImage()會被調(diào)用,函數(shù)內(nèi)容如下

 void _resolveImage() {
  final ImageStream newStream =
   widget.image.resolve(createLocalImageConfiguration(
     context,
     size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null
   ));
  assert(newStream != null);
  _updateSourceStream(newStream);
 }

函數(shù)中先創(chuàng)建了一個ImageStream對象,該對象是一個圖片資源的句柄,其持有著圖片資源加載完畢后的監(jiān)聽回調(diào)和圖片資源的管理者。而其中的ImageStreamCompleter對象就是圖片資源的一個管理類,也就是說,_ImageState通過ImageStream和ImageStreamCompleter管理類建立了聯(lián)系。

再回頭看一下ImageStream對象是通過widget.image.resolve方法創(chuàng)建的,也就是對應(yīng)NetworkImage的resolve方法,我們查看NetworkImage類的源碼發(fā)現(xiàn)并沒有resolve方法,于是查找其父類,在ImageProvider類中找到了。

 ImageStream resolve(ImageConfiguration configuration) {
  assert(configuration != null);
  final ImageStream stream = ImageStream();
  T obtainedKey;
  Future<void> handleError(dynamic exception, StackTrace stack) async {
   .
   .
  }
  obtainKey(configuration).then<void>((T key) {
   obtainedKey = key;
   final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);
   if (completer != null) {
    stream.setCompleter(completer);
   }
  }).catchError(handleError);
  return stream;
 }

ImageStream中的圖片管理者ImageStreamCompleter通過PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);方法創(chuàng)建,imageCache是Flutter框架中實現(xiàn)的用于圖片緩存的單例,查看其中的putIfAbsent方法

 ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
  assert(key != null);
  assert(loader != null);
  ImageStreamCompleter result = _pendingImages[key]?.completer;
  // Nothing needs to be done because the image hasn't loaded yet.
  if (result != null)
   return result;
  // Remove the provider from the list so that we can move it to the
  // recently used position below.
  final _CachedImage image = _cache.remove(key);
  if (image != null) {
   _cache[key] = image;
   return image.completer;
  }
  try {
   result = loader();
  } catch (error, stackTrace) {
   if (onError != null) {
    onError(error, stackTrace);
    return null;
   } else {
    rethrow;
   }
  }
  void listener(ImageInfo info, bool syncCall) {
   // Images that fail to load don't contribute to cache size.
   final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
   final _CachedImage image = _CachedImage(result, imageSize);
   // If the image is bigger than the maximum cache size, and the cache size
   // is not zero, then increase the cache size to the size of the image plus
   // some change.
   if (maximumSizeBytes > 0 && imageSize > maximumSizeBytes) {
    _maximumSizeBytes = imageSize + 1000;
   }
   _currentSizeBytes += imageSize;
   final _PendingImage pendingImage = _pendingImages.remove(key);
   if (pendingImage != null) {
    pendingImage.removeListener();
   }

   _cache[key] = image;
   _checkCacheSize();
  }
  if (maximumSize > 0 && maximumSizeBytes > 0) {
   _pendingImages[key] = _PendingImage(result, listener);
   result.addListener(listener);
  }
  return result;
 }

通過以上代碼可以看到會通過key來查找緩存中是否存在,如果存在則返回,如果不存在則會通過執(zhí)行l(wèi)oader()方法創(chuàng)建圖片資源管理者,而后再將緩存圖片資源的監(jiān)聽方法注冊到新建的圖片管理者中以便圖片加載完畢后做緩存處理。

根據(jù)上面的代碼調(diào)用PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);看出load()方法由ImageProvider對象實現(xiàn),這里就是NetworkImage對象,看下其具體實現(xiàn)代碼

 @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');
   }
  );
 }

代碼中其就是創(chuàng)建一個MultiFrameImageStreamCompleter對象并返回,這是一個多幀圖片管理器,表明Flutter是支持GIF圖片的。創(chuàng)建對象時的codec變量由_loadAsync方法的返回值初始化,查看該方法內(nèi)容

 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 PaintingBinding.instance.instantiateImageCodec(bytes);
 }

這里才是關(guān)鍵,就是通過HttpClient對象對指定的url進行下載操作,下載完成后根據(jù)圖片二進制數(shù)據(jù)實例化圖像編解碼器對象Codec,然后返回。

那么圖片下載完成后是如何顯示到界面上的呢,下面看下MultiFrameImageStreamCompleter的構(gòu)造方法實現(xiàn)

 MultiFrameImageStreamCompleter({
  @required Future<ui.Codec> codec,
  @required double scale,
  InformationCollector informationCollector
 }) : assert(codec != null),
    _informationCollector = informationCollector,
    _scale = scale,
    _framesEmitted = 0,
    _timer = null {
  codec.then<void>(_handleCodecReady, onError: (dynamic error, StackTrace stack) {
   reportError(
    context: 'resolving an image codec',
    exception: error,
    stack: stack,
    informationCollector: informationCollector,
    silent: true,
   );
  });
 }

看,構(gòu)造方法中的代碼塊,codec的異步方法執(zhí)行完成后會調(diào)用_handleCodecReady函數(shù),函數(shù)內(nèi)容如下

 void _handleCodecReady(ui.Codec codec) {
  _codec = codec;
  assert(_codec != null);

  _decodeNextFrameAndSchedule();
 }

方法中會將codec對象保存起來,然后解碼圖片幀

 Future<void> _decodeNextFrameAndSchedule() async {
  try {
   _nextFrame = await _codec.getNextFrame();
  } catch (exception, stack) {
   reportError(
    context: 'resolving an image frame',
    exception: exception,
    stack: stack,
    informationCollector: _informationCollector,
    silent: true,
   );
   return;
  }
  if (_codec.frameCount == 1) {
   // This is not an animated image, just return it and don't schedule more
   // frames.
   _emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));
   return;
  }
  SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame);
 }

如果圖片是png或jpg只有一幀,則執(zhí)行_emitFrame函數(shù),從幀數(shù)據(jù)中拿到圖片幀對象根據(jù)縮放比例創(chuàng)建ImageInfo對象,然后設(shè)置顯示的圖片信息

 void _emitFrame(ImageInfo imageInfo) {
  setImage(imageInfo);
  _framesEmitted += 1;
 }
 
 /// Calls all the registered listeners to notify them of a new image.
 @protected
 void setImage(ImageInfo image) {
  _currentImage = image;
  if (_listeners.isEmpty)
   return;
  final List<ImageListener> localListeners = _listeners.map<ImageListener>(
   (_ImageListenerPair listenerPair) => listenerPair.listener
  ).toList();
  for (ImageListener listener in localListeners) {
   try {
    listener(image, false);
   } catch (exception, stack) {
    reportError(
     context: 'by an image listener',
     exception: exception,
     stack: stack,
    );
   }
  }
 }

這時就會根據(jù)添加的監(jiān)聽器來通知一個新的圖片需要渲染。那么這個監(jiān)聽器是什么時候添加的呢,我們回頭看一下_ImageState類中的didChangeDependencies()方法內(nèi)容,執(zhí)行完_resolveImage();后會執(zhí)行_listenToStream();方法

 void _listenToStream() {
  if (_isListeningToStream)
   return;
  _imageStream.addListener(_handleImageChanged);
  _isListeningToStream = true;
 }

該方法就向ImageStream對象中添加了監(jiān)聽器_handleImageChanged,監(jiān)聽方法如下

 void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
  setState(() {
   _imageInfo = imageInfo;
  });
 }

最終就是調(diào)用setState方法來通知界面刷新,將下載到的圖片渲染到界面上來了。

實際問題

從以上源碼分析,我們應(yīng)該清楚了整個網(wǎng)絡(luò)圖片從加載到顯示的過程,不過使用這種原生的方式我們發(fā)現(xiàn)網(wǎng)絡(luò)圖片只是進行了內(nèi)存緩存,如果殺掉應(yīng)用進程再重新打開后還是要重新下載圖片,這對于用戶而言,每次打開應(yīng)用還是會消耗下載圖片的流量,不過我們可以從中學(xué)習(xí)到一些思路來自己設(shè)計網(wǎng)絡(luò)圖片加載框架,下面作者就簡單的基于Image.network來進行一下改造,增加圖片的磁盤緩存。

解決方案

我們通過源碼分析可知,圖片在緩存中未找到時,會通過網(wǎng)絡(luò)直接下載獲取,而下載的方法是在NetworkImage類中,于是我們可以參考NetworkImage來自定義一個ImageProvider。

代碼實現(xiàn)

拷貝一份NetworkImage的代碼到新建的network_image.dart文件中,在_loadAsync方法中我們加入磁盤緩存的代碼。

 static final CacheFileImage _cacheFileImage = CacheFileImage();

 Future<ui.Codec> _loadAsync(NetworkImage key) async {
  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)
   throw Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved');

/// 新增代碼塊start
/// 將下載的圖片數(shù)據(jù)保存到指定緩存文件中
  await _cacheFileImage.saveBytesToFile(key.url, bytes);
/// 新增代碼塊end

  return PaintingBinding.instance.instantiateImageCodec(bytes);
 }

代碼中注釋已經(jīng)表明了基于原有代碼新增的代碼塊,CacheFileImage是自己定義的文件緩存類,完整代碼如下

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}/imagecache/");
  if(!cachePath.existsSync()) {
   cachePath.createSync();
  }
  return cachePath.path;
 }

 /// 判斷是否有對應(yīng)圖片緩存文件存在
 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;
 }

 /// 將下載的圖片數(shù)據(jù)緩存到指定文件
 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);
  }
 }
}

這樣就增加了文件緩存的功能,思路很簡單,就是在獲取網(wǎng)絡(luò)圖片之前先檢查一下本地文件緩存目錄中是否有緩存文件,如果有則不用再去下載,否則去下載圖片,下載完成后立即將下載到的圖片緩存到文件中供下次需要時使用。

工程的pubspec.yaml中需要增加以下依賴庫

dependencies:
 path_provider: ^0.4.1
 crypto: ^2.0.6

自定義ImageProvider使用

在創(chuàng)建圖片Widget時使用帶參數(shù)的非命名構(gòu)造函數(shù),指定image參數(shù)為自定義ImageProvider對象即可,代碼示例如下

import 'imageloader/network_image.dart' as network;

 Widget getNetworkImage() {
  return Container(
   color: Colors.blue,
   width: 200,
   height: 200,
   child: Image(image: network.NetworkImage("https://flutter.dev/images/flutter-mono-81x100.png")),
  );
 }

寫在最后

以上對Flutter中自帶的Image小部件的網(wǎng)絡(luò)圖片加載流程進行了源碼分析,了解了源碼的設(shè)計思路之后,我們新增了簡單的本地文件緩存功能,這使我們的網(wǎng)絡(luò)圖片加載同時具備了內(nèi)存緩存和文件緩存兩種能力,大大提升了用戶體驗,如果其他同學(xué)有更好的方案可以給作者留言交流。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Android 完全退出的實例詳解

    Android 完全退出的實例詳解

    這篇文章主要介紹了Android 完全退出的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-10-10
  • Android自定義View實現(xiàn)時鐘功能

    Android自定義View實現(xiàn)時鐘功能

    這篇文章主要為大家詳細(xì)介紹了Android自定義View實現(xiàn)時鐘功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Android系統(tǒng)的五種數(shù)據(jù)存儲形式實例(二)

    Android系統(tǒng)的五種數(shù)據(jù)存儲形式實例(二)

    Android系統(tǒng)有五種數(shù)據(jù)存儲形式,分別是文件存儲、SP存儲、數(shù)據(jù)庫存儲、contentprovider 內(nèi)容提供者、網(wǎng)絡(luò)存儲。本文介紹了Android系統(tǒng)的五種數(shù)據(jù)存儲形式,有興趣的可以了解一下。
    2016-12-12
  • Android TextView添加超鏈接的方法示例

    Android TextView添加超鏈接的方法示例

    這篇文章主要介紹了Android TextView添加超鏈接的方法,結(jié)合實例形式分析了TextView控件添加HTML超鏈接的實現(xiàn)技巧,需要的朋友可以參考下
    2016-10-10
  • 詳解Android中的Menu菜單鍵

    詳解Android中的Menu菜單鍵

    本文給大家介紹Android中的設(shè)置按鈕,長按或點擊菜單鍵,涉及到menu菜單的基本用法,本文介紹非常詳細(xì)具有參考價值,特此分享供大家學(xué)習(xí)
    2016-03-03
  • 如何給Android中的按鈕添加圖片功能

    如何給Android中的按鈕添加圖片功能

    這篇文章主要介紹了Android中的按鈕加圖片功能,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-02-02
  • Android開發(fā)注解排列組合出啟動任務(wù)ksp

    Android開發(fā)注解排列組合出啟動任務(wù)ksp

    這篇文章主要為大家介紹了Android開發(fā)注解排列組合出啟動任務(wù)ksp示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • Android入門之TextClock的使用教程

    Android入門之TextClock的使用教程

    TextClock是在Android 4.2(API 17)后推出的用來替代DigitalClock的一個控件。本文將為大家詳細(xì)說說TextClock的使用,感興趣的小伙伴可以了解一下
    2022-11-11
  • android 選項卡(TabHost)如何放置在屏幕的底部

    android 選項卡(TabHost)如何放置在屏幕的底部

    如何將TAB放置在屏幕的底端,有很多的新手都想實現(xiàn)這種效果,本文搜集整理了一些,感興趣的朋友可以參考下哦
    2013-01-01
  • Android實現(xiàn)可拖拽列表和多選功能

    Android實現(xiàn)可拖拽列表和多選功能

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)可拖拽列表和多選功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-06-06

最新評論

沅陵县| 江永县| 全椒县| 朝阳县| 鄯善县| 慈利县| 石家庄市| 若羌县| 双城市| 电白县| 拉孜县| 承德市| 怀远县| 桐柏县| 禹城市| 云浮市| 宣城市| 盖州市| 高雄市| 宜兰市| 凭祥市| 淮阳县| 富锦市| 安泽县| 新和县| 沂水县| 双江| 武鸣县| 赤峰市| 吉首市| 张北县| 依兰县| 上高县| 大庆市| 门头沟区| 石柱| 龙州县| 赞皇县| 齐河县| 遵义县| 霸州市|