Flutter 图片加载方案分析之 power_image
Flutter 默认提供了Image用于从网络、文件等加载图片,并且使用ImageCache统一管理图片缓存,但有时候并不能满足使用需求(比如网络图片没有磁盘缓存,导致每次 ImageCache 清除缓存之后又要从网络下载),所以又出现了flutter_cached_network_image、extended_image等基于 Flutter 原生的解决方案,以及power_image等基于混合开发的解决方案。
本文对 Alibaba 中的 power_image 加载过程、原理做一简单分析。
power_image
power_image是阿里巴巴出品的 Flutter 混合开发图片加载库,通过texture和ffi技术借助原生图片加载库加载图片、Flutter 端展示图片。
无论是 Flutter Image 组件,还是第三方的extende_image、flutter_cached_nework_image都是在 Flutter 端加载解析图片,这些方案对一般纯 Flutter 开发的 APP 来说基本可以满足要求,但是对于大多数混合开发的 APP 来说,这些方案会在 Flutter 和 Native 同时存在两份图片资源造成内存浪费,此外根据贝壳的分析,Flutter 端解决方案存在图片内存释放时机(Flutter 引擎持有的 SkImage 释放时机)以及超大图内存峰值等问题。
而power_image能够较好的解决上述问题,其整体架构如下:
类结构图:
架构图:
power_image可以大体划分为Flutter 端图片展示和Native 图片加载两部分,下面分别分析。
Flutter 端图片展示
PowerImage
PowerImage
继承自StatefulWidget,提供多种创建方式:既可以使用预设的PowerImage.network
、PowerImage.file
等构造函数从网络、文件等获取图片;也可以使用PowerImage.type
、PowerImage.options
等自定义通道获取图片并展示;或者使用PowerImage()
完全自定义。
除了PowerImage()
构造函数之外,上述其余构造函数都根据传入的String? renderingType
指定了 PowerImage 特定的PowerImageProvider image
属性(是ffi还是texture)用于获取图片。
PowerImageState
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:
image
是 PowerTextureImageProvider 类型:采用 texture 模式展示图片,返回**PowerTextureImage,最终会返回经过封装的Texture**对象。image
是**PowerExternalImageProvider类型:采用 ffi 模式展示图片,返回PowerExternalImage,最终返回的是RawImage**对象,和使用 Flutter Image 展示图片的流程一致。- 其他类型,按照自定义的规则展示。
让我们来分别看一下**PowerTextureImage和PowerExternalImage**的实现:
PowerTextureImage
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
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,
);
}
}
通过对比**PowerTextureImage和PowerExternalImage**的源码可以发现,二者最终还是创建了 ImageExt 对象,只不过 PowerTextureImage 中 ImageExt.imageBuilder 返回了 Texture,而 PowerExternalImage 中 ImageExt.imageBuilder 为 null。
再根据下面的_ImageExtState.build
源码可以确定,当使用 PowerTextureImage 时 PowerImage 创建的是封装了的 Texture,而 PowerExternalImage 时则会使用 PowerExternalImageProvider 创建的 ImageInfo 创建 RawImage,这实际上与 Flutter 原有的 Image 组件一致。
// _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 管理图片。
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();
}
}
在上面的分析中,我们得知,texture和ffi方案分别使用 ImageProvider 提供的 PowerImageInfo 中的int? textureId
和ui.Image image
展示图片,让我们分别看一下他们是如何获取的:
PowerTextureImageProvider
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
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 正常使用。
这里需要注意,虽然 texture 和 ffi 都采用了 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:
// 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 的单例加载图片,看一下具体的实现:
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 对象:
class PowerImageRequest {
PowerImageRequest.create(PowerImageRequestOptions options)
: imageWidth = options.imageWidth,
imageHeight = options.imageHeight,
imageType = options.imageType,
renderingType = options.renderingType,
src = options.src;
}
其中:
imageType
表示获取图片的方式(比如network
,nativeAsset
,file
,asset
等);renderingType
表示图片渲染方式,比如external
(即 ffi 方案)、texture
。
然后通过PowerImageChannel
发送请求(实际的执行的类是PowerImagePlatformChannel
):
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 引擎注册对应的方法。
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
方法:
// 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
的情况):
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();
}
}
}
可见对于ffi和texture方案,分别涉及到**PowerImageExternalRequest和PowerImageTextureRequest两个类。他们都继承自PowerImageBaseRequest**类,其startLoading
方法会调用performLoadImage
方法:
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
方法也就是PowerImageExternalRequest和PowerImageTextureRequest的onLoadResult()
方法。
在他们的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()
方法正是调用他们获取图片。
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
方法中:
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
PowerImageLoader.getInstance().registerImageLoader(
PowerImageNetworkLoader(this.applic ationContext), "network"
)
}
}
PowerImageNetworkLoader继承自PowerImageLoaderProtocol,图片的加载逻辑在其handleRequest
方法中:
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
进行处理之后回调PowerImageBaseRequest
的onLoadSuccess()
或者onLoadFailed(final String errMsg)
方法返回图片请求结果。
总结
power_image 是一个利用原生库加载/管理图片的比较适用于 Flutter/Native 混合开发的图片加载库,提供了 texture 和 ffi 两种加载图片的方式。
其中,texture 方案实际使用 Texture 组件展示图片;而 ffi 方案则只有图片获取在 Native 端,当使用ui.decodeImageFromPixels
方法从 Bitmap 内存指针创建ui.Image之后(根据阿里的描述,这里会发生一次内存拷贝,实际代码可以参考这里),剩下按照和 Flutter Image 类似的步骤展示图片。
根据官方的说法:
- Texture 适用于日常场景,优先选择;
- FFI 更适用于
- flutter <= 1.23.0-18.1.pre 版本中,在模拟器上显示图片
- 获取 ui.Image 图片数据
- flutter 侧解码,解码前的数据拷贝影响较小。
此外,根据官方power_image/issues/17的说法,“在 2.5.3 上 ffi 性能已经跟 texture
不相上下了”,而且 textrue 方案在 Android 上较大尺寸可能会 crash(flutter/flutter#92397),所以更推荐使用 ffi 方案。
参考资料
https://github.com/alibaba/power_image