【移动应用开发技术】如何使用Flutter加载网络图片_第1页
【移动应用开发技术】如何使用Flutter加载网络图片_第2页
【移动应用开发技术】如何使用Flutter加载网络图片_第3页
【移动应用开发技术】如何使用Flutter加载网络图片_第4页
【移动应用开发技术】如何使用Flutter加载网络图片_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】如何使用Flutter加载网络图片

这篇文章将为大家详细讲解有关如何使用Flutter加载网络图片,文章内容质量较高,因此在下分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。有参构造函数:Image(Keykey,@requiredthis.image,...)开发者可根据自定义的ImageProvider来创建Image。命名构造函数:Iwork(Stringsrc,...)src即是根据网络获取的图片url地址。Image.file(Filefile,...)file指本地一个图片文件对象,安卓中需要android.permission.READ_EXTERNAL_STORAGE权限。Image.asset(Stringname,...)name指项目中添加的图片资源名,事先在pubspec.yaml文件中有声明。Image.memory(Uint8Listbytes,...)bytes指内存中的图片数据,将其转化为图片对象。其中Iwork就是我们本篇分享的重点--加载网络图片。Iwork源码分析下面通过源码我们来看下Iwork加载网络图片的具体实现。

Iwork(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;首先,使用Iwork命名构造函数创建Image对象时,会同时初始化实例变量image,image是一个ImageProvider对象,该ImageProvider就是我们所需要的图片的提供者,它本身是一个抽象类,子类包括NetworkImage、FileImage、ExactAssetImage、AssetImage、MemoryImage等,网络加载图片使用的就是NetworkImage。Image作为一个StatefulWidget其状态由_ImageState控制,_ImageState继承自State类,其生命周期方法包括initState()、didChangeDependencies()、build()、deactivate()、dispose()、didUpdateWidget()等。我们重点来_ImageState中函数的执行。由于插入渲染树时会先调用initState()函数,然后调用didChangeDependencies()函数,_ImageState中并没有重写initState()函数,所以didChangeDependencies()函数会执行,看下didChangeDependencies()里的内容@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()会被调用,函数内容如下

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

}函数中先创建了一个ImageStream对象,该对象是一个图片资源的句柄,其持有着图片资源加载完毕后的监听回调和图片资源的管理者。而其中的ImageStreamCompleter对象就是图片资源的一个管理类,也就是说,_ImageState通过ImageStream和ImageStreamCompleter管理类建立了联系。再回头看一下ImageStream对象是通过widget.image.resolve方法创建的,也就是对应NetworkImage的resolve方法,我们查看NetworkImage类的源码发现并没有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);方法创建,imageCache是Flutter框架中实现的用于图片缓存的单例,查看其中的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

pleter;

}

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来查找缓存中是否存在,如果存在则返回,如果不存在则会通过执行loader()方法创建图片资源管理者,而后再将缓存图片资源的监听方法注册到新建的图片管理者中以便图片加载完毕后做缓存处理。根据上面的代码调用PaintingBinding.instance.imageCache.putIfAbsent(key,()=>load(key),onError:handleError);看出load()方法由ImageProvider对象实现,这里就是NetworkImage对象,看下其具体实现代码

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

}

);

}代码中其就是创建一个MultiFrameImageStreamCompleter对象并返回,这是一个多帧图片管理器,表明Flutter是支持GIF图片的。创建对象时的codec变量由_loadAsync方法的返回值初始化,查看该方法内容

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

}这里才是关键,就是通过HttpClient对象对指定的url进行下载操作,下载完成后根据图片二进制数据实例化图像编解码器对象Codec,然后返回。那么图片下载完成后是如何显示到界面上的呢,下面看下MultiFrameImageStreamCompleter的构造方法实现

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,

);

});

}看,构造方法中的代码块,codec的异步方法执行完成后会调用_handleCodecReady函数,函数内容如下

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只有一帧,则执行_emitFrame函数,从帧数据中拿到图片帧对象根据缩放比例创建ImageInfo对象,然后设置显示的图片信息

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,

);

}

}

}这时就会根据添加的监听器来通知一个新的图片需要渲染。那么这个监听器是什么时候添加的呢,我们回头看一下_ImageState类中的didChangeDependencies()方法内容,执行完_resolveImage();后会执行_listenToStream();方法

void

_listenToStream()

{

if

(_isListeningToStream)

return;

_imageStream.addListener(_handleImageChanged);

_isListeningToStream

=

true;

}该方法就向ImageStream对象中添加了监听器_handleImageChanged,监听方法如下

void

_handleImageChanged(ImageInfo

imageInfo,

bool

synchronousCall)

{

setState(()

{

_imageInfo

=

imageInfo;

});

}最终就是调用setState方法来通知界面刷新,将下载到的图片渲染到界面上来了。实际问题从以上源码分析,我们应该清楚了整个网络图片从加载到显示的过程,不过使用这种原生的方式我们发现网络图片只是进行了内存缓存,如果杀掉应用进程再重新打开后还是要重新下载图片,这对于用户而言,每次打开应用还是会消耗下载图片的流量,不过我们可以从中学习到一些思路来自己设计网络图片加载框架,下面作者就简单的基于Iwork来进行一下改造,增加图片的磁盘缓存。解决方案我们通过源码分析可知,图片在缓存中未找到时,会通过网络直接下载获取,而下载的方法是在NetworkImage类中,于是我们可以参考NetworkImage来自定义一个ImageProvider。代码实现拷贝一份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

///

将下载的图片数据保存到指定缓存文件中

await

_cacheFileImage.saveBytesToFile(key.url,

bytes);

///

新增代码块end

return

PaintingBinding.instance.instantiateImageCodec(bytes);

}代码中注释已经表明了基于原有代码新增的代码块,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;

}

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论