专栏首页玩转全栈5分钟彻底搞懂Flutter中PlatFormView与Texture
原创

5分钟彻底搞懂Flutter中PlatFormView与Texture

想要在flutter想显示原生的东东,大家知道,一般有两种方式,一种是PlatformView,另外一种是Texture(俗称外接纹理)。其中PlatformView区分Android和iOS,在Android平上上叫做 AndroidView,而在iOS平台,叫UIKitView。而今天,我要说的是,

PlatformView和Texture看似两种不同的方式,其实是一种方式。

PlatformView https://api.flutter.dev/flutter/widgets/AndroidView-class.html

主要适用于flutter中不太容易实现的widget(Native中已经很成熟,并且很有优势的View),如官方的WebView。感兴趣的化可以了解一下。

Texture Texture class - widgets library - Dart API

既然有PlatformView可以在flutter中显示原生的view,我们为什么还需要Texture,简单的来说,显示一个view,过于繁重了点,我们有可能只需要显示那个数据而已,我们知道,原生向flutter传递数据,我们可以使用消息通道,大家一定知道MethodChannel.Result也一定玩过

result.success(data);

但是,举个栗子,假如我们要发送拍照的图片和录像的视频数据到flutter那边,是否可以走这个方式呢,理论上是没啥问题的,但是,如果我们采用消息通道将录像时摄像头采集的每一帧图片都要从原生传递到Flutter中,这样做代价将会非常大,因为<u>将图像或视频数据通过消息通道实时传输必然会引起内存和CPU的巨大消耗</u>!为此,Flutter提供了一种基于Texture的图片数据共享机制。

然而,今天我要说的是,<u>PlatformView其实也是使用外接纹理的方式实现的</u>,如果你不信,那好把,我们一起拨开这层神秘面纱吧。

首先,我们看下flutter创建一个PlatformView做了些什么

为了减少叙述,这里直接拿AndroidView来分析

const AndroidView({
    Key key,
    @required this.viewType,
    this.onPlatformViewCreated,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    this.layoutDirection,
    this.gestureRecognizers,
    this.creationParams,
    this.creationParamsCodec,
  }) : assert(viewType != null),
       assert(hitTestBehavior != null),
       assert(creationParams == null || creationParamsCodec != null),
       super(key: key);

我们看到AndroidView的构造函数中要求传一个viewType,然后常见的是,还有一个onPlatformViewCreated的回调。传viewType是告知插件为我们创建原生那个NativeView,可以是ImageView,AudioVIew等等,这些都需要在插件初始化时注册。

public interface PlatformViewRegistry {

    boolean registerViewFactory(String viewTypeId, PlatformViewFactory factory);

接下来,我们关心onPlatformViewCreated,其回调参数是一个id,我们继续跟踪AndroidView的构造函数,往下面走,最后,我们会走到<u>platform_views.dart</u>中的_create方法

 Future<void> _create(Size size) async {
    final Map<String, dynamic> args = <String, dynamic>{
      'id': id,
      'viewType': _viewType,
      'width': size.width,
      'height': size.height,
      'direction': _getAndroidDirection(_layoutDirection),
    };
    if (_creationParams != null) {
      final ByteData paramsByteData = _creationParamsCodec.encodeMessage(_creationParams);
      args['params'] = Uint8List.view(
        paramsByteData.buffer,
        0,
        paramsByteData.lengthInBytes,
      );
    }
    _textureId = await SystemChannels.platform_views.invokeMethod('create', args);
    _state = _AndroidViewState.created;
    for (PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
      callback(id);
    }
  }

我们看到_textureId = await SystemChannels.platform_views.invokeMethod('create', args);这里是通过platform_views调用原生方法,返回了一个_textureId,那么,这就能证明是创建了一个纹理吗?不服气,继续往下追,看看platform_views。在system_channels.dart中

static const MethodChannel platform_views = MethodChannel(
    'flutter/platform_views',
    StandardMethodCodec(),
  );

注册名是flutter/platform_views,我们取原生成去找,看看这个MethodChannel的create方法究竟干了啥,在PlatformViewsChannel.java 我们看到 。(<u>注意,同目录包下有很多Channel,感兴趣的可以了解下</u>)

public PlatformViewsChannel(@NonNull DartExecutor dartExecutor) {
    channel = new MethodChannel(dartExecutor, "flutter/platform_views", StandardMethodCodec.INSTANCE);
    channel.setMethodCallHandler(parsingHandler);
  }

发现确实是他注册了这个消息通道,那么,create方法肯定是他来处理,看他怎么处理,最终,我们跟踪到这个方法

public long createPlatformView(@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
            ensureValidAndroidVersion();

            if (!validateDirection(request.direction)) {
                throw new IllegalStateException("Trying to create a view with unknown direction value: "
                    + request.direction + "(view id: " + request.viewId + ")");
            }

            if (vdControllers.containsKey(request.viewId)) {
                throw new IllegalStateException("Trying to create an already created platform view, view id: "
                    + request.viewId);
            }

            PlatformViewFactory viewFactory = registry.getFactory(request.viewType);
            if (viewFactory == null) {
                throw new IllegalStateException("Trying to create a platform view of unregistered type: "
                    + request.viewType);
            }

            Object createParams = null;
            if (request.params != null) {
                createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params);
            }

            int physicalWidth = toPhysicalPixels(request.logicalWidth);
            int physicalHeight = toPhysicalPixels(request.logicalHeight);
            validateVirtualDisplayDimensions(physicalWidth, physicalHeight);

            TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture();
            VirtualDisplayController vdController = VirtualDisplayController.create(
                    context,
                    accessibilityEventsDelegate,
                    viewFactory,
                    textureEntry,
                    physicalWidth,
                    physicalHeight,
                    request.viewId,
                    createParams,
                    (view, hasFocus) -> {
                        if (hasFocus) {
                            platformViewsChannel.invokeViewFocused(request.viewId);
                        }
                    }
            );

            if (vdController == null) {
                throw new IllegalStateException("Failed creating virtual display for a "
                    + request.viewType + " with id: " + request.viewId);
            }

            // If our FlutterEngine is already attached to a Flutter UI, provide that Android
            // View to this new platform view.
            if (flutterView != null) {
                vdController.onFlutterViewAttached(flutterView);
            }

            vdControllers.put(request.viewId, vdController);
            View platformView = vdController.getView();
            platformView.setLayoutDirection(request.direction);
            contextToPlatformView.put(platformView.getContext(), platformView);

            // TODO(amirh): copy accessibility nodes to the FlutterView's accessibility tree.

            return textureEntry.id();
        }

我们看到关键部分

TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture();
。。。。。。
return textureEntry.id();

最后确实是返回的是纹理 textureId,至此,我们也证实了PlatformView其实背后也是用到了外接纹理。

在看看NativeView是怎么呈现到Flutter这边的

我们看官方实现的视频播放器的源码,(嗯,视频播放器是使用外接纹理方式)plugins/VideoPlayer.java at master · flutter/plugins · GitHub 第166行,关键代码

surface = new Surface(textureEntry.surfaceTexture());
exoPlayer.setVideoSurface(surface);

这里,通过TextureRegistry.SurfaceTextureEntry 这个entry拿到的这个surfaceTexture,是塞给了一个Surface,然后exoPlayer视频播放器将一帧帧的数据画到Surface上,这样,就能够实现数据共享了,也就是说,flutter端通过entry的那个textureId,就能用Texture展示数据啦。

 const Texture({
    Key key,
    @required this.textureId,
  }) : assert(textureId != null),
       super(key: key);

那么,PlatformView呢,怎么做的呢?是否也用Surface包了一下,然后把View给Draw到这个包装Surface上呢?这是我们的猜测,具体是不是,还是要看看源码。

public static VirtualDisplayController create(
            Context context,
            AccessibilityEventsDelegate accessibilityEventsDelegate,
            PlatformViewFactory viewFactory,
            TextureRegistry.SurfaceTextureEntry textureEntry,
            int width,
            int height,
            int viewId,
            Object createParams,
            OnFocusChangeListener focusChangeListener
    ) {
        textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
        Surface surface = new Surface(textureEntry.surfaceTexture());
        DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);

        int densityDpi = context.getResources().getDisplayMetrics().densityDpi;
        VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
                "flutter-vd",
                width,
                height,
                densityDpi,
                surface,
                0
        );

同样的画面出现在了源码当中

textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
Surface surface = new Surface(textureEntry.surfaceTexture());

而Surface也交给了VirtualDisplay管理,猜的没错的化,display的时候,就是把NativeView给Draw到这个Surface上,于是,我们在Flutter那边就看到这个NativeView。

PlatformView上的点击事件是如何从FLutter传递到原生的

确实你肯定也会好奇,那点击事件通过FLutter这边传递到原生的呢,其实,背后的实现是通过消息通道,将点击事件发送过去。

Future<void> sendMotionEvent(AndroidMotionEvent event) async {
    await SystemChannels.platform_views.invokeMethod<dynamic>(
        'touch',
        event._asList(id),
    );
  }

然后,在原生的中,我们发现,他处理了touch事件。

switch (call.method) {
        case "create":
          create(call, result);
          break;
        case "dispose":
          dispose(call, result);
          break;
        case "resize":
          resize(call, result);
          break;
        case "touch":
          touch(call, result);
          break;
        case "setDirection":
          setDirection(call, result);
          break;
        case "clearFocus":
          clearFocus(call, result);
          break;
        default:
          result.notImplemented();
      }

最终会交给PlatformViewFactory创建的那个与你目前联系的view来处理点击事件。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 现有项目集成flutter排坑指南

    1、如果选择,stable,我们遇到的情况是,IOS上接入之后是跑不了的。切到master上就OK了。

    brzhang
  • Flutter单引擎和外接纹理内存优化探索之路

    今年九月初,王者人生Android端及iOS端正式接入flutter跨平台方案来提升开发效率。

    brzhang
  • flutter接入现有的app详细介绍

    接入的方式,我是参考的官方的介绍文档,我这里尝试的是android的接入方式,还算比较顺利。

    brzhang
  • 「数据架构」:为什么要为MDM构建业务用例?

    今天的IT预算主要花在了“保持灯亮”上。事实上,大约70%预算被用于维持和运行现有的能力,而只有30%被用于为业务提供新的功能。企业和IT部门需要找到解决问题的...

    首席架构师智库
  • 「主数据架构」14个主数据管理误区

    虽然理想情况下是针对“企业”(企业可能不是您的整个组织),但是MDM仍然必须交付应用程序,从而支持多个应用程序,并且在某种程度上能够成为真正的企业。

    首席架构师智库
  • 「数据架构」:主数据管理 (MDM)概览和为什么选择主数据管理

    主数据管理(MDM)是一种主动的整个企业“管理”数据的数据管理规程,而不是在每个交易系统中“维护”它。由于商业智能(BI)应用程序的普及,最近对MDM的关注持续...

    首席架构师智库
  • MySQL删除数据Delete 语句、Trunca…

    恶人自有恶人磨,如果数据库里面的数据有问题了,或者是有人捣乱,再或者就是您老人家看这条数据不爽,还有就是您想毁灭证据(其实总是会留下痕迹的)的时候,你就需要了...

    用户2192970
  • 「vue基础」Vue相关构建工具和基础插件简介

    像其他框架一样,Vue 的生态也有很多一系列的工具,通过工具,可以快速帮我们构建项目、发布项目、部署打包等,方便我们调试,避免不必要的Bug等。本篇文章我将重点...

    前端达人
  • 如何干掉开发人员——0代码开发

    视察也就罢了,你察你的,我干我的,只要不搞幺蛾子咱还是好朋友。然鹅,临下班了,业务小姐姐来了,她目光炯炯深情款款地向我走来了。

    yuanyi928
  • kafka Consumer — offset的控制

    在N久之前,曾写过kafka 生产者使用详解, 今天补上关于 offset 相关的内容。 那么本文主要涉及:

    solve

扫码关注云+社区

领取腾讯云代金券