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
