Flutter Hybrid Composition混合圖層的原理分析

介紹

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

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

在此之前建議先了解使用一下,具體使用方法,可點(diǎn)擊下方鏈接:

官方使用文檔

此文將會橫跨 flutter、android和engine,我會盡量簡要以縮短篇幅。

你可能還需要對surface有一定的了解。

我們就從Flutter側(cè)開始吧。

Flutter

我們先來看一下Flutter 側(cè)的代碼:

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

其結(jié)構(gòu)圖如下:

image

有用戶定義一個(gè)viewType,并實(shí)現(xiàn)兩個(gè)回調(diào)。 而其本身是statefulWidget,所以我們直接看它的state類。

_PlatformViewLinkState

image

屬性比較簡單,兩個(gè)比較重要的是:

_controller 
_surface

我們從state的調(diào)用流程中,來了解一下這兩個(gè)屬性的創(chuàng)建。流程圖如下:

image

由上圖可以看到,最先執(zhí)行的時(shí)_initialize 這個(gè)方法:

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

此方法最終調(diào)用我們所實(shí)現(xiàn)的_onCreatePlatformView方法,實(shí)例化 PlatformViewController控制器。

一個(gè)接口,除了提供其所控制的原生viewId外,還分發(fā)指針事件

而在其后的build()方法中,我們調(diào)用所實(shí)現(xiàn)的_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,
    );
  }

至此,我們所實(shí)現(xiàn)的兩個(gè)方法:surfaceFactory和onCreatePlatformView均被調(diào)用了,我們調(diào)用按順序看一下內(nèi)部細(xì)節(jié)。

onCreatePlatformView()

                onCreatePlatformView: (PlatformViewCreationParams params){
                  return PlatformViewsService.initSurfaceAndroidView(id: params.id,
                      viewType: viewType,
                      layoutDirection: TextDirection.ltr,creationParams: creationParams,
                      creationParamsCodec: StandardMessageCodec())
                    ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
                    //最終調(diào)用了一個(gè)create方法
                    ..create();
                },

這里我們通過initSurfaceAndroidView 創(chuàng)建了一個(gè)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;
  }

它的結(jié)構(gòu)關(guān)系圖如下:

image

可以看到,綁定了一些channel、事件分發(fā)回調(diào)等,我們主要看顯示相關(guān)的。還記得上面我們調(diào)用的create方法嗎?

//創(chuàng)建一個(gè)android view
  Future<void> create() async {
    assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');

    await _sendCreateMessage();
    //更改狀態(tài)
    _state = _AndroidViewState.created;
    //調(diào)用一些回調(diào)
    for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
      callback(viewId);
    }
  }

它調(diào)用了 await _sendCreateMessage(); 這個(gè)方法,而這個(gè)方法則是在 SurfaceAndroidViewController中實(shí)現(xiàn)的:

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

這個(gè)方法最終會向原生端發(fā)送一條“create”指令,而這條指令則由 PlatformViewsChannel (這是一個(gè)系統(tǒng)級的channel) 的 create()方法來處理 :

        private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
         
         ------刪除一些代碼----
         
         ///封裝一個(gè)請求
         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, 會調(diào)用 createAndroidViewForPlatformView()這個(gè)方法,而這個(gè)方法的實(shí)現(xiàn)則在PlatformViewsController。

看類名也知道,用于管理 platformView 的

我們看一下createAndroidViewForPlatformView()的實(shí)現(xiàn):

        @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,這個(gè)id就是兩端確定view的標(biāo)志。

至此onCreatePlatformView()方法告一段落,我們接著看我們所實(shí)現(xiàn)的surfaceFactory 回調(diào)。

surfaceFactory

看起來是個(gè)吹風(fēng)機(jī),實(shí)際它是個(gè)刮胡刀。
而這個(gè)雖然名字有suraface,但是是個(gè)widget。
                surfaceFactory: (ctx,PlatformViewController controller){
                  return AndroidViewSurface(
                    controller: controller,
                    gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
                    hitTestBehavior: PlatformViewHitTestBehavior.opaque,
                  );
                },

我們直接創(chuàng)建了一個(gè)AndroidViewSurface,它繼承自PlatformViewSurface,與咱們分析相關(guān)的全在PlatformViewSurface里,所以我們直接看它:

image
一般 xxRenderObjectWidget 多用于自定義view,我們常用的大多數(shù)widget都是它的子類

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

更多關(guān)于這方面的知識,大家可以百度了解一下

按照上圖流程,可知最終創(chuàng)建一個(gè)PlatformViewRenderBox,并在其Paint方法中添加一個(gè)PlatformViewLayer:

  @override
  void paint(PaintingContext context, Offset offset) {
    assert(_controller.viewId != null);
    ///創(chuàng)建一個(gè)layer 添加到layerTree上
    context.addLayer(PlatformViewLayer(
      rect: offset & size,
      viewId: _controller.viewId,
    ));
  }

先暫停一下, 從這里我們可以看到,我們在頁面中創(chuàng)建的AndroidSurafeView,其實(shí)內(nèi)部啥都沒有,只是在 layer樹上加了一個(gè)rect大小的layer,并且 帶一個(gè)和原生端相關(guān)的viewId。

我們繼續(xù)看PlatformViewLayer

PlatformViewLayer

image

這里簡單說一下,當(dāng)flutter 的widget構(gòu)建時(shí),最終會生成layer tree,并且最終生成scene發(fā)送到engine進(jìn)行渲染。

layer添加到scene時(shí),就會調(diào)用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,
    );
  }

進(jìn)一步來看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';

這里直接調(diào)用了 native 方法,并將viewId傳過去。

停頓梳理下一下

到這里我們先回顧一下:

目前我們已經(jīng)通知了安卓端保存了我們的viewId,還有一些繪制參數(shù)。
flutter端,我們也向layerTree里添加了一個(gè)Layer,雖然沒有什么繪制內(nèi)容
(實(shí)際上還綁定了事件、焦點(diǎn)回調(diào)啥的)

ok,現(xiàn)在我們看一下native方法 :SceneBuilder_addPlatformView

Engine

你如果感興趣的話,可以clone一份 engine的代碼

由上面可知,最終會調(diào)用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));
}

此方法會創(chuàng)建一個(gè)指向PlatformViewLayer類型的layer指針(帶著view的id),并添加到layer_stack_中,這里我們只看PlatformViewLayer。

在進(jìn)行 paint以及CompositeEmbeddedView 之前,會先preRoll操作 :

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

實(shí)際會調(diào)用AndroidExternalViewEmbedder的PrerollCompositeEmbeddedView這個(gè)方法,其實(shí)現(xiàn):

AndroidExternalViewEmbedder 這個(gè)類將回通過PlatformViewAndroidJNI 調(diào)用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());
  
  ///拿到一個(gè)指向SkPictureRecorder的指針
  auto picture_recorder = std::make_unique<SkPictureRecorder>();
  ///開始錄制繪制命令
  picture_recorder->beginRecording(SkRect::Make(frame_size_), &rtree_factory);
  ///將上面的recoder收集起來,因?yàn)榭赡苡泻芏鄓iew
  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相關(guān)參數(shù),大小啊、位置啥的
  view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get()));
}

到這里,我們的layer就添加完成,并且準(zhǔn)備好錄制繪制指令了,而此時(shí)我們發(fā)現(xiàn),原生view貌似還沒有調(diào)用過,即,得不到原生view的繪制指令。

這時(shí)就要看 rasterizer 這個(gè)類了。

skia的繪制會經(jīng)過pathGeneration、rasterizer、shading和transfer

其方法:

 ///代碼較多,我以偽代碼來概括
RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree){
    -----非常長------
    ///當(dāng)我們使用了 hybird composition時(shí)
    if (external_view_embedder_){
        ///將會進(jìn)入這里
        external_view_embedder_->BeginFrame
    }
    
    /.../
    
    if (external_view_embedder_){
        ///將會進(jìn)入這里
        external_view_embedder_->SubmitFrame
    }
    
    /.../

    
}

這里,我們只看SubmitFrame方法,它的實(shí)現(xiàn)在 AndroidExternalViewEmbedder::SubmitFrame 中:

///這個(gè)方法很長
///還是用偽代碼來概括

void AndroidExternalViewEmbedder::SubmitFrame(...){

    ///算出 flutterUI和 原生view的相交區(qū)域
    
    /// 錄制背景繪制指令
    ///提交一下
    
    ///這里是真代碼
    ///開始原生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 是一個(gè)指針,指向PlatformViewAndroidJNI

這個(gè)方法最終會調(diào)用 安卓端的 onDisplayPlatformView方法。

Android

上面的方法,會進(jìn)入到 FlutterJNI.Java類,并調(diào)用它的onDisplayPlatformView方法

FlutterJNI.onDisplayPlatformView

我們看一下onDisplayPlatformView 這個(gè)方法

  @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,并調(diào)用它的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);
  }

關(guān)注,第一步initializePlatformViewIfNeeded 這個(gè)方法

  private void initializePlatformViewIfNeeded(int viewId) {
  
    ----刪除一部分代碼----
    ///這個(gè) 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);
    ///設(shè)置寬高
    platformView.setLayoutParams(layoutParams);
    ///切換到前臺
    platformView.bringToFront();
    currentFrameUsedPlatformViewIds.add(viewId);

通過這一步,我們的原生view就會被切換到前臺,之后便可以向engine提供繪制指令了。

至此,整個(gè)Hybrid Composition 的繪制工作流程就大致梳理完成了,謝謝大家的閱讀。

如果有錯(cuò)誤,請指出,非常感謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容