介紹
在 Flutter 1.20, 官方推出了Hybrid Composition,此方法仿照ios端的platform view 在flutter中的顯示方式,相較于原來的AndroidView性能要更好。
此文將分析Hybrid Composition 在繪制方面的工作流程。
在此之前建議先了解使用一下,具體使用方法,可點(diǎn)擊下方鏈接:
此文將會橫跨 flutter、android和engine,我會盡量簡要以縮短篇幅。
你可能還需要對surface有一定的了解。
我們就從Flutter側(cè)開始吧。
Flutter
我們先來看一下Flutter 側(cè)的代碼:
PlatformViewLink(
viewType: viewType,
surfaceFactory: (ctx,PlatformViewController controller){
return AndroidViewSurface(
controller: controller,
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())
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
)
PlatformViewLink
其結(jié)構(gòu)圖如下:
有用戶定義一個(gè)viewType,并實(shí)現(xiàn)兩個(gè)回調(diào)。 而其本身是statefulWidget,所以我們直接看它的state類。
_PlatformViewLinkState
屬性比較簡單,兩個(gè)比較重要的是:
_controller
_surface
我們從state的調(diào)用流程中,來了解一下這兩個(gè)屬性的創(chuàng)建。流程圖如下:
由上圖可以看到,最先執(zhí)行的時(shí)_initialize 這個(gè)方法:
void _initialize() {
_id = platformViewsRegistry.getNextPlatformViewId();
_controller = widget._onCreatePlatformView(
PlatformViewCreationParams._(
id: _id,
viewType: widget.viewType,
onPlatformViewCreated: _onPlatformViewCreated,
onFocusChanged: _handlePlatformFocusChanged,
),
);
}
此方法最終調(diào)用我們所實(shí)現(xiàn)的_onCreatePlatformView方法,實(shí)例化 PlatformViewController控制器。
一個(gè)接口,除了提供其所控制的原生viewId外,還分發(fā)指針事件
而在其后的build()方法中,我們調(diào)用所實(shí)現(xiàn)的_surfaceFactory方法,初始化了_surface:
@override
Widget build(BuildContext context) {
if (!_platformViewCreated) {
return const SizedBox.expand();
}
_surface ??= widget._surfaceFactory(context, _controller);
return Focus(
focusNode: _focusNode,
onFocusChange: _handleFrameworkFocusChanged,
child: _surface,
);
}
至此,我們所實(shí)現(xiàn)的兩個(gè)方法:surfaceFactory和onCreatePlatformView均被調(diào)用了,我們調(diào)用按順序看一下內(nèi)部細(xì)節(jié)。
onCreatePlatformView()
onCreatePlatformView: (PlatformViewCreationParams params){
return PlatformViewsService.initSurfaceAndroidView(id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,creationParams: creationParams,
creationParamsCodec: StandardMessageCodec())
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
//最終調(diào)用了一個(gè)create方法
..create();
},
這里我們通過initSurfaceAndroidView 創(chuàng)建了一個(gè)SurfaceAndroidViewController
static SurfaceAndroidViewController initSurfaceAndroidView({
@required int id,
@required String viewType,
@required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic> creationParamsCodec,
VoidCallback onFocus,
}) {
assert(id != null);
assert(viewType != null);
assert(layoutDirection != null);
assert(creationParams == null || creationParamsCodec != null);
final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
viewId: id,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
);
_instance._focusCallbacks[id] = onFocus ?? () {};
return controller;
}
它的結(jié)構(gòu)關(guān)系圖如下:
可以看到,綁定了一些channel、事件分發(fā)回調(diào)等,我們主要看顯示相關(guān)的。還記得上面我們調(diào)用的create方法嗎?
//創(chuàng)建一個(gè)android view
Future<void> create() async {
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
await _sendCreateMessage();
//更改狀態(tài)
_state = _AndroidViewState.created;
//調(diào)用一些回調(diào)
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(viewId);
}
}
它調(diào)用了 await _sendCreateMessage(); 這個(gè)方法,而這個(gè)方法則是在 SurfaceAndroidViewController中實(shí)現(xiàn)的:
@override
Future<void> _sendCreateMessage() {
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': _viewType,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
'hybrid': true,
};
if (_creationParams != null) {
final ByteData paramsByteData =
_creationParamsCodec.encodeMessage(_creationParams);
args['params'] = Uint8List.view(
paramsByteData.buffer,
0,
paramsByteData.lengthInBytes,
);
}
return SystemChannels.platform_views.invokeMethod<void>('create', args);
}
這個(gè)方法最終會向原生端發(fā)送一條“create”指令,而這條指令則由 PlatformViewsChannel (這是一個(gè)系統(tǒng)級的channel) 的 create()方法來處理 :
private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
------刪除一些代碼----
///封裝一個(gè)請求
PlatformViewCreationRequest request =
new PlatformViewCreationRequest(
///flutter 那邊定義好的id
(int) createArgs.get("id"),
///長寬、類型等等
(String) createArgs.get("viewType"),
width,
height,
(int) createArgs.get("direction"),
createArgs.containsKey("params")
? ByteBuffer.wrap((byte[]) createArgs.get("params"))
: null);
try {
// flutter 1.20 HybridComposition
if (usesHybridComposition) {
handler.createAndroidViewForPlatformView(request);
result.success(null);
} else {
//老版本的 AndroidView 方式
long textureId = handler.createVirtualDisplayForPlatformView(request);
result.success(textureId);
}
} catch (IllegalStateException exception) {
result.error("error", detailedExceptionString(exception), null);
}
}
這里是新舊兩條路,我們直接看 usesHybridComposition 為True, 會調(diào)用 createAndroidViewForPlatformView()這個(gè)方法,而這個(gè)方法的實(shí)現(xiàn)則在PlatformViewsController。
看類名也知道,用于管理 platformView 的
我們看一下createAndroidViewForPlatformView()的實(shí)現(xiàn):
@Override
public void createAndroidViewForPlatformView(
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
// API level 19 is required for android.graphics.ImageReader.
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);
platformViewRequests.put(request.viewId, request);
}
只是保存了一下我們上面封裝的請求,并以ID作為key,這個(gè)id就是兩端確定view的標(biāo)志。
至此onCreatePlatformView()方法告一段落,我們接著看我們所實(shí)現(xiàn)的surfaceFactory 回調(diào)。
surfaceFactory
看起來是個(gè)吹風(fēng)機(jī),實(shí)際它是個(gè)刮胡刀。
而這個(gè)雖然名字有suraface,但是是個(gè)widget。
surfaceFactory: (ctx,PlatformViewController controller){
return AndroidViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
我們直接創(chuàng)建了一個(gè)AndroidViewSurface,它繼承自PlatformViewSurface,與咱們分析相關(guān)的全在PlatformViewSurface里,所以我們直接看它:
一般 xxRenderObjectWidget 多用于自定義view,我們常用的大多數(shù)widget都是它的子類
另外,常說的layerTree,就在PaintingContext里
更多關(guān)于這方面的知識,大家可以百度了解一下
按照上圖流程,可知最終創(chuàng)建一個(gè)PlatformViewRenderBox,并在其Paint方法中添加一個(gè)PlatformViewLayer:
@override
void paint(PaintingContext context, Offset offset) {
assert(_controller.viewId != null);
///創(chuàng)建一個(gè)layer 添加到layerTree上
context.addLayer(PlatformViewLayer(
rect: offset & size,
viewId: _controller.viewId,
));
}
先暫停一下, 從這里我們可以看到,我們在頁面中創(chuàng)建的AndroidSurafeView,其實(shí)內(nèi)部啥都沒有,只是在 layer樹上加了一個(gè)rect大小的layer,并且 帶一個(gè)和原生端相關(guān)的viewId。
我們繼續(xù)看PlatformViewLayer
PlatformViewLayer
這里簡單說一下,當(dāng)flutter 的widget構(gòu)建時(shí),最終會生成layer tree,并且最終生成scene發(fā)送到engine進(jìn)行渲染。
layer添加到scene時(shí),就會調(diào)用addToScene()方法:
@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,
);
}
進(jìn)一步來看addPlatformView方法:
void addPlatformView(
int viewId, {
Offset offset = Offset.zero,
double width = 0.0,
double height = 0.0,
}) {
assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
_addPlatformView(offset.dx, offset.dy, width, height, viewId);
}
void _addPlatformView(double dx, double dy, double width, double height, int viewId)
native 'SceneBuilder_addPlatformView';
這里直接調(diào)用了 native 方法,并將viewId傳過去。
停頓梳理下一下
到這里我們先回顧一下:
目前我們已經(jīng)通知了安卓端保存了我們的viewId,還有一些繪制參數(shù)。
flutter端,我們也向layerTree里添加了一個(gè)Layer,雖然沒有什么繪制內(nèi)容
(實(shí)際上還綁定了事件、焦點(diǎn)回調(diào)啥的)
ok,現(xiàn)在我們看一下native方法 :SceneBuilder_addPlatformView
Engine
你如果感興趣的話,可以clone一份 engine的代碼
由上面可知,最終會調(diào)用SceneBuilder的addPlatformView方法:
void SceneBuilder::addPlatformView(double dx,
double dy,
double width,
double height,
int64_t viewId) {
auto layer = std::make_unique<flutter::PlatformViewLayer>(
SkPoint::Make(dx, dy), SkSize::Make(width, height), viewId);
AddLayer(std::move(layer));
}
此方法會創(chuàng)建一個(gè)指向PlatformViewLayer類型的layer指針(帶著view的id),并添加到layer_stack_中,這里我們只看PlatformViewLayer。
在進(jìn)行 paint以及CompositeEmbeddedView 之前,會先preRoll操作 :
void PlatformViewLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
------刪除一些代碼-----
context->view_embedder->PrerollCompositeEmbeddedView(view_id_,
std::move(params));
}
實(shí)際會調(diào)用AndroidExternalViewEmbedder的PrerollCompositeEmbeddedView這個(gè)方法,其實(shí)現(xiàn):
AndroidExternalViewEmbedder 這個(gè)類將回通過PlatformViewAndroidJNI 調(diào)用android端的方法
void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView(
int view_id,
std::unique_ptr<EmbeddedViewParams> params) {
TRACE_EVENT0("flutter",
"AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView");
///這里生成一顆R樹,B樹的升級版
auto rtree_factory = RTreeFactory();
view_rtrees_.insert_or_assign(view_id, rtree_factory.getInstance());
///拿到一個(gè)指向SkPictureRecorder的指針
auto picture_recorder = std::make_unique<SkPictureRecorder>();
///開始錄制繪制命令
picture_recorder->beginRecording(SkRect::Make(frame_size_), &rtree_factory);
///將上面的recoder收集起來,因?yàn)榭赡苡泻芏鄓iew
picture_recorders_.insert_or_assign(view_id, std::move(picture_recorder));
composition_order_.push_back(view_id);
// Update params only if they changed.
if (view_params_.count(view_id) == 1 &&
view_params_.at(view_id) == *params.get()) {
return;
}
///保存了view相關(guān)參數(shù),大小啊、位置啥的
view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get()));
}
到這里,我們的layer就添加完成,并且準(zhǔn)備好錄制繪制指令了,而此時(shí)我們發(fā)現(xiàn),原生view貌似還沒有調(diào)用過,即,得不到原生view的繪制指令。
這時(shí)就要看 rasterizer 這個(gè)類了。
skia的繪制會經(jīng)過pathGeneration、rasterizer、shading和transfer
其方法:
///代碼較多,我以偽代碼來概括
RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree){
-----非常長------
///當(dāng)我們使用了 hybird composition時(shí)
if (external_view_embedder_){
///將會進(jìn)入這里
external_view_embedder_->BeginFrame
}
/.../
if (external_view_embedder_){
///將會進(jìn)入這里
external_view_embedder_->SubmitFrame
}
/.../
}
這里,我們只看SubmitFrame方法,它的實(shí)現(xiàn)在 AndroidExternalViewEmbedder::SubmitFrame 中:
///這個(gè)方法很長
///還是用偽代碼來概括
void AndroidExternalViewEmbedder::SubmitFrame(...){
///算出 flutterUI和 原生view的相交區(qū)域
/// 錄制背景繪制指令
///提交一下
///這里是真代碼
///開始原生view的顯示
for (int64_t view_id : composition_order_) {
SkRect view_rect = GetViewRect(view_id);
const EmbeddedViewParams& params = view_params_.at(view_id);
// Display the platform view. If it's already displayed, then it's
// just positioned and sized.
jni_facade_->FlutterViewOnDisplayPlatformView(
view_id, //
view_rect.x(), //
view_rect.y(), //
view_rect.width(), //
view_rect.height(), //
params.sizePoints().width() * device_pixel_ratio_,
params.sizePoints().height() * device_pixel_ratio_,
params.mutatorsStack() //
);
for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
std::unique_ptr<SurfaceFrame> frame =
CreateSurfaceIfNeeded(context, //
view_id, //
pictures.at(view_id), //
overlay_rect //
);
if (should_submit_current_frame) {
frame->Submit();
}
}
}
}
此處,通過jni_facade_->FlutterViewOnDisplayPlatformView 方法,來顯示原生view,
jni_facade 是一個(gè)指針,指向PlatformViewAndroidJNI
這個(gè)方法最終會調(diào)用 安卓端的 onDisplayPlatformView方法。
Android
上面的方法,會進(jìn)入到 FlutterJNI.Java類,并調(diào)用它的onDisplayPlatformView方法
FlutterJNI.onDisplayPlatformView
我們看一下onDisplayPlatformView 這個(gè)方法
@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);
}
可以看到,將viewId傳給了controller,并調(diào)用它的onDisplayPlatformView方法:
public void onDisplayPlatformView(
int viewId,
int x,
int y,
int width,
int height,
int viewWidth,
int ViewHeight,
FlutterMutatorsStack mutatorsStack) {
initializeRootImageViewIfNeeded();
/// 第一步
initializePlatformViewIfNeeded(viewId);
FlutterMutatorView mutatorView = mutatorViews.get(viewId);
mutatorView.readyToDisplay(mutatorsStack, x, y, width, height);
mutatorView.setVisibility(View.VISIBLE);
mutatorView.bringToFront();
///第二步
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
View platformView = platformViews.get(viewId);
platformView.setLayoutParams(layoutParams);
platformView.bringToFront();
currentFrameUsedPlatformViewIds.add(viewId);
}
關(guān)注,第一步initializePlatformViewIfNeeded 這個(gè)方法
private void initializePlatformViewIfNeeded(int viewId) {
----刪除一部分代碼----
///這個(gè) factory.create 熟悉不?
///如果不熟悉的話,可以去看一下文章開頭的使用文檔
PlatformView platformView = factory.create(context, viewId, createParams);
///我們的原生view
View view = platformView.getView();
///把我們的view和viewId 保存起來
platformViews.put(viewId, view);
FlutterMutatorView mutatorView =
new FlutterMutatorView(context, context.getResources().getDisplayMetrics().density);
mutatorViews.put(viewId, mutatorView);
mutatorView.addView(platformView.getView());
((FlutterView) flutterView).addView(mutatorView);
}
接下來我們看第二步:
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
View platformView = platformViews.get(viewId);
///設(shè)置寬高
platformView.setLayoutParams(layoutParams);
///切換到前臺
platformView.bringToFront();
currentFrameUsedPlatformViewIds.add(viewId);
通過這一步,我們的原生view就會被切換到前臺,之后便可以向engine提供繪制指令了。
至此,整個(gè)Hybrid Composition 的繪制工作流程就大致梳理完成了,謝謝大家的閱讀。
如果有錯(cuò)誤,請指出,非常感謝!