Flutter图片加载方案分析之power_image

JI,XIAOYONG...大约 16 分钟flutter

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

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

power_image

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

无论是 Flutter Imageopen in new window 组件,还是第三方的extende_imageopen in new windowflutter_cached_nework_image都是在 Flutter 端加载解析图片,这些方案对一般纯 Flutter 开发的 APP 来说基本可以满足要求,但是对于大多数混合开发的 APP 来说,这些方案会在 Flutter 和 Native 同时存在两份图片资源造成内存浪费,此外根据贝壳的分析open in new window,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

class PowerImageState extends State<PowerImage> {
  
  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

class PowerTextureImage extends StatefulWidget {
  const PowerTextureImage({...}):super(key: key);

  final PowerTextureImageProvider provider;

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

class PowerTextureState extends State<PowerTextureImage> {
  
  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;

  
  PowerExteralState createState() => PowerExteralState();
}

class PowerExteralState extends State<PowerExternalImage> {
  
  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 组件一致。

// _ImageExtState.build
  
  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();
    }
  }

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

PowerTextureImageProvider

class PowerTextureImageProvider extends PowerImageProvider {
  PowerTextureImageProvider(PowerImageRequestOptions options) : super(options);

  
  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);
  }

  
  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);

  
  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 Imageopen in new window中分析的加载大量高清网图会出现的内存爆炸,这是因为虽然在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表示获取图片的方式(比如networknativeAssetfileasset等);
  • 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);
    };
  }

  
  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;
  }

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

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

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

  
  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();
        }
    }
}

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

public abstract class PowerImageBaseRequest {
    private void performLoadImage() {
        // 获取图片
        PowerImageLoader.getInstance().handleRequest(
                imageRequestConfig,
                new PowerImageLoaderProtocol.PowerImageResponse() {
                    
                    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()方法正是调用他们获取图片。

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进行处理之后回调PowerImageBaseRequestonLoadSuccess()或者onLoadFailed(final String errMsg)方法返回图片请求结果。

总结

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

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

根据官方的说法:

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

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

参考资料

https://github.com/alibaba/power_imageopen in new window

Flutter 图片库高燃新登场open in new window

闲鱼 Flutter 图片框架架构演进(超详细)open in new window

Flutter 图片内存优化实践open in new window

https://github.com/alibaba/power_image/issues/17open in new window

ui.decodeImageFromPixels分析open in new window

文章标题:《Flutter图片加载方案分析之power_image》
本文作者: JI,XIAOYONG
发布日期: 2022年8月2日
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/727d7800.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.1