欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

重溫小部件Image

常用小部件Image中實(shí)現(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指項(xiàng)目中添加的圖片資源名,事先在pubspec.yaml文件中有聲明。

Image.memory(Uint8List bytes, ...)

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

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

Image.network源碼分析

下面通過源碼我們來看下Image.network加載網(wǎng)絡(luò)圖片的具體實(shí)現(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對象時,會同時初始化實(shí)例變量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()等。我們重點(diǎn)來_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框架中實(shí)現(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對象實(shí)現(xiàn),這里就是NetworkImage對象,看下其具體實(shí)現(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ìn)行下載操作,下載完成后根據(jù)圖片二進(jìn)制數(shù)據(jù)實(shí)例化圖像編解碼器對象Codec,然后返回。

那么圖片下載完成后是如何顯示到界面上的呢,下面看下MultiFrameImageStreamCompleter的構(gòu)造方法實(shí)現(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方法來通知界面刷新,將下載到的圖片渲染到界面上來了。

實(shí)際問題

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

解決方案

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

代碼實(shí)現(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ò)圖片加載流程進(jìn)行了源碼分析,了解了源碼的設(shè)計(jì)思路之后,我們新增了簡單的本地文件緩存功能,這使我們的網(wǎng)絡(luò)圖片加載同時具備了內(nèi)存緩存和文件緩存兩種能力,大大提升了用戶體驗(yàn),如果其他同學(xué)有更好的方案可以給作者留言交流。

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

相關(guān)文章

  • Android基礎(chǔ)之Activity生命周期

    Android基礎(chǔ)之Activity生命周期

    activity類是Android 應(yīng)用生命周期的重要部分。在系統(tǒng)中的Activity被一個Activity棧所管理。當(dāng)一個新的Activity啟動時,將被放置到棧頂,成為運(yùn)行中的Activity,前一個Activity保留在棧中,不再放到前臺,直到新的Activity退出為止。
    2016-05-05
  • AndroidStuio插件開發(fā)適用于jetbrains全家桶

    AndroidStuio插件開發(fā)適用于jetbrains全家桶

    這篇文章主要介紹了AndroidStuio插件開發(fā)適用于jetbrains全家桶,本文通過實(shí)例給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • Android Wear計(jì)時器開發(fā)

    Android Wear計(jì)時器開發(fā)

    這篇文章主要介紹了Android Wear計(jì)時器開發(fā),需要的朋友可以參考下
    2014-11-11
  • Kotlin基礎(chǔ)學(xué)習(xí)之循環(huán)和異常

    Kotlin基礎(chǔ)學(xué)習(xí)之循環(huán)和異常

    最近在學(xué)習(xí)kotlin,Kotlin 是一個基于 JVM 的新的編程語言,下面這篇文章主要給大家介紹了關(guān)于Kotlin基礎(chǔ)學(xué)習(xí)之循環(huán)和異常的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-12-12
  • Android編程實(shí)現(xiàn)只顯示圖片一部分的方法

    Android編程實(shí)現(xiàn)只顯示圖片一部分的方法

    這篇文章主要介紹了Android編程實(shí)現(xiàn)只顯示圖片一部分的方法,涉及Android針對圖片的局部顯示操作技巧,需要的朋友可以參考下
    2016-10-10
  • Android 使用CoordinatorLayout實(shí)現(xiàn)滾動標(biāo)題欄效果的實(shí)例

    Android 使用CoordinatorLayout實(shí)現(xiàn)滾動標(biāo)題欄效果的實(shí)例

    下面小編就為大家?guī)硪黄狝ndroid 使用CoordinatorLayout實(shí)現(xiàn)滾動標(biāo)題欄效果的實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • 深入Android 五大布局對象的應(yīng)用

    深入Android 五大布局對象的應(yīng)用

    本篇文章小編為大家介紹,深入Android 五大布局對象的應(yīng)用。需要的朋友參考下
    2013-04-04
  • Android實(shí)現(xiàn)手勢劃定區(qū)域裁剪圖片

    Android實(shí)現(xiàn)手勢劃定區(qū)域裁剪圖片

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手勢劃定區(qū)域裁剪圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Android之用PopupWindow實(shí)現(xiàn)彈出菜單的方法詳解

    Android之用PopupWindow實(shí)現(xiàn)彈出菜單的方法詳解

    本篇文章是對在Android中,用PopupWindow實(shí)現(xiàn)彈出菜單的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • Android 中 MD5 的幾種生成方式(小結(jié))

    Android 中 MD5 的幾種生成方式(小結(jié))

    這篇文章主要介紹了Android 中 MD5 的幾種生成方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03

最新評論