10betFlutter platform view 使用篇

来源:http://www.chinese-glasses.com 作者:Web前端 人气:128 发布时间:2020-03-16
摘要:Native侧 这里我写了个小demo来验证下实际效果,demo的主要逻辑是以60FPS的帧率,渲染一个旋转的三角形到一个pixelBuffer映射的纹理上。然后每帧绘制完成之后,通知flutter侧来读取这个p

Native侧

这里我写了个小demo来验证下实际效果,demo的主要逻辑是以60FPS的帧率,渲染一个旋转的三角形到一个pixelBuffer映射的纹理上。然后每帧绘制完成之后,通知flutter侧来读取这个pixelBuffer对象去做渲染。

代码部分稍多一点,我们分两部分来说,下面是第一部分

图中红色块,是我们自己要编写的native代码,黄色是Flutter engine的内部代码逻辑。整体流程分为注册纹理,和整体的纹理渲染逻辑。

整体框架图Dart侧MapView StatefulWidget

dart侧声明一个widget,表明该widget实际渲染的是native提供的纹理

最后就是controller对象了,controller继承自FlutterPlatformView协议,工厂调用Controller对象来创建真正的view实例。

我们从上面的gif图上可以看到整个渲染过程是十分流畅的,最后看displayLink的帧率也能够达到60FPS。该demo是可以套用到其他的需要CPU与GPU共享内存的场景的。

QQMapViewFactory

从文档里面,我们了解到几个关键点:

args 对应dart侧UIKitView的creationParams参数

CVPixelBuffer格式分析

creationParams 这里是允许传递给native侧的初始化参数

通过来注册第一步的对象,获取一个Flutter纹理id

@interface QQMapViewController : NSObjectFlutterPlatformView// 略@end@implementation QQMapViewController- (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args registrar:(NSObjectFlutterPluginRegistrar *)registrar{ if (self = [super init]) { _mapView = [[QMapView alloc] initWithFrame:frame]; _mapView.delegate = self; [self mapArgs:args toView:_mapView]; NSString *channelName = [NSString stringWithFormat:@"qq_maps_%lld", viewId]; _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:registrar.messenger]; __weak __typeof__(self) weakSelf = self; [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { if (weakSelf) { [weakSelf onMethodCall:call result:result]; } }]; } return self;}- (void)mapArgs:(id _Nullable)args toView:(QMapView *)view{ if ([args isKindOfClass:[NSDictionary class]]  view != nil) { view.mapType = [args[@"mapType"] intValue];// 其他参数,略 }}- (nonnull UIView *)view { return _mapView;}

关键点在于,首先需要创建pixelBuffer对象,并分配内存。然后在native gl环境和flutter gl环境里面分别映射一个纹理对象。这样,在2个独立的gl环境里面,我们都有各自的纹理对象,但实际上其内存都被映射到同一个上。在实际的每一帧渲染流程里面,native环境做渲染到纹理,而flutter环境里面则是从纹理读取数据。

typedef void MapViewRegionDidChange();class MapViewController { final MethodChannel channel; final _MapViewState _googleMapState; MapViewRegionDidChange regionDidChange; MapViewController._( this.channel, this._googleMapState, ) { channel.setMethodCallHandler(_handleMethodCall); } static FutureMapViewController init( int id, _MapViewState mapViewState, ) async { assert(id != null); final MethodChannel channel = MethodChannel('qq_maps_$id'); return MapViewController._(channel, mapViewState); } Futuredynamic _handleMethodCall(MethodCall call) async { switch (call.method) { case 'map#regionDidChange': regionDidChange(); break; } } Futurevoid backToCurLocation() async { await channel.invokeMethod( 'map#backToCurLocation', ); } FutureList getRecentPoiList({String keyword = "大厦"}) async { final Map data = await channel.invokeMethod( 'map#getRecentPoiList', keyword, ); int result = data["result"]; if (result == 0) { // 定位成功 return data["poiList"]; } else { return List(); } }}

Flutter 外接纹理的原理

dart侧传递过来的初始化参数,mapArgs:toView方法里面我们传递给了mapview对象。

首先通过dart侧传递的id,找到先注册的,该flutterTexture是我们自己用native代码实现的,其核心是实现了方法

Flutter作为备受关注的跨平台的开发框架,长远来看,前景肯定是比较好的。

This function either creates a new or returns a cachedtexture object mapped to theand associated parameters. This operation creates a live binding between the image buffer and the underlying texture object. The EAGLContext associated with the cache may be modified to create, delete, or bind textures. When used as a source texture or, the image buffer must be unlocked before rendering. The source or render buffer texture should not be re-used until the rendering has completed. This can be guaranteed by calling.

第一部分很简单,我们主要关注如下点:

闲鱼这边的解决方案是修改了Flutter engine的代码,将Flutter的gl环境和native的gl环境通过ShareGroup来联通,避免2个环境的纹理传递还要去cpu内存绕一圈。此方案能够解决内存拷贝的性能问题,但暴露Flutter的gl环境,毕竟是一个存在风险的操作,给以后的Flutter渲染问题定位也增加了复杂度。

定义了一个create回调,参数是一个controller,这个controller其实就是和native侧做交互的对象,后面详细介绍

之前看了一篇闲鱼的文章《万万没想到——Flutter这样外接纹理》,了解到Flutter提供一种机制,可以将native的纹理共享给Flutter来进行渲染。但是,由于Flutter获取native纹理的数据类型是,导致native纹理需要经过GPU->CPU->GPU的转换过程消耗额外性能,这对于需要实时渲染的音视频类需求,是不可接受的。

然后,controller提供了2个方法,这两个方法都是直接桥接native侧的:

Demo演示

第三个注意点,就是接收native侧的事件回调,主要是通过channel的回调函数_handleMethodCall来统一处理的

一切问题的根源就在这里了:CVPixelBuffer。从上面flutter外接纹理的渲染流程来看,native纹理到flutter纹理的数据交互,是通过传递的,其参数就是。而前面咸鱼文章里面说的性能问题,就来自于纹理与之间的转换。

@interface MapviewPlugin : NSObjectFlutterPlugin@end@implementation MapviewPlugin+ (void)registerWithRegistrar:(NSObjectFlutterPluginRegistrar*)registrar { QQMapViewFactory *factory = [[QQMapViewFactory alloc] initWithRegistrar:registrar]; [registrar registerViewFactory:factory withId:@"qq_maps"];}@end

GPU访问的时候,该,不能够处于lock状态。

viewId 由于可能有多个地图组件同时展示,每个地图实例都有各自的viewId来区分

创建一个对象,实现协议,该对象用来管理具体的纹理数据

核心就在build这里了,Flutter提供了一个UIKitView(iOS侧,安卓对应的是AndroidView)的组件,这个组件就是桥接native view的关键,我们看看其参数。

那么,如果能够和OpenGL的纹理同享同一份内存拷贝,GPU -> CPU -> GPU的性能瓶颈,是否就能够迎刃而解了呢?其实我们看一下flutter engine里面利用CVPixelBuffer来创建纹理的方法,就能够得到答案:

注意一个tips:这个channel对象的name是qq_maps_$id,id是UIKitView的create回调带过来的,表示viewID。这就是说如果有多个地图实例的话,每一个地图实例都对应一个自己的channel,保证消息收发不会串掉。

所以这里也容易理解,GPU使用纹理的时候,其必然不能够同时被CPU操作。

onMethodCall dart侧发起的函数调用,首先会到这里,然后再分发给具体的实现函数。我们可以看到刚刚dart侧的2个接口(map#backToCurLocation和map#getRecentPoiList),在native侧具体是怎么实现的。

共享内存方案

MapviewPlugin

flutter engine调用拿到具体的纹理数据,然后交由底层进行GPU渲染

首先我们观察到,controller维护了一个channel对象,这个对象就负责收发native端的消息。

关键代码都添加了注释,这里就不分析了

MapViewController

既然了解到CVPixelBuffer对象,实际上是可以桥接一个OpenGL的纹理的,那我们的整体解决方案就水到渠成了,可以看看下面这个图

下面是dart侧最后一个类:controller。前面我们说过,组件使用者在创建MapView这个widget的时候,就能在onCreate回调拿到这个controller,然后后续就能够通过controller来与native做一些交互,比如说开启地图定位,搜索附近poi的列表等等。

所以,有没有一个完美、简便的方案呢?

第二部分就是和dart侧相关交互的代码了,基本和dart的controller代码相对应:

答案就是利用的共享内存机制。

在其基础组件还未完善与成熟之前,能够高效的复用现有的native组件,是比较合适的方案。官方提供了Plugin的方式,允许将一个成熟的native组件(比如mapview),封装成一个可用dart来操作的widget。本文以封装一个腾讯地图组件为例,介绍一下整个过程。具体也可以参照一下谷歌官方封装的地图组件google_maps_flutter

10bet,engine侧拿到layerTree,layerTree的节点负责外接纹理的渲染

本文由10bet发布于Web前端,转载请注明出处:10betFlutter platform view 使用篇

关键词:

上一篇:没有了

下一篇:没有了

最火资讯