文章目錄
- 背景
- 如何接入Hybrid composition
- 1. 新建原生工程和Flutter Module
- 2. 新建native_view_example.dart檔案
- 3. 在main.dart檔案中顯示
- 4. 新建NativeView
- 5. 新建NativeViewFactory
- 6. 注冊平臺視圖
- Hybrid Composition原理分析
- FlutterImageView分析
- PlatformViewLink分析
- PlatformViewController分析
- AndroidViewSurface分析
- PlatformView創建程序的時序圖
- PlatformViewLayer分析
- FlutterJNI.onDisplayPlatformView分析
背景
平臺整合是指平臺視圖允許在 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 作業原理就大致梳理完成了,
如果有錯誤,還希望指出,有問題多多交流,多謝,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/344292.html
標籤:其他
