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 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:
image
是 PowerTextureImageProvider 类型:采用 texture 模式展示图片,返回**PowerTextureImage ,最终会返回经过封装的 Texture **对象。
image
是**PowerExternalImageProvider 类型:采用 ffi 模式展示图片,返回 PowerExternalImage ,最终返回的是 RawImage **对象,和使用 Flutter Image 展示图片的流程一致。
其他类型,按照自定义的规则展示。
让我们来分别看一下**PowerTextureImage 和 PowerExternalImage **的实现:
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( image: widget.provider, frameBuilder: widget.frameBuilder, errorBuilder: widget.errorBuilder, width: widget.width, height: widget.height, fit: widget.fit, alignment: widget.alignment, 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: 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, 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 组件一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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' ]; bool? isMultiFrame = map['_multiFrame' ]; if (isMultiFrame == true ) { _completer! .addOnLastListenerRemovedCallback(() { scheduleMicrotask(() { PaintingBinding.instance!.imageCache!.evict(key); }); }); } _completer = null ; if (success != true ) { final PowerImageLoadException exception = PowerImageLoadException(nativeResult: map); PowerImageMonitor.instance().anErrorOccurred(exception); throw exception; } return createImageInfo(map); } catch (e) { scheduleMicrotask(() { PaintingBinding.instance!.imageCache!.evict(key); }); rethrow ; } finally { } }
在上面的分析中,我们得知,texture 和ffi 方案分别使用 ImageProvider 提供的 PowerImageInfo 中的int? textureId
和ui.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; 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.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.decodeImageFromPixels(pixels, width, height, pixelFormat, (ui.Image image) { ImageInfo imageInfo = PowerImageInfo(image: image); completer.complete(imageInfo); 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:
1 2 3 4 5 6 7 8 9 10 11 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(); } 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); channel.startImageRequests(<PowerImageRequest>[request]); PowerImageCompleter completer = PowerImageCompleter(); completer.request = request; completer.completer = Completer<Map >(); completers[request.uniqueKey()] = completer; return completer; } void onImageComplete(Map <dynamic , dynamic > map) async { String? uniqueKey = map['uniqueKey' ]; PowerImageCompleter? completer = completers.remove(uniqueKey); 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
表示获取图片的方式(比如network
,nativeAsset
,file
,asset
等);
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) { PowerImageLoader.instance.onImageComplete(event); }; } @override void setup() { startListening(); } StreamSubscription? startListening() { _subscription ??= eventChannel.receiveBroadcastStream().listen(onEvent); return _subscription; } Map <String , EventHandler?> eventHandlers = <String , EventHandler?>{}; 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 { } } 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' ); @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(); } 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 @Override public void onMethodCall(MethodCall call, Result result) { if ("startImageRequests" .equals(call.method)) { if (call.arguments instanceof List) { List arguments = (List) call.arguments; List results = PowerImageRequestManager.getInstance() .configRequestsWithArguments(arguments); result.success(results); 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)) { request = new PowerImageExternalRequest(arguments); } else if (RENDER_TYPE_TEXTURE.equals(renderType)) { 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" ); PowerImageBaseRequest request = requests.get (requestId); request.startLoading(); } } }
可见对于ffi 和texture 方案,分别涉及到**PowerImageExternalRequest 和 PowerImageTextureRequest 两个类。他们都继承自 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
方法也就是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()
方法正是调用他们获取图片。
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; } public void registerImageLoader(PowerImageLoaderProtocol loader, String imageType) { imageLoaders.put(imageType, loader); } @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.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
Flutter 图片库高燃新登场
闲鱼 Flutter 图片框架架构演进(超详细)
Flutter 图片内存优化实践
https://github.com/alibaba/power_image/issues/17
ui.decodeImageFromPixels分析