中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

如何使用Flutter加載網(wǎng)絡(luò)圖片

這篇文章將為大家詳細(xì)講解有關(guān)如何使用Flutter加載網(wǎng)絡(luò)圖片,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

創(chuàng)新互聯(lián)公司網(wǎng)站建設(shè)公司是一家服務(wù)多年做網(wǎng)站建設(shè)策劃設(shè)計(jì)制作的公司,為廣大用戶提供了成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作,成都網(wǎng)站設(shè)計(jì),一元廣告,成都做網(wǎng)站選創(chuàng)新互聯(lián)公司,貼合企業(yè)需求,高性價(jià)比,滿足客戶不同層次的需求一站式服務(wù)歡迎致電。

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

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

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

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

Image.network(String src, ...)

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

Image.file(File file, ...)

file指本地一個(gè)圖片文件對(duì)象,安卓中需要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)化為圖片對(duì)象。

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

Image.network源碼分析

下面通過(guò)源碼我們來(lái)看下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對(duì)象時(shí),會(huì)同時(shí)初始化實(shí)例變量image,image是一個(gè)ImageProvider對(duì)象,該ImageProvider就是我們所需要的圖片的提供者,它本身是一個(gè)抽象類,子類包括NetworkImage、FileImage、ExactAssetImage、AssetImage、MemoryImage等,網(wǎng)絡(luò)加載圖片使用的就是NetworkImage。

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

由于插入渲染樹(shù)時(shí)會(huì)先調(diào)用initState()函數(shù),然后調(diào)用didChangeDependencies()函數(shù),_ImageState中并沒(méi)有重寫(xiě)initState()函數(shù),所以didChangeDependencies()函數(shù)會(huì)執(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()會(huì)被調(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)建了一個(gè)ImageStream對(duì)象,該對(duì)象是一個(gè)圖片資源的句柄,其持有著圖片資源加載完畢后的監(jiān)聽(tīng)回調(diào)和圖片資源的管理者。而其中的ImageStreamCompleter對(duì)象就是圖片資源的一個(gè)管理類,也就是說(shuō),_ImageState通過(guò)ImageStream和ImageStreamCompleter管理類建立了聯(lián)系。

再回頭看一下ImageStream對(duì)象是通過(guò)widget.image.resolve方法創(chuàng)建的,也就是對(duì)應(yīng)NetworkImage的resolve方法,我們查看NetworkImage類的源碼發(fā)現(xiàn)并沒(méi)有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通過(guò)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;
 }

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

根據(jù)上面的代碼調(diào)用PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);看出load()方法由ImageProvider對(duì)象實(shí)現(xiàn),這里就是NetworkImage對(duì)象,看下其具體實(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)建一個(gè)MultiFrameImageStreamCompleter對(duì)象并返回,這是一個(gè)多幀圖片管理器,表明Flutter是支持GIF圖片的。創(chuàng)建對(duì)象時(shí)的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)鍵,就是通過(guò)HttpClient對(duì)象對(duì)指定的url進(jìn)行下載操作,下載完成后根據(jù)圖片二進(jìn)制數(shù)據(jù)實(shí)例化圖像編解碼器對(duì)象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í)行完成后會(huì)調(diào)用_handleCodecReady函數(shù),函數(shù)內(nèi)容如下

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

  _decodeNextFrameAndSchedule();
 }

方法中會(huì)將codec對(duì)象保存起來(lái),然后解碼圖片幀

 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ù)中拿到圖片幀對(duì)象根據(jù)縮放比例創(chuàng)建ImageInfo對(duì)象,然后設(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,
    );
   }
  }
 }

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

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

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

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

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

實(shí)際問(wèn)題

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

解決方案

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

代碼實(shí)現(xiàn)

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

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

 /// 判斷是否有對(duì)應(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);
  }
 }
}

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

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

dependencies:
 path_provider: ^0.4.1
 crypto: ^2.0.6

自定義ImageProvider使用

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

import 'imageloader/network_image.dart' as network;

 Widget getNetworkImage() {
  return Container(
   color: Colors.blue,
   width: 200,
   height: 200,
   child: Image(image: network.NetworkImage("/upload/otherpic74/119303.png")),
  );
 }

關(guān)于如何使用Flutter加載網(wǎng)絡(luò)圖片就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

當(dāng)前名稱:如何使用Flutter加載網(wǎng)絡(luò)圖片
網(wǎng)頁(yè)地址:http://www.rwnh.cn/article30/pgscpo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站動(dòng)態(tài)網(wǎng)站、小程序開(kāi)發(fā)品牌網(wǎng)站建設(shè)、外貿(mào)建站、Google

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名
辽阳县| 马尔康县| 桐乡市| 巴楚县| 土默特右旗| 阿合奇县| 青州市| 慈溪市| 新巴尔虎左旗| 大英县| 武义县| 聂荣县| 镇原县| 民和| 河北区| 固镇县| 呼玛县| 延庆县| 竹北市| 枣阳市| 枣强县| 巴里| 宁城县| 林州市| 合川市| 新巴尔虎右旗| 龙门县| 黎川县| 子洲县| 澜沧| 子长县| 白玉县| 广昌县| 兴安盟| 韩城市| 清新县| 青州市| 福安市| 嘉兴市| 榆社县| 彭水|