




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】如何使用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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 宠物训练与心理健康-全面剖析
- 课题申报书:新时代高校校友文化培养研究
- 课题申报书:新时代高校辅导员阶段性发展特点与支持策略研究
- 课题申报书:新课改背景下初高中生物学课堂教学模式及教学效果提升研究
- 随机网络建模与应用-全面剖析
- 围篱用钢铁松绞丝企业县域市场拓展与下沉战略研究报告
- 织机及其辅助机械零件企业数字化转型与智慧升级战略研究报告
- 绿色环保型产品研发-全面剖析
- 量子中继技术在长距离通信中的应用-全面剖析
- 随机微分方程在金融数学中的前沿应用-全面剖析
- GB/T 16955-1997声学农林拖拉机和机械操作者位置处噪声的测量简易法
- GB/T 15593-2020输血(液)器具用聚氯乙烯塑料
- GB 16410-2007家用燃气灶具
- 铁碳合金的相图解读
- 2023年复旦大学博士研究生入学考试专家推荐信模板
- 中小学教师资格证面试课件讲义
- 全国初中英语优质课大赛一等奖《八年级Unit 6An old man》说课课件
- 云南省饮用水生产企业名录534家
- 湖北地区医院详细名单一览表
- 麦肯锡入职培训第一课:让职场新人一生受用的逻辑思考力新员工培训教材
- 金属压铸机的plc控制
评论
0/150
提交评论