Flutter实践深入——平台整合Hybrid composition分析

Posted 牧羊人.阿标

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter实践深入——平台整合Hybrid composition分析相关的知识,希望对你有一定的参考价值。

背景

平台整合是指平台视图允许在 Flutter 应用程序中嵌入原生视图,可以将变换、剪辑和不透明度等应用到 Dart 的原生视图。例如,这允许您通过使用平台视图直接在 Flutter 应用程序中使用来自 androidios SDK 的原生 百度,高德地图等。下面我们一起来看看如何在 Flutter 应用程序中托管您自己的原生视图,以及其中的原理深入分析。

目前Flutter支持两种模式:

  1. Virtual displays

    虚拟显示,在Flutter 1.20之前需要将AndroidView绘制到VirtualDisplays中,然后Flutter Engine通过Surface获取到绘制的画面,显示在Flutter UI上面。这种方式的弊端就是在于Flutter UI与原生的View在各种时间交互上存在诸多问题。

  2. Hybrid composition

    混合组合,需要在Flutter1.22之后(推荐使用1.22.2版本)。它与VirtualDispalys不同,它是通过将Flutter UI分为两个纹理来组合完成,一个用于显示平台视图,一个用于显示FLutterUI。这样的优点就是平台视图可以直接参与到Flutter的UI层次结构中去。

本文主要是介绍分析Hybrid composition这种模式。

如何接入Hybrid composition

1. 新建原生工程和Flutter Module

  1. 新建原生宿主工程’‘hello_flutter_composition’’
  2. 打开原生宿主工程,通过AS新建Flutter Module:’‘flutter_module_composition’’

新建完成之后,宿主工程settings.gradle和app/build.gradle会自动加入 flutter_module_composition和flutter_module_composition/Flutter的导入。如图:

2. 新建native_view_example.dart文件

在flutter_module_composition工程lib目录下新建native_view_example.dart文件

添加代码如下:

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

class NativeViewExample extends StatefulWidget {
  const NativeViewExample({Key key}) : super(key: key);

  @override
  _NativeViewExampleState createState() => _NativeViewExampleState();
}

class _NativeViewExampleState extends State<NativeViewExample> {
  @override
  Widget build(BuildContext context) {
    // This is used in the platform side to register the view.
    final String viewType = 'platform-view-type';
    // Pass parameters to the platform side.
    final Map<String, dynamic> creationParams = <String, dynamic>{};

    return PlatformViewLink(
      viewType: viewType,
      surfaceFactory:
          (BuildContext context, PlatformViewController controller) {
        return AndroidViewSurface(
          controller: controller as AndroidViewController,
          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(),
          onFocus: () {
            params.onFocusChanged(true);
          } ,
        )
          ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
          ..create();
      },
    );
  }
}

3. 在main.dart文件中显示

在main.dart中添加上面新建widget显示

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            // 新建显示
            Container(
              padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10),
              width: 130.0,
              height: 100.0,
              child: NativeViewExample(),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

到了这里,flutter代码基本上修改完毕

再来看看原生工程的修改。

4. 新建NativeView

在flutter_module_composition/.android/Flutter下,新建NativeView,实现PlatformView接口,如:

package com.hb.flutter;
//省略各种导包...

public class NativeView implements PlatformView {
    @NonNull
    private final TextView textView;

    NativeView(@NonNull Context context, int id, @Nullable Map<String, Object> creationParams) {
        textView = new TextView(context);
        textView.setTextSize(25);
        textView.setGravity(Gravity.CENTER);
        textView.setBackgroundColor(Color.rgb(100,200,200));
        textView.setText("native view");
    }

    @NonNull
    @Override
    public View getView() {
        return textView;
    }

    @Override
    public void dispose() {
    }

}

5. 新建NativeViewFactory

还需要创建一个工厂类来创建之前创建的实例 NativeView

package com.hb.flutter;
//省略各种导包...

public class NativeViewFactory extends PlatformViewFactory {
    @NonNull
    private final BinaryMessenger messenger;
    @NonNull private final View containerView;

    public NativeViewFactory(@NonNull BinaryMessenger messenger, @NonNull View containerView) {
        super(StandardMessageCodec.INSTANCE);
        this.messenger = messenger;
        this.containerView = containerView;
    }

    @NonNull
    @Override
    public PlatformView create(@NonNull Context context, int id, @Nullable Object args) {
        final Map<String, Object> creationParams = (Map<String, Object>) args;
        return new NativeView(context, id, creationParams);
    }
}

最后,注册平台视图。这可以在应用程序或插件中完成。

6. 注册平台视图

在宿主工程里面新建平台视图,修改MainActivity方法如下

public class MainActivity extends FlutterActivity {
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        flutterEngine
                .getPlatformViewsController()
                .getRegistry()
                .registerViewFactory("platform-view-type", new NativeViewFactory(null, null));
    }
}

最后效果如图:

大功告成,中间显示的’Rendered on a native Android view’就是来着Android的平台视图。

我们来看一下Flutter Inspector里面视图层次展示图片:

NativeViewExample,PlatformViewLink,AndroidViewSurface就是我们展示之后层次结构。

下面在详细分析。

Hybrid Composition原理分析

FlutterImageView分析

要分析Hybrid composition不得不先了解一下:FlutterImageView。

FlutterImageView并不是一个真正的ImageView

事实上Hybrid composition上混合原生控件所需的突出合成都是通过FlutterImageView来实现的。FlutterImageView原版就是一个原生的View,我们来看源码:

// FlutterImageView 适用于开发人员需要渲染 Flutter UI,但也需要渲染交互式 io.flutter.plugin.platform.PlatformView 的情况。
public class FlutterImageView extends View implements RenderSurface {
  @NonNull private ImageReader imageReader;
  @Nullable private Queue<Image> imageQueue;
  @Nullable private Image currentImage;
  @Nullable private Bitmap currentBitmap;
  @Nullable private FlutterRenderer flutterRenderer;

  //从imageRender读取bitmap绘制到canvas上
  @Override
  protected void onDraw(Canvas cavas) {
    super.onDraw(canvas);

    if (!imageQueue.isEmpty()) {
      if (currentImage != null) {
        currentImage.close();
      }
      currentImage = imageQueue.poll();
      updateCurrentBitmap();
    }
    if (currentBitmap != null) {
      canvas.drawBitmap(currentBitmap, 0, 0, null);
    }
  }
}

源码基本上没什么可将,我们能看到FlutterImageView本质上是一个普通的原生View,它实现了RenderSurface接口,从而实现类似FlutterSurfaceView的部分能力。

下面来了解一下FlutterImageView中的几个关键类作用:ImageReader,Image,Bitmap

  • ImageReader:ImageReader 类允许应用程序直接访问渲染到 Surface 中的图像数据,图像数据封装在Image对象中,可以同时访问多个这样的对象,最多可达maxImages构造函数参数指定的数量。 通过其 Surface 发送到 ImageReader 的新图像将排队,直到通过 AcquireLatestImage 或 AcquireNextImage 调用访问。
  • Image:Image就是包含了ByteBuffers的像素数据
  • Bitmap:Bitmap就是将Image转化为可以绘制的位图

这样我们就能理解FlutterImageView,其实就是通过ImageReader读取Surface的数据,然后通过Bitmap绘制出来。Surface的数据从哪来呢?其实跟前面FlutterSurfaceView类似,都是从FlutterRender而来。


下面再来看Flutter侧,我们从上面Flutter Inspector视图层次入手,先来来分析PlatformViewLink

PlatformViewLink分析

我们首先来看看PlatformViewLink的一个结构图:

PlatformViewLink源码部分,看构造函数:

class PlatformViewLink extends StatefulWidget {
  const PlatformViewLink({
    Key? key,
    required PlatformViewSurfaceFactory surfaceFactory,
    required CreatePlatformViewCallback onCreatePlatformView,
    required this.viewType,
    }) : assert(surfaceFactory != null),
         assert(onCreatePlatformView != null),
         assert(viewType != null),
         _surfaceFactory = surfaceFactory,
         _onCreatePlatformView = onCreatePlatformView,
         super(key: key);

}

从代码可以看出,PlatformViewLink核心在于三个必选参数:surfaceFactory,onCreatePlatformView,viewType,其中surfaceFactoryonCreatePlatformView 仅在此widget的状态被初始化或 viewType 更改时被调用。

  • PlatformViewSurfaceFactory: 工厂类,返回一个_surface,实际上也是一个widget,Android平台返回的是:AndroidViewSurface
  • CreatePlatformViewCallback: 返回的其实是通过PlatformViewsService.initSurfaceAndroidView创建的一个PlatformViewController,通过它可以控制单个平台视图的界面,并与平台视图交互。

下面来逐一分析下AndroidViewSurfacePlatformViewController

首先来看看PlatformViewController

PlatformViewController分析

PlatformViewController是一个抽象类,字面意思就是平台视图的控制器,被PlatformViewSurface引用,用于与平台视图交互,我们来看类源码:

abstract class PlatformViewController {
	// 与控制器关联的视图ID,
  // viewId 应始终唯一且非负。 并且它不能为空。
  // viewId通过全局唯一的管理器PlatformViewsRegistry生成管理
  int get viewId;

  // 事件分发到平台视图
  Future<void> dispatchPointerEvent(PointerEvent event);

  // Disposes平台视图
  Future<void> dispose();

  // 清理平台视图的焦点
  Future<void> clearFocus();
}

AndroidViewSurface分析

我们再来看看AndroidViewSurface,见源码:

class AndroidViewSurface extends PlatformViewSurface {
  /// Construct an `AndroidPlatformViewSurface`.
  const AndroidViewSurface({
    Key? key,
    required AndroidViewController controller,
    required PlatformViewHitTestBehavior hitTestBehavior,
    required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
  }) : assert(controller != null),
       assert(hitTestBehavior != null),
       assert(gestureRecognizers != null),
       super(
          key: key,
          controller: controller,
          hitTestBehavior: hitTestBehavior,
          gestureRecognizers: gestureRecognizers);

  @override
  RenderObject createRenderObject(BuildContext context) {
    final PlatformViewRenderBox renderBox =
        super.createRenderObject(context) as PlatformViewRenderBox;

    (controller as AndroidViewController).pointTransformer =
        (Offset position) => renderBox.globalToLocal(position);

    return renderBox;
  }
}

AndroidViewSurface核心代码不多,我们在页面中创建的AndroidSurafeView,其实内部啥都没有。主要逻辑都在PlatformViewSurface类中,后面单独开文章分析。AndroidViewSurface的主要作用就是将 Android 视图与 Flutter 的合成器、触摸和语义子系统集成。合成器集成是通过PlatformViewRenderBoxPlatformViewLayer 添加到层树来完成的。PlatformViewLayer就是一个平台视图的合成层,是通过PlatformViewRenderBox在paint时候,添加到PaintingContext,Flutter中的Layer后面再花单独文章来分析。

关于Hybrid composition的flutter framework层大致流程到这里介绍完毕了

我们这里再抛出一个疑问,在Hybrid Composition模式下,FlutterImageView是如何工作的呢?也就是说我们在flutterUI中创建了 PlatformViewLink,这时候引擎又是如何知道需要创建哪个PlatformView的呢?他们又是如何进行关联的呢?

PlatformView创建过程的时序图

代码看起来比较枯燥,这里直接画了个时序图

这样看流程比较清晰,这里大流程分为两步:

  1. 创建PlatformView

    ​ _platformViewLinkState是什么相信学Flutter应该都知道,在它的initState方法里面间接调用到了SurfaceAndroidViewController的create方法,在调用到_sendCreateMessage方法,在_sendCreateMessage方法内部,发送消息到Flutter Engine层PlatformViewsChannel.java的create方法。Flutter Engine通过dart端口传递过来的参数最终由PlatformViewsController的createAndroidViewForPlatformView方法创建PlatformView。

  2. 创建PlatformViewLayer

    ​ 引擎创建完PlatformView以后,会触发SurfaceAndroidViewControllerPlatformViewCreatedCallback回调,间接触发了_PlatformViewLinkStatesetState方法,最后通过AndroidViewSurface.createRenderObject方法创建并添加PlatformViewLayer到flutter layer tree。

讲到这里我们再稍微来看看PlatformViewLayer是如何工作的

PlatformViewLayer分析

先开看看PlatformViewLayer源码:

class PlatformViewLayer extends Layer {
  @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,
    );
  }
}

从源码看到PlatformViewLayer代码很简单,直接调用到了ScenBuilder.addPlatformView方法,我们继续看addPlatformView:

  void addPlatformView(
    int viewId, {
    Offset offset = Offset.zero,
    double width = 0.0,
    double height = 0.0,
  }) {
    _addPlatformView(offset.dx, offset.dy, width, height, viewId);
  }

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

addPlatformView见名知意,主要作用就是向scene添加平台视图。看源码addPlatformView方法直接调用到了SceneBuilder_addPlatformView这个native方法。

这里省略若干的native c++的跟踪…

FlutterJNI.onDisplayPlatformView分析

SceneBuilder_addPlatformView这个方法最终会调用到FlutterJNI.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);
  }

可以看到它直接调用到了PlatformViewsControlleronDisplayPlatformView方法:

  /**
   * 在当前帧中显示PlatformView的时候调用,每一帧开始都会走这个流程
   */
  public void onDisplayPlatformView(
      int viewId,
      int x,
      int y,
      int width,
      int height,
      int viewWidth,
      int viewHeight,
      FlutterMutatorsStack mutatorsStack) {
    //步骤1,创建并添加lutterImageView到FlutterView,FlutterImageView分析可以见上面分析。
    initializeRootImageViewIfNeeded();
    //步骤2,如果FlutterMutatorView为空的话,创建FlutterMutatorView,并将之前创建好的PlatformView添加到FlutterMutatorView中。再调用FlutterView的addView原生方法将FlutterMutatorView添加到FlutterView中。
    //这样原生的PlatformView就显示到了FlutterView
    initializePlatformViewIfNeeded(viewId);
    //步骤3,设置FlutterMutatorView大小,并且显示到前台
    final FlutterMutatorView parentView = platformViewParent.get(viewId);
    //将必要的参数传递给视图,以便它可以将正确的更改应用于其子项。在FlutterMutatorView内部其实也是通过原生的draw方法将原生PlatformView的绘制边界绘制出来。
    parentView.readyToDisplay(mutatorsStack, x, y, width, height);
    parentView.setVisibility(View.VISIBLE);
    parentView.bringToFront();

    final FrameLayout.LayoutParams layoutParams =
        new FrameLayout.LayoutParams(viewWidth, viewHeight);
    final View view = platformViews.get(viewId).getView();
    if (view != null) {
      view.setLayoutParams(layoutParams);
      view.bringToFront();
    }
    currentFrameUsedPlatformViewIds.add(viewId);
  }

讲到这里我们有必要再来了解一下FlutterMutatorView, FlutterMutatorView内部维护着一个FlutterMutatorsStack,而FlutterMutatorsStack内部维护着一个FlutterMutator列表,简单理解来说一个FlutterMutator代表一个变换信息,比如说裁剪,变换等,一个FlutterMutatorsStack对应多种变换信息,应用于一个PlatformView的显示。


好了降到这里,对Hybrid Composition 工作原理就大致梳理完成了。

如果有错误,还希望指出,有问题多多交流,多谢。

以上是关于Flutter实践深入——平台整合Hybrid composition分析的主要内容,如果未能解决你的问题,请参考以下文章

爱奇艺 Flutter 跨平台 Hybrid 实践

爱奇艺开播助手Flutter跨平台Hybrid实践

Hybrid App 开发实践总结

Flutter原理平台视图系列问题分析

Flutter原理平台视图系列问题分析

Flutter性能优化在携程酒店的实践