Flutter——Hybrid Composition混合图层的原理分析

·  阅读 1320

介绍

在 Flutter 1.20, 官方推出了Hybrid Composition,此方法仿照ios端的platform view 在flutter中的显示方式,相较于原来的AndroidView性能要更好。

此文将分析Hybrid Composition 在绘制方面的工作流程。

在此之前建议先了解使用一下,具体使用方法,可点击下方链接:

官方使用文档

此文将会横跨 flutter、android和engine,我会尽量简要以缩短篇幅。

你可能还需要对surface有一定的了解。
复制代码

我们就从Flutter侧开始吧。

Flutter

我们先来看一下Flutter 侧的代码:

PlatformViewLink(
                viewType: viewType,
                surfaceFactory: (ctx,PlatformViewController controller){
                  return AndroidViewSurface(
                    controller: controller,
                    gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
                    hitTestBehavior: PlatformViewHitTestBehavior.opaque,
                  );
                },
                onCreatePlatformView: (PlatformViewCreationParams params){
                  return PlatformViewsService.initSurfaceAndroidView(id: params.id,
                      viewType: viewType,
                      layoutDirection: TextDirection.ltr,creationParams: creationParams,
                      creationParamsCodec: StandardMessageCodec())
                    ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
                    ..create();
                },
              )
复制代码

PlatformViewLink

其结构图如下:

有用户定义一个viewType,并实现两个回调。 而其本身是statefulWidget,所以我们直接看它的state类。

_PlatformViewLinkState

属性比较简单,两个比较重要的是:

_controller 
_surface
复制代码

我们从state的调用流程中,来了解一下这两个属性的创建。流程图如下:

由上图可以看到,最先执行的时_initialize 这个方法:

  void _initialize() {
   _id = platformViewsRegistry.getNextPlatformViewId();
   _controller = widget._onCreatePlatformView(
     PlatformViewCreationParams._(
       id: _id,
       viewType: widget.viewType,
       onPlatformViewCreated: _onPlatformViewCreated,
       onFocusChanged: _handlePlatformFocusChanged,
     ),
   );
 }
复制代码

此方法最终调用我们所实现的_onCreatePlatformView方法,实例化 PlatformViewController控制器。

一个接口,除了提供其所控制的原生viewId外,还分发指针事件
复制代码

而在其后的build()方法中,我们调用所实现的_surfaceFactory方法,初始化了_surface:

  @override
  Widget build(BuildContext context) {
    if (!_platformViewCreated) {
      return const SizedBox.expand();
    }
    _surface ??= widget._surfaceFactory(context, _controller);
    return Focus(
      focusNode: _focusNode,
      onFocusChange: _handleFrameworkFocusChanged,
      child: _surface,
    );
  }
复制代码

至此,我们所实现的两个方法:surfaceFactory和onCreatePlatformView均被调用了,我们调用按顺序看一下内部细节。

onCreatePlatformView()

                onCreatePlatformView: (PlatformViewCreationParams params){
                  return PlatformViewsService.initSurfaceAndroidView(id: params.id,
                      viewType: viewType,
                      layoutDirection: TextDirection.ltr,creationParams: creationParams,
                      creationParamsCodec: StandardMessageCodec())
                    ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
                    //最终调用了一个create方法
                    ..create();
                },
复制代码

这里我们通过initSurfaceAndroidView 创建了一个SurfaceAndroidViewController

  static SurfaceAndroidViewController initSurfaceAndroidView({
    @required int id,
    @required String viewType,
    @required TextDirection layoutDirection,
    dynamic creationParams,
    MessageCodec<dynamic> creationParamsCodec,
    VoidCallback onFocus,
  }) {
    assert(id != null);
    assert(viewType != null);
    assert(layoutDirection != null);
    assert(creationParams == null || creationParamsCodec != null);

    final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
      viewId: id,
      viewType: viewType,
      layoutDirection: layoutDirection,
      creationParams: creationParams,
      creationParamsCodec: creationParamsCodec,
    );

    _instance._focusCallbacks[id] = onFocus ?? () {};
    return controller;
  }
复制代码

它的结构关系图如下:

可以看到,绑定了一些channel、事件分发回调等,我们主要看显示相关的。还记得上面我们调用的create方法吗?

//创建一个android view
  Future<void> create() async {
    assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');

    await _sendCreateMessage();
	//更改状态
    _state = _AndroidViewState.created;
    //调用一些回调
    for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
      callback(viewId);
    }
  }
复制代码

它调用了 await _sendCreateMessage(); 这个方法,而这个方法则是在 SurfaceAndroidViewController中实现的:

  @override
  Future<void> _sendCreateMessage() {
    final Map<String, dynamic> args = <String, dynamic>{
      'id': viewId,
      'viewType': _viewType,
      'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
      'hybrid': true,
    };
    if (_creationParams != null) {
      final ByteData paramsByteData =
          _creationParamsCodec.encodeMessage(_creationParams);
      args['params'] = Uint8List.view(
        paramsByteData.buffer,
        0,
        paramsByteData.lengthInBytes,
      );
    }
    return SystemChannels.platform_views.invokeMethod<void>('create', args);
  }
复制代码

这个方法最终会向原生端发送一条“create”指令,而这条指令则由 PlatformViewsChannel (这是一个系统级的channel) 的 create()方法来处理 :

        private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
         
         ------删除一些代码----
         
         ///封装一个请求
         PlatformViewCreationRequest request =
              new PlatformViewCreationRequest(
               ///flutter 那边定义好的id
                  (int) createArgs.get("id"),
                  ///长宽、类型等等
                  (String) createArgs.get("viewType"),
                  width,
                  height,
                  (int) createArgs.get("direction"),
                  createArgs.containsKey("params")
                      ? ByteBuffer.wrap((byte[]) createArgs.get("params"))
                      : null);
         
          try {
            // flutter 1.20 HybridComposition
            if (usesHybridComposition) {
              handler.createAndroidViewForPlatformView(request);
              result.success(null);
            } else {
            //老版本的 AndroidView 方式
              long textureId = handler.createVirtualDisplayForPlatformView(request);
              result.success(textureId);
            }
          } catch (IllegalStateException exception) {
            result.error("error", detailedExceptionString(exception), null);
          }
        }
复制代码

这里是新旧两条路,我们直接看 usesHybridComposition 为True, 会调用 createAndroidViewForPlatformView()这个方法,而这个方法的实现则在PlatformViewsController。

看类名也知道,用于管理 platformView 的
复制代码

我们看一下createAndroidViewForPlatformView()的实现:

        @Override
        public void createAndroidViewForPlatformView(
            @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
          // API level 19 is required for android.graphics.ImageReader.
          ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);
          platformViewRequests.put(request.viewId, request);
        }
复制代码

只是保存了一下我们上面封装的请求,并以ID作为key,这个id就是两端确定view的标志。

至此onCreatePlatformView()方法告一段落,我们接着看我们所实现的surfaceFactory 回调。

surfaceFactory

看起来是个吹风机,实际它是个刮胡刀。
而这个虽然名字有suraface,但是是个widget。
复制代码
                surfaceFactory: (ctx,PlatformViewController controller){
                  return AndroidViewSurface(
                    controller: controller,
                    gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
                    hitTestBehavior: PlatformViewHitTestBehavior.opaque,
                  );
                },
复制代码

我们直接创建了一个AndroidViewSurface,它继承自PlatformViewSurface,与咱们分析相关的全在PlatformViewSurface里,所以我们直接看它:

一般 xxRenderObjectWidget 多用于自定义view,我们常用的大多数widget都是它的子类

另外,常说的layerTree,就在PaintingContext里

更多关于这方面的知识,大家可以百度了解一下
复制代码

按照上图流程,可知最终创建一个PlatformViewRenderBox,并在其Paint方法中添加一个PlatformViewLayer:

  @override
  void paint(PaintingContext context, Offset offset) {
    assert(_controller.viewId != null);
    ///创建一个layer 添加到layerTree上
    context.addLayer(PlatformViewLayer(
      rect: offset & size,
      viewId: _controller.viewId,
    ));
  }
复制代码

先暂停一下, 从这里我们可以看到,我们在页面中创建的AndroidSurafeView,其实内部啥都没有,只是在 layer树上加了一个rect大小的layer,并且 带一个和原生端相关的viewId。

我们继续看PlatformViewLayer

PlatformViewLayer

这里简单说一下,当flutter 的widget构建时,最终会生成layer tree,并且最终生成scene发送到engine进行渲染。

layer添加到scene时,就会调用addToScene()方法:

  @override
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
    builder.addPlatformView(
      viewId,
      offset: shiftedRect.topLeft,
      width: shiftedRect.width,
      height: shiftedRect.height,
    );
  }
复制代码

进一步来看addPlatformView方法:

  void addPlatformView(
    int viewId, {
    Offset offset = Offset.zero,
    double width = 0.0,
    double height = 0.0,
  }) {
    assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
    _addPlatformView(offset.dx, offset.dy, width, height, viewId);
  }

  void _addPlatformView(double dx, double dy, double width, double height, int viewId)
      native 'SceneBuilder_addPlatformView';
复制代码

这里直接调用了 native 方法,并将viewId传过去。

停顿梳理下一下

到这里我们先回顾一下:

目前我们已经通知了安卓端保存了我们的viewId,还有一些绘制参数。
flutter端,我们也向layerTree里添加了一个Layer,虽然没有什么绘制内容
(实际上还绑定了事件、焦点回调啥的)
复制代码

ok,现在我们看一下native方法 :SceneBuilder_addPlatformView

Engine

你如果感兴趣的话,可以clone一份 engine的代码
复制代码

由上面可知,最终会调用SceneBuilder的addPlatformView方法:

void SceneBuilder::addPlatformView(double dx,
                                   double dy,
                                   double width,
                                   double height,
                                   int64_t viewId) {
  auto layer = std::make_unique<flutter::PlatformViewLayer>(
      SkPoint::Make(dx, dy), SkSize::Make(width, height), viewId);
  AddLayer(std::move(layer));
}
复制代码

此方法会创建一个指向PlatformViewLayer类型的layer指针(带着view的id),并添加到layer_stack_中,这里我们只看PlatformViewLayer。

在进行 paint以及CompositeEmbeddedView 之前,会先preRoll操作 :

void PlatformViewLayer::Preroll(PrerollContext* context,
                                const SkMatrix& matrix) {
	------删除一些代码-----
  context->view_embedder->PrerollCompositeEmbeddedView(view_id_,
                                                       std::move(params));
}
复制代码

实际会调用AndroidExternalViewEmbedder的PrerollCompositeEmbeddedView这个方法,其实现:

AndroidExternalViewEmbedder 这个类将回通过PlatformViewAndroidJNI 调用android端的方法
复制代码
void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView(
    int view_id,
    std::unique_ptr<EmbeddedViewParams> params) {
  TRACE_EVENT0("flutter",
               "AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView");
	
    ///这里生成一颗R树,B树的升级版
  auto rtree_factory = RTreeFactory();
  view_rtrees_.insert_or_assign(view_id, rtree_factory.getInstance());
  
  ///拿到一个指向SkPictureRecorder的指针
  auto picture_recorder = std::make_unique<SkPictureRecorder>();
  ///开始录制绘制命令
  picture_recorder->beginRecording(SkRect::Make(frame_size_), &rtree_factory);
  ///将上面的recoder收集起来,因为可能有很多view
  picture_recorders_.insert_or_assign(view_id, std::move(picture_recorder));
  composition_order_.push_back(view_id);
  // Update params only if they changed.
  if (view_params_.count(view_id) == 1 &&
      view_params_.at(view_id) == *params.get()) {
    return;
  }
  ///保存了view相关参数,大小啊、位置啥的
  view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get()));
}
复制代码

到这里,我们的layer就添加完成,并且准备好录制绘制指令了,而此时我们发现,原生view貌似还没有调用过,即,得不到原生view的绘制指令。

这时就要看 rasterizer 这个类了。

skia的绘制会经过pathGeneration、rasterizer、shading和transfer
复制代码

其方法:

 ///代码较多,我以伪代码来概括
RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree){
	-----非常长------
    ///当我们使用了 hybird composition时
    if (external_view_embedder_){
    	///将会进入这里
        external_view_embedder_->BeginFrame
    }
    
    /.../
    
    if (external_view_embedder_){
    	///将会进入这里
        external_view_embedder_->SubmitFrame
    }
    
    /.../

    
}
复制代码

这里,我们只看SubmitFrame方法,它的实现在 AndroidExternalViewEmbedder::SubmitFrame 中:

///这个方法很长
///还是用伪代码来概括

void AndroidExternalViewEmbedder::SubmitFrame(...){

	///算出 flutterUI和 原生view的相交区域
    
    /// 录制背景绘制指令
    ///提交一下
    
    ///这里是真代码
    ///开始原生view的显示
    
      for (int64_t view_id : composition_order_) {
    SkRect view_rect = GetViewRect(view_id);
    const EmbeddedViewParams& params = view_params_.at(view_id);
    // Display the platform view. If it's already displayed, then it's
    // just positioned and sized.
    jni_facade_->FlutterViewOnDisplayPlatformView(
        view_id,             //
        view_rect.x(),       //
        view_rect.y(),       //
        view_rect.width(),   //
        view_rect.height(),  //
        params.sizePoints().width() * device_pixel_ratio_,
        params.sizePoints().height() * device_pixel_ratio_,
        params.mutatorsStack()  //
    );
    for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
      std::unique_ptr<SurfaceFrame> frame =
          CreateSurfaceIfNeeded(context,               //
                                view_id,               //
                                pictures.at(view_id),  //
                                overlay_rect           //
          );
      if (should_submit_current_frame) {
        frame->Submit();
      }
    }
  }


}


复制代码

此处,通过jni_facade_->FlutterViewOnDisplayPlatformView 方法,来显示原生view,

jni_facade 是一个指针,指向PlatformViewAndroidJNI
复制代码

这个方法最终会调用 安卓端的 onDisplayPlatformView方法。

Android

上面的方法,会进入到 FlutterJNI.Java类,并调用它的onDisplayPlatformView方法

FlutterJNI.onDisplayPlatformView

我们看一下onDisplayPlatformView 这个方法

  @UiThread
  public void onDisplayPlatformView(
      int viewId,
      int x,
      int y,
      int width,
      int height,
      int viewWidth,
      int viewHeight,
      FlutterMutatorsStack mutatorsStack) {
    ensureRunningOnMainThread();
    if (platformViewsController == null) {
      throw new RuntimeException(
          "platformViewsController must be set before attempting to position a platform view");
    }
    platformViewsController.onDisplayPlatformView(
        viewId, x, y, width, height, viewWidth, viewHeight, mutatorsStack);
  }
复制代码

可以看到,将viewId传给了controller,并调用它的onDisplayPlatformView方法:

  public void onDisplayPlatformView(
      int viewId,
      int x,
      int y,
      int width,
      int height,
      int viewWidth,
      int ViewHeight,
      FlutterMutatorsStack mutatorsStack) {
    initializeRootImageViewIfNeeded();
    /// 第一步
    initializePlatformViewIfNeeded(viewId);

    FlutterMutatorView mutatorView = mutatorViews.get(viewId);
    mutatorView.readyToDisplay(mutatorsStack, x, y, width, height);
    mutatorView.setVisibility(View.VISIBLE);
    mutatorView.bringToFront();
    ///第二步
    FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
    View platformView = platformViews.get(viewId);
    platformView.setLayoutParams(layoutParams);
    platformView.bringToFront();
    currentFrameUsedPlatformViewIds.add(viewId);
  }
复制代码

关注,第一步initializePlatformViewIfNeeded 这个方法

  private void initializePlatformViewIfNeeded(int viewId) {
  
    ----删除一部分代码----
	///这个 factory.create 熟悉不?
    ///如果不熟悉的话,可以去看一下文章开头的使用文档
    PlatformView platformView = factory.create(context, viewId, createParams);
    ///我们的原生view
    View view = platformView.getView();
    ///把我们的view和viewId 保存起来
    platformViews.put(viewId, view);

    FlutterMutatorView mutatorView =
        new FlutterMutatorView(context, context.getResources().getDisplayMetrics().density);
    mutatorViews.put(viewId, mutatorView);
    mutatorView.addView(platformView.getView());
    ((FlutterView) flutterView).addView(mutatorView);
  }
复制代码

接下来我们看第二步:

    FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
    View platformView = platformViews.get(viewId);
    ///设置宽高
    platformView.setLayoutParams(layoutParams);
    ///切换到前台
    platformView.bringToFront();
    currentFrameUsedPlatformViewIds.add(viewId);
复制代码

通过这一步,我们的原生view就会被切换到前台,之后便可以向engine提供绘制指令了。

至此,整个Hybrid Composition 的绘制工作流程就大致梳理完成了,谢谢大家的阅读。

如果有错误,请指出,非常感谢!

系列文章

Flutter——仿网易云音乐App(基础版)

实现网易云音乐的滑动冲突处理效果

Flutter自定义View——仿高德三级联动Drawer

Flutter 自定义View——仿同花顺自选股列表

Flutter入门练习——Evenet&Method Channel协作加载大图

Flutter&Bedrock框架——页面的局部刷新介绍

分类:
Android
标签: