Flutter原理平台视图整合Hybrid composition分析
Posted 牧羊人.阿标
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter原理平台视图整合Hybrid composition分析相关的知识,希望对你有一定的参考价值。
背景
平台整合是指平台视图允许在 Flutter 应用程序中嵌入原生视图,可以将变换、剪辑和不透明度等应用到 Dart 的原生视图。例如,这允许您通过使用平台视图直接在 Flutter 应用程序中使用来自 android 和 ios SDK 的原生 百度,高德地图等。下面我们一起来看看如何在 Flutter 应用程序中托管您自己的原生视图,以及其中的原理深入分析。
目前Flutter支持两种模式:
-
Virtual displays
虚拟显示,在Flutter 1.20之前需要将AndroidView绘制到VirtualDisplays中,然后Flutter Engine通过Surface获取到绘制的画面,显示在Flutter UI上面。这种方式的弊端就是在于Flutter UI与原生的View在各种时间交互上存在诸多问题。
-
Hybrid composition
混合组合,需要在Flutter1.22之后(推荐使用1.22.2版本)。它与VirtualDispalys不同,它是通过将Flutter UI分为两个纹理来组合完成,一个用于显示平台视图,一个用于显示FLutterUI。这样的优点就是平台视图可以直接参与到Flutter的UI层次结构中去。
本文主要是介绍分析Hybrid composition这种模式。
如何接入Hybrid composition
1. 新建原生工程和Flutter Module
- 新建原生宿主工程’‘hello_flutter_composition’’
- 打开原生宿主工程,通过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,其中surfaceFactory
和 onCreatePlatformView
仅在此widget的状态被初始化或 viewType
更改时被调用。
PlatformViewSurfaceFactory
: 工厂类,返回一个_surface,实际上也是一个widget,Android平台返回的是:AndroidViewSurfaceCreatePlatformViewCallback
: 返回的其实是通过PlatformViewsService.initSurfaceAndroidView
创建的一个PlatformViewController,通过它可以控制单个平台视图的界面,并与平台视图交互。
下面来逐一分析下AndroidViewSurface
和PlatformViewController
首先来看看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 的合成器、触摸和语义子系统集成。合成器集成是通过PlatformViewRenderBox
将 PlatformViewLayer
添加到层树来完成的。PlatformViewLayer就是一个平台视图的合成层,是通过PlatformViewRenderBox
在paint时候,添加到PaintingContext
,Flutter中的Layer
后面再花单独文章来分析。
关于Hybrid composition的flutter framework层大致流程到这里介绍完毕了
我们这里再抛出一个疑问,在Hybrid Composition模式下,FlutterImageView是如何工作的呢?也就是说我们在flutterUI中创建了 PlatformViewLink,这时候引擎又是如何知道需要创建哪个PlatformView的呢?他们又是如何进行关联的呢?
PlatformView创建过程的时序图
代码看起来比较枯燥,这里直接画了个时序图
这样看流程比较清晰,这里大流程分为两步:
-
创建PlatformView
_platformViewLinkState是什么相信学Flutter应该都知道,在它的
initState
方法里面间接调用到了SurfaceAndroidViewController
的create方法,在调用到_sendCreateMessage
方法,在_sendCreateMessage
方法内部,发送消息到Flutter Engine层PlatformViewsChannel.java的create方法。Flutter Engine通过dart端口传递过来的参数最终由PlatformViewsController的createAndroidViewForPlatformView
方法创建PlatformView。 -
创建PlatformViewLayer
引擎创建完PlatformView以后,会触发
SurfaceAndroidViewController
的PlatformViewCreatedCallback
回调,间接触发了_PlatformViewLinkState
的setState
方法,最后通过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);
}
可以看到它直接调用到了PlatformViewsController
的onDisplayPlatformView
方法:
/**
* 在当前帧中显示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分析的主要内容,如果未能解决你的问题,请参考以下文章