Flutter图片加载方案分析之power_image

Flutter 默认提供了Image用于从网络、文件等加载图片,并且使用ImageCache统一管理图片缓存,但有时候并不能满足使用需求(比如网络图片没有磁盘缓存,导致每次 ImageCache 清除缓存之后又要从网络下载),所以又出现了flutter_cached_network_imageextended_image等基于 Flutter 原生的解决方案,以及power_image等基于混合开发的解决方案。

本文对 Alibaba 中的 power_image 加载过程、原理做一简单分析。

power_image

power_image是阿里巴巴出品的 Flutter 混合开发图片加载库,通过textureffi技术借助原生图片加载库加载图片、Flutter 端展示图片。

无论是 Flutter Image 组件,还是第三方的extende_imageflutter_cached_nework_image都是在 Flutter 端加载解析图片,这些方案对一般纯 Flutter 开发的 APP 来说基本可以满足要求,但是对于大多数混合开发的 APP 来说,这些方案会在 Flutter 和 Native 同时存在两份图片资源造成内存浪费,此外根据贝壳的分析,Flutter 端解决方案存在图片内存释放时机(Flutter 引擎持有的 SkImage 释放时机)以及超大图内存峰值等问题。

power_image能够较好的解决上述问题,其整体架构如下:

类结构图:
类结构图

架构图:
架构图

power_image可以大体划分为Flutter 端图片展示Native 图片加载两部分,下面分别分析。

Flutter 端图片展示

PowerImage

PowerImage继承自StatefulWidget,提供多种创建方式:既可以使用预设的PowerImage.networkPowerImage.file等构造函数从网络、文件等获取图片;也可以使用PowerImage.typePowerImage.options等自定义通道获取图片并展示;或者使用PowerImage()完全自定义。

除了PowerImage()构造函数之外,上述其余构造函数都根据传入的String? renderingType指定了 PowerImage 特定的PowerImageProvider image属性(是ffi还是texture)用于获取图片。

PowerImageState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class PowerImageState extends State<PowerImage> {
@override
Widget build(BuildContext context) {

ImageErrorWidgetBuilder? errorWidgetBuilder = widget.errorBuilder;
errorWidgetBuilder ??= (...) {
return SizedBox(...);
};

if (widget.image.runtimeType == PowerTextureImageProvider) {
return PowerTextureImage(
provider: widget.image as PowerTextureImageProvider,
...);
} else if (widget.image.runtimeType == PowerExternalImageProvider) {
return PowerExternalImage(
provider: widget.image as PowerExternalImageProvider,
...
);
}

return ImageExt(
image: widget.image,
imageBuilder: widget.imageBuilder,
...
);
}
}

在 PowerImageState 的build()方法中,根据不同的PowerImageProvider image类型会返回不同的 Widget:

  • imagePowerTextureImageProvider 类型:采用 texture 模式展示图片,返回**PowerTextureImage,最终会返回经过封装的Texture**对象。
  • image是**PowerExternalImageProvider类型:采用 ffi 模式展示图片,返回PowerExternalImage,最终返回的是RawImage**对象,和使用 Flutter Image 展示图片的流程一致。
  • 其他类型,按照自定义的规则展示。

让我们来分别看一下**PowerTextureImagePowerExternalImage**的实现:

PowerTextureImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class PowerTextureImage extends StatefulWidget {
const PowerTextureImage({...}):super(key: key);

final PowerTextureImageProvider provider;

@override
PowerTextureState createState() {
return PowerTextureState();
}
}

class PowerTextureState extends State<PowerTextureImage> {
@override
Widget build(BuildContext context) {
return ImageExt(
// 这里的provider实际上创建的是一个虚假的ui.Image? dummy
image: widget.provider,
frameBuilder: widget.frameBuilder,
errorBuilder: widget.errorBuilder,
width: widget.width,
height: widget.height,
fit: widget.fit,
alignment: widget.alignment,
// 注意,这里会创建一个封装的Texture,真正展示图片内容
imageBuilder: buildImage,
semanticLabel: widget.semanticLabel,
excludeFromSemantics: widget.excludeFromSemantics,
);
}

Widget buildImage(BuildContext context, ImageInfo? imageInfo) {
if (imageInfo == null || imageInfo is! PowerTextureImageInfo) {
return SizedBox(
width: widget.width,
height: widget.height,
);
}

PowerTextureImageInfo textureImageInfo = imageInfo;
return ClipRect(
child: SizedBox(
child: FittedBox(
fit: widget.fit ?? BoxFit.contain,
alignment: widget.alignment,
child: SizedBox(
width: textureImageInfo.width?.toDouble() ?? widget.width,
height: textureImageInfo.height?.toDouble() ?? widget.height,
child: Texture(
// 注意,这里的textureId是从provider创建的ImageInfo中获取的
textureId: textureImageInfo.textureId!,
),
),
),
width: widget.width,
height: widget.height,
),
);
}
}

PowerExternalImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class PowerExternalImage extends StatefulWidget {
const PowerExternalImage({...}):super(key: key);

final PowerExternalImageProvider provider;

@override
PowerExteralState createState() => PowerExteralState();
}

class PowerExteralState extends State<PowerExternalImage> {
@override
Widget build(BuildContext context) {
return ImageExt(
frameBuilder: widget.frameBuilder,
errorBuilder: widget.errorBuilder,
// provider会根据Native数据创建含有对应的ui.Image的
// ImageInfo,展示对应图片
image: widget.provider,
width: widget.width,
height: widget.height,
fit: widget.fit,
alignment: widget.alignment,
semanticLabel: widget.semanticLabel,
excludeFromSemantics: widget.excludeFromSemantics,
);
}
}

通过对比**PowerTextureImagePowerExternalImage**的源码可以发现,二者最终还是创建了 ImageExt 对象,只不过 PowerTextureImage 中 ImageExt.imageBuilder 返回了 Texture,而 PowerExternalImage 中 ImageExt.imageBuilder 为 null。

再根据下面的_ImageExtState.build源码可以确定,当使用 PowerTextureImage 时 PowerImage 创建的是封装了的 Texture,而 PowerExternalImage 时则会使用 PowerExternalImageProvider 创建的 ImageInfo 创建 RawImage,这实际上与 Flutter 原有的 Image 组件一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// _ImageExtState.build
@override
Widget build(BuildContext context) {
...

Widget result;

if (widget.imageBuilder != null) {
result = widget.imageBuilder!(context, _imageInfo);
} else {
result = RawImage(
image: _imageInfo?.image,
...
);
}

...

return result;
}

通过上面的分析,可以知道,**PowerImage使用从PowerImageProvider** 获取的 ImageInfo 来展示图片,采用**texture**方案时,使用 ImageInfo 中的 textureId 并返回 Texture 对象展示图片;而使用 ffi 方案时,会使用 ImageInfo 中的ui.Image image对象传入 RawIamge 展示图片(这部分与 Flutter Image 组件逻辑一致)。


PowerImageProvider

PowerImageProvider 继承自ImageProviderExt -> _ImageProvider_,是power_image的关键类之一,主要实现通过 Flutter/Native 跨端通信从 Native 获取/释放图片资源等,创建供ImageExt使用的ImageInfo。

相对于 Flutter 官方的 ImageProvider,除了修改部分类为 power_image 对应的类之外,PowerImageProvider主要有以下几点改变:

  • 工厂方法PowerImageProvider.options生产PowerImageProvider:根据传入的PowerImageRequestOptions中PowerImageRequestOptions.renderingType的值,分别创建对应的PowerExternalImageProvider或者PowerTextureImageProvider
  • 重写_loadAsync方法,调用 Native 图片库加载图片,并根据返回值调用子类 createImageInfo 方法创建 PowerImageInfo。

_loadAsync

通过重写_loadAsync方法,PowerImageProvider 实现了不同的子类分别创建 PowerImageInfo 展示图片、统一让 ImageCache 管理图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Future<ImageInfo> _loadAsync(
PowerImageProvider key, DecoderCallback? decode) async {
try {
// 跨端通信获取图片资源,后面再详细分析
PowerImageCompleter powerImageCompleter =
PowerImageLoader.instance.loadImage(options);
Map map = await powerImageCompleter.completer!.future;
bool? success = map['success'];

// remove multiFrame image cache On Last Listener Removed
bool? isMultiFrame = map['_multiFrame'];
if (isMultiFrame == true) {
_completer!
.addOnLastListenerRemovedCallback(() {
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
});
}
_completer = null;

if (success != true) {
// The network may be only temporarily unavailable, or the file will be
// added on the server later. Avoid having future calls to resolve
// fail to check the network again.
final PowerImageLoadException exception =
PowerImageLoadException(nativeResult: map);
PowerImageMonitor.instance().anErrorOccurred(exception);
throw exception;
}
// 创建ImageInfo
return createImageInfo(map);
} catch (e) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
rethrow;
} finally {
// chunkEvents.close();
}
}

在上面的分析中,我们得知,textureffi方案分别使用 ImageProvider 提供的 PowerImageInfo 中的int? textureIdui.Image image展示图片,让我们分别看一下他们是如何获取的:

PowerTextureImageProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class PowerTextureImageProvider extends PowerImageProvider {
PowerTextureImageProvider(PowerImageRequestOptions options) : super(options);

@override
FutureOr<ImageInfo> createImageInfo(Map map) {
int? textureId = map['textureId'];
int? width = map['width'];
int? height = map['height'];
return PowerTextureImageInfo.create(
textureId: textureId, width: width, height: height);
}

@override
void dispose() {
PowerImageLoader.instance.releaseImageRequest(options);
super.dispose();
}
}

class PowerTextureImageInfo extends PowerImageInfo {
static ui.Image? dummy;
final int? textureId;

// 此方法使用一个通用的ui.Image? dummy创建PowerTextureImageInfo
// 以便让ImageCache能够管理texture创建的图片
static FutureOr<PowerTextureImageInfo> create(
{int? textureId, int? width, int? height}) async {
if (dummy != null) {
return PowerTextureImageInfo(
textureId: textureId,
width: width,
height: height,
image: dummy!.clone());
}

dummy = await _createImage(1, 1);
return PowerTextureImageInfo(
textureId: textureId,
width: width,
height: height,
image: dummy!.clone());
}
}

Future<ui.Image> _createImage(int width, int height) async {
final Completer<ui.Image> completer = Completer<ui.Image>();
ui.decodeImageFromPixels(// 使用指定的Uint8List创建ui.Image
Uint8List.fromList(
List<int>.filled(width * height * 4, 0, growable: false)),
width,
height,
ui.PixelFormat.rgba8888,
(ui.Image image) {
completer.complete(image);
},
);
return completer.future;
}

PowerExternalImageProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class PowerExternalImageProvider extends PowerImageProvider {
PowerExternalImageProvider(PowerImageRequestOptions options) : super(options);

@override
FutureOr<ImageInfo> createImageInfo(Map map) {
Completer<ImageInfo> completer = Completer<ImageInfo>();
int handle = map['handle'];
int length = map['length'];
int width = map['width'];
int height = map['height'];
int? rowBytes = map['rowBytes'];
ui.PixelFormat pixelFormat =
ui.PixelFormat.values[map['flutterPixelFormat'] ?? 0];
// 获取图片在内存中的指针
Pointer<Uint8> pointer = Pointer<Uint8>.fromAddress(handle);
// 获取对应内存中的数据
Uint8List pixels = pointer.asTypedList(length);
// 根据内存中的数据创建ui.Image,这里会发生内存拷贝,大图片会出现内存峰值偏高
ui.decodeImageFromPixels(pixels, width, height, pixelFormat,
(ui.Image image) {
ImageInfo imageInfo = PowerImageInfo(image: image);
completer.complete(imageInfo);
//释放platform_image
PowerImageLoader.instance.releaseImageRequest(options);
}, rowBytes: rowBytes);
return completer.future;
}
}

从上述代码可以看到:

  • texture 方案采用的 PowerTextureImageProvider 创建的 ImageInfo 对应的 ui.Image image 一个共享的占位符,并不能真正真正绘制内容,实际上图片信息在对应的PowerTextureImageInfo.textureId中;
  • ffi 方案创建的 ImageInfo 则根据 native 内存中的图片数据创建了对应的 ui.Image,与 Flutter 默认的 ImageProvider 提供的 ImageInfo 一样可以被 RawImage 正常使用。

这里需要注意,虽然 textureffi 都采用了ImageCache来管理图片缓存,甚至 ffi 的内存也在Flutter侧管理,但是PowerImage本身不会出现我们之前在Flutter Image中分析的加载大量高清网图会出现的内存爆炸,这是因为虽然在ImageCache.putIfAbsent方法中_pendingImages同样保存了加载中的图片,但是实际这些图片加载过程中的内存由Native端图片加载库管理,而非Flutter,所以只要Native端图片加载库比较成熟,就可以避免这个问题。

到目前为止,我们分析了 PowerImage 根据 PowerImageProvider 获取的 ImageInfo 分别采用 ffi 和 texture 两种方案展示图片的过程。


接下来分析一下之前提到的 PowerImageProvider._loadAsync 方法中使用 PowerImageLoader 获取图片的过程。整个过程可以分为 flutter 端发起请求/处理回调native 端接收请求/返回结果两部分,在这过程中 Flutter 和 Native 使用 MethodChannel(发送获取释放图片指令) 和 EventChannel(接收图片成功加载的事件)进行通信。

Flutter/Native 通信

在上面分析PowerImageProvider._loadAsync方法时,我们注意到其中使用了**PowerImageLoader**获取图片信息 PowerImageCompleter:

1
2
3
4
5
6
7
8
9
10
11
// PowerImageProvider._loadAsync 省略部分代码
Future<ImageInfo> _loadAsync(
PowerImageProvider key, DecoderCallback? decode) async {
try {
PowerImageCompleter powerImageCompleter =
PowerImageLoader.instance.loadImage(options);
Map map = await powerImageCompleter.completer!.future;
}
return createImageInfo(map);
}

这里是使用 PowerImageLoader 的单例加载图片,看一下具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class PowerImageLoader {
// 保存发起的图片请求
static Map<String?, PowerImageCompleter> completers =
<String?, PowerImageCompleter>{};

PowerImageChannel channel = PowerImageChannel();
static PowerImageLoader instance = PowerImageLoader._();

PowerImageLoader._() {
channel.impl = PowerImagePlatformChannel();
}

// 初始化PowerImageChannel等,需要在加载图片之前(比如runApp之前执行)
void setup(PowerImageSetupOptions? options) {
_globalRenderType = options?.globalRenderType ?? defaultGlobalRenderType;
PowerImageMonitor.instance().errorCallback = options?.errorCallback;
PowerImageMonitor.instance().errorCallbackSamplingRate = options?.errorCallbackSamplingRate;
channel.setup();
}

PowerImageCompleter loadImage(PowerImageRequestOptions options,) {
PowerImageRequest request = PowerImageRequest.create(options);// 创建PowerImageRequest
// 发起图片请求
channel.startImageRequests(<PowerImageRequest>[request]);
// 使用completers记录下刚刚发起的请求
PowerImageCompleter completer = PowerImageCompleter();
completer.request = request;
completer.completer = Completer<Map>();
completers[request.uniqueKey()] = completer;
return completer;
}

// 当上面loadImage发起的图片加载完成之后,会调用此方法,从completers中取回对应的请求,调用完成
void onImageComplete(Map<dynamic, dynamic> map) async {
String? uniqueKey = map['uniqueKey'];
PowerImageCompleter? completer = completers.remove(uniqueKey);
//todo null case
completer?.completer?.complete(map);
}
}

首先创建了 PowerImageRequest 对象:

1
2
3
4
5
6
7
8
class PowerImageRequest {
PowerImageRequest.create(PowerImageRequestOptions options)
: imageWidth = options.imageWidth,
imageHeight = options.imageHeight,
imageType = options.imageType,
renderingType = options.renderingType,
src = options.src;
}

其中:

  • imageType表示获取图片的方式(比如networknativeAssetfileasset等);
  • renderingType表示图片渲染方式,比如external(即 ffi 方案)、texture

然后通过PowerImageChannel发送请求(实际的执行的类是PowerImagePlatformChannel):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class PowerImagePlatformChannel extends PowerImageChannelImpl {

StreamSubscription? _subscription;

PowerImagePlatformChannel() {
eventHandlers['onReceiveImageEvent'] = (Map<dynamic, dynamic> event) {
// 将onReceiveImageEvent放到eventHandlers中,
// 上述PowerImageLoader发起的请求完成后会执行下述代码
PowerImageLoader.instance.onImageComplete(event);
};
}

@override
void setup() {
// 监听回调方法,监听Native端发送的图片加载结束事件
startListening();
}

StreamSubscription? startListening() {
_subscription ??= eventChannel.receiveBroadcastStream().listen(onEvent);
return _subscription;
}

Map<String, EventHandler?> eventHandlers = <String, EventHandler?>{};

// 处理Native端发送的事件
void onEvent(dynamic val) {
assert(val is Map<dynamic, dynamic>);
final Map<dynamic, dynamic> event = val;
String? eventName = event['eventName'];
EventHandler? eventHandler = eventHandlers[eventName!];
if (eventHandler != null) {
eventHandler(event);
} else {
//TODO 发来了不认识的事件,需要处理一下
}
}

void registerEventHandler(String eventName, EventHandler eventHandler) {
assert(eventName.isNotEmpty);
eventHandlers[eventName] = eventHandler;
}

void unregisterEventHandler(String eventName) {
eventHandlers[eventName] = null;
}

@visibleForTesting
final MethodChannel methodChannel = const MethodChannel('power_image/method');

@visibleForTesting
EventChannel eventChannel = const EventChannel('power_image/event');

// 主动发送请求到Native端
@override
void startImageRequests(List<PowerImageRequest> requests) async {
await methodChannel.invokeListMethod(
'startImageRequests', encodeRequests(requests));
}

@override
void releaseImageRequests(List<PowerImageRequest> requests) async {
await methodChannel.invokeListMethod(
'releaseImageRequests', encodeRequests(requests));
}
}

小结一下:

  • 使用PowerImageLoader.setup注册 MethodChannel 和 EventChannel
  • 使用PowerImageLoader.loadImage向 Native 发起请求加载图片,将请求保存到PowerImageLoader.completers中并返回给调用者
  • 当 Native 端处理完请求之后会回调 PowerImagePlatformChannel 中注册的 EventChannel,然后会执行PowerImageLoader.instance.onImageComplete(event)方法,使用返回的图片信息,从PowerImageLoader.completers找出并完成之前的请求

以上分析为 Flutter 端向 Native 端发起请求的过程,下面以 Android 端为例分析一下 Native 端的处理过程:

首先是在 PowerImagePlugin 中向 Flutter 引擎注册对应的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PowerImagePlugin implements FlutterPlugin, MethodCallHandler {

@Override
public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
if(sContext == null){
sContext = flutterPluginBinding.getApplicationContext();
}
// 注册与Flutter端对应的方法
methodChannel = new MethodChannel(
flutterPluginBinding.getBinaryMessenger(), "power_image/method");
methodChannel.setMethodCallHandler(this);
eventChannel = new EventChannel(
flutterPluginBinding.getBinaryMessenger(), "power_image/event");
eventChannel.setStreamHandler(PowerImageEventSink.getInstance());
PowerImageRequestManager.getInstance()
.configWithTextureRegistry(flutterPluginBinding.getTextureRegistry());
PowerImageDispatcher.getInstance().prepare();
}
}

当 Flutter 端向 Native 发送消息时,Flutter 引擎会调用PowerImagePlugin.onMethodCall方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// PowerImagePlugin.onMethodCall
@Override
public void onMethodCall(MethodCall call, Result result) {
if ("startImageRequests".equals(call.method)) {
if (call.arguments instanceof List) {
List arguments = (List) call.arguments;
// 将请求结果返回,只是根据传参创建请求并保存,
// 将请求信息返回给Flutter端
List results = PowerImageRequestManager.getInstance()
.configRequestsWithArguments(arguments);
result.success(results);
// 开始真正执行请求,找到上一步创建的请求PowerImageBaseRequest
// 并执行PowerImageBaseRequest.startLoading
PowerImageRequestManager.getInstance().startLoadingWithArguments(arguments);
} else {
throw new IllegalArgumentException("startImageRequests require List arguments");
}
} else if ("releaseImageRequests".equals(call.method)) {
if (call.arguments instanceof List) {
List arguments = (List) call.arguments;
// 立即执行释放请求
List results = PowerImageRequestManager.getInstance().releaseRequestsWithArguments(arguments);
result.success(results);
} else {
throw new IllegalArgumentException("stopImageRequests require List arguments");
}
} else {
result.notImplemented();
}
}

对于不同的调用请求:

  • startImageRequests:先根据请求参数创建好请求并返回给 Flutter 调用方;然后通过 PowerImageRequestManager 真正执行请求(最终会通过PowerImagePlugin.PowerImageEventSink.getInstance().sendImageStateEvent向 Flutter 通知结果)。
  • releaseImageRequests:立即从PowerImageRequestManager.requests中去除对应的请求并尝试终止任务,并向 Flutter 返回结果。

下面着重分析一下执行图片请求的逻辑(startImageRequests的情况):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class PowerImageRequestManager {
private Map<String, PowerImageBaseRequest> requests;
private WeakReference<TextureRegistry> textureRegistryWrf;

public List<Map<String, Object>> configRequestsWithArguments(List<Map<String, Object>> list) {
List<Map<String, Object>> results = new ArrayList<>();
if (list == null || list.isEmpty()) {
return results;
}
for (int i = 0; i < list.size(); i++) {
Map<String, Object> arguments = list.get(i);
String renderType = (String) arguments.get("renderingType");
PowerImageBaseRequest request;
if (RENDER_TYPE_EXTERNAL.equals(renderType)) {// ffi方案
request = new PowerImageExternalRequest(arguments);
} else if (RENDER_TYPE_TEXTURE.equals(renderType)) {// texture方案
request = new PowerImageTextureRequest(arguments, textureRegistryWrf.get());
} else {
continue;
}
// 保存创建的请求
requests.put(request.requestId, request);
boolean success = request.configTask();
Map<String, Object> requestInfo = request.encode();
requestInfo.put("success", success);
results.add(requestInfo);
}
return results;
}

public void startLoadingWithArguments(List arguments) {
if (arguments == null || arguments.isEmpty()) {
return;
}
for (int i = 0; i < arguments.size(); i++) {
Map arg = (Map) arguments.get(i);
String requestId = (String) arg.get("uniqueKey");
// 找出在configRequestsWithArguments方法中创建的请求并执行
PowerImageBaseRequest request = requests.get(requestId);
request.startLoading();
}
}
}

可见对于ffitexture方案,分别涉及到**PowerImageExternalRequestPowerImageTextureRequest两个类。他们都继承自PowerImageBaseRequest**类,其startLoading方法会调用performLoadImage方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class PowerImageBaseRequest {
private void performLoadImage() {
// 获取图片
PowerImageLoader.getInstance().handleRequest(
imageRequestConfig,
new PowerImageLoaderProtocol.PowerImageResponse() {
@Override
public void onResult(PowerImageResult result) {
// 加载到图片之后进行解析
PowerImageBaseRequest.this.onLoadResult(result);
}
}
);
}
}

PowerImageBaseRequest.performLoadImage方法中:

  • 会先通过PowerImageLoader.getInstance().handleRequest方法获取图片;
  • 然后调用PowerImageBaseRequest.this.onLoadResult方法也就是PowerImageExternalRequestPowerImageTextureRequestonLoadResult()方法。

在他们的onLoadResult(final PowerImageResult result)方法中,入参 PowerImageResult 持有 FlutterImage 对象,后者持有加载的图片的 Drawable,他们根据各自的特点对图片进行处理后(ffi获取 Drawable 的 bitmap 对象,<>如果图片不是ARGB_8888发生一次 Bitmap 拷贝>;texture使用 Bitmap 绘制到 Canvas 上面),通过PowerImageBaseRequest.onLoadSuccess()方法或者PowerImageBaseRequest.onLoadFailed返回结果。

其中:

  • PowerImageExternalRequest 从获取到的图片生成 Bitmap 并返回其指针、宽高、大小等属性返回;
  • PowerImageTextureRequest 则将图片绘制到Surface中并返回textureId等信息。

而对于PowerImageLoader.getInstance().handleRequest(),这里面的各个 PowerImageLoaderProtocol 由 Native 端通过PowerImageLoader.getInstance().registerImageLoader注册具体的实现handleRequest()方法正是调用他们获取图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PowerImageLoader implements PowerImageLoaderProtocol {

private final Map<String, PowerImageLoaderProtocol> imageLoaders;

private PowerImageLoader() {
imageLoaders = new HashMap<>();
}

private static class Holder {
private final static PowerImageLoader instance = new PowerImageLoader();
}

public static PowerImageLoader getInstance() {
return PowerImageLoader.Holder.instance;
}

// 在Android中调用此方法,注册获取"network"、"nativeAsset"、"asset"、"file"等图片的实现
public void registerImageLoader(PowerImageLoaderProtocol loader, String imageType) {
imageLoaders.put(imageType, loader);
}

// 此方法调用上面registerImageLoader方法注册的ImageLoader获取图片
@Override
public void handleRequest(PowerImageRequestConfig request, PowerImageResponse response) {
PowerImageLoaderProtocol imageLoader = imageLoaders.get(request.imageType);
if (imageLoader == null) {
throw new IllegalStateException("PowerImageLoader for "
+ request.imageType + " has not been registered.");
}
imageLoader.handleRequest(request, response);
}
}

Native 端图片获取

上面提到,power_image默认的**PowerImageLoaderProtocol**有以下几种类:”network“、”nativeAsset“、”asset“、”file“,这些都需要使用者在 Native 端注册才能正常使用。

以”network“为例,在MainActivity.onCreate方法中:

1
2
3
4
5
6
7
8
9
class MainActivity: FlutterActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
...
PowerImageLoader.getInstance().registerImageLoader(
PowerImageNetworkLoader(this.applic ationContext), "network"
)
}
}

**PowerImageNetworkLoader继承自PowerImageLoaderProtocol**,图片的加载逻辑在其handleRequest方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class PowerImageNetworkLoader(private val context: Context) : PowerImageLoaderProtocol {
override fun handleRequest(request: PowerImageRequestConfig, response: PowerImageResponse) {
// 使用Glide加载图片Drawable
Glide.with(context).asDrawable().load(request.srcString())
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?,model: Any,
target: Target<Drawable>,isFirstResource: Boolean): Boolean {
response.onResult(PowerImageResult.genFailRet("Native加载失败: " + if (e != null) e.message else "null"))
return true
}

override fun onResourceReady(resource: Drawable,model: Any target:Target<Drawable>,dataSource: DataSource,isFirstResource:Boolean
): Boolean {
if (resource is GifDrawable) {
// 动图
// 加载成功,调用回调
response.onResult(
PowerImageResult.genSucRet(
GlideMultiFrameImage(
resource as GifDrawable,
false
)
)
)
} else {
if (resource is BitmapDrawable) {// 普通图片
response.onResult(
PowerImageResult.genSucRet(
FlutterSingleFrameImage(
resource as BitmapDrawable
)
)
)
} else {
response.onResult(PowerImageResult.genFailRet("Native加载失败: resource : $resource"))
}
}
return true
}
}).submit(
if (request.width <= 0) Target.SIZE_ORIGINAL else request.width,
if (request.height <= 0) Target.SIZE_ORIGINAL else request.height
)
}
}

这样,当 Flutter 端向 Native 发送消息时:

  • Flutter 引擎会调用PowerImagePlugin.onMethodCall方法,先创建对应的请求**PowerImageBaseRequest**;
  • 然后PowerImageRequestManager.getInstance().startLoadingWithArguments执行刚刚上一步创建的请求,此方法内部执行PowerImageBaseRequest.startLoading()方法;
  • 在**PowerImageBaseRequest类内部,其startLoading方法会调用performLoadImage方法,后者又会调用PowerImageLoader.getInstance().handleRequest()方法请求加载图片**,并指定回调方法为PowerImageBaseRequest.onLoadResult(result)
  • PowerImageLoader.handleRequest方法内部通过请求的imageType找到 Native 端(比如 Android 在MainActivity.onCreate中注册的)PowerImageLoaderProtocol imageLoader,并执行其handleRequest方法处理加载图片请求;
  • PowerImageLoaderProtocol.handleRequest()方法中调用原生的图片加载库获取 Drawable 并生成 PowerImageResult 回调PowerImageResponse.onResult方法,此方法会回调PowerImageBaseRequest.this.onLoadResult(result)
  • 在**PowerImageTextureRequest或者PowerImageExternalRequest**的onLoadResult方法中对获取到的PowerImageResult进行处理之后回调PowerImageBaseRequestonLoadSuccess()或者onLoadFailed(final String errMsg)方法返回图片请求结果。

总结

power_image 是一个利用原生库加载/管理图片的比较适用于 Flutter/Native 混合开发的图片加载库,提供了 textureffi 两种加载图片的方式。

其中,texture 方案实际使用 Texture 组件展示图片;而 ffi 方案则只有图片获取在 Native 端,当使用ui.decodeImageFromPixels方法从 Bitmap 内存指针创建ui.Image之后(根据阿里的描述,这里会发生一次内存拷贝,实际代码可以参考这里),剩下按照和 Flutter Image 类似的步骤展示图片。

根据官方的说法:

  1. Texture 适用于日常场景,优先选择;
  2. FFI 更适用于
    1. flutter <= 1.23.0-18.1.pre 版本中,在模拟器上显示图片
    2. 获取 ui.Image 图片数据
    3. flutter 侧解码,解码前的数据拷贝影响较小。

此外,根据官方power_image/issues/17的说法,“在 2.5.3 上 ffi 性能已经跟  texture不相上下了”,而且 textrue 方案在 Android 上较大尺寸可能会 crash(flutter/flutter#92397),所以更推荐使用 ffi 方案

参考资料

https://github.com/alibaba/power_image

Flutter 图片库高燃新登场

闲鱼 Flutter 图片框架架构演进(超详细)

Flutter 图片内存优化实践

https://github.com/alibaba/power_image/issues/17

ui.decodeImageFromPixels分析