Flutter集成原生视图
介绍
原生视图在 Flutter 中被称作平台视图(Platform View),Flutter 允许将原生视图嵌入 Flutter 中。
这篇文章主要是对网络资料的整合,加上我自己的一些实践体会。包含实现方式和 Android 底层原理学习。
Android 侧接入
两种集成模式
虚拟显示模式(Virtual displays)和混合集成模式(Hybrid composition)。
虚拟显示模式
将 android.view.View 实例渲染为纹理,不会嵌入到 Activity 的视图层次结构中。键盘处理和辅助功能可能无法工作。
混合集成模式
需要 Flutter 1.22(推荐 1.22.2)。
将原生的 android.view.View 附加到视图层次结构中。键盘处理和无障碍功能是开箱即用的。
Android 10 之前,这个模式会大大降低 Flutter UI 的帧率(FPS)。
虚拟显示模式实现
Android 侧实现
创建一个 View 实现 PlatformView 接口:
public class ThreeImageView implements PlatformView {
private LinearLayout ll;
public ThreeImageView(Context context) {
ll = new LinearLayout(context);
ll.setOrientation(LinearLayout.HORIZONTAL);
ImageView im1 = new ImageView(context);
LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(100, 100);
im1.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.mipmap.ic_launcher, null));
ll.addView(im1, lp1);
ImageView im2 = new ImageView(context);
LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(100, 100);
im2.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.mipmap.ic_launcher, null));
ll.addView(im2, lp2);
ImageView im3 = new ImageView(context);
LinearLayout.LayoutParams lp3 = new LinearLayout.LayoutParams(100, 100);
im3.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.mipmap.ic_launcher, null));
ll.addView(im3, lp3);
}
@Override
public View getView() {
return ll;
}
@Override
public void dispose() {
ll = null;
}
}
这里需要注意一个细节,布局实在构造函数中构建的,而不是在 getView 中构建,好处:
- 避免 View 多次重复创建,造成性能开销,提升性能
创建一个 FlutterViewFactory 继承自 PlatformViewFactory:
public class ThreeImageViewFactory extends PlatformViewFactory {
public ThreeImageViewFactory(MessageCodec<Object> createArgsCodec) {
super(createArgsCodec);
}
@Override
public PlatformView create(Context context, int viewId, Object args) {
return new ThreeImageView(context);
}
}
接下来再创建一个 Plugin,在插件里注册组件以及一个名称:
public class DemoPlatformViewPlugin implements FlutterPlugin {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
binding.getPlatformViewRegistry().registerViewFactory(
"NATIVE_THREE_IMAGE",
new ThreeImageViewFactory(StandardMessageCodec.INSTANCE));
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
}
}
接下来是将 Plugin 注册到引擎当中。在 MainActivity 的 configureFlutterEngine 中,注册插件:
public class MainActivity extends FlutterActivity {
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
flutterEngine.getPlugins().add(new DemoPlatformViewPlugin());
}
}
Flutter Dart 侧开发
在 Flutter Dart 代码里,创建了一个长列表,在列表中嵌入 Native 视图,dart 代码:
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
if (index % 10 == 0) {
return Container(
width: MediaQuery.of(context).size.width,
height: 100,
child: ThreeImageViewWrapper(),
);
}
return ListTile(title: Text('item $index'));
}));
}
}
class ThreeImageViewWrapper extends StatefulWidget {
@override
_ThreeImageViewWrapperState createState() => _ThreeImageViewWrapperState();
}
class _ThreeImageViewWrapperState extends State<ThreeImageViewWrapper> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => AndroidView(
viewType: "NATIVE_THREE_IMAGE",
creationParams: {},
creationParamsCodec: const StandardMessageCodec(),
),
);
}
@override
bool get wantKeepAlive => true;
}

展示效果如右图。其中:
- 每隔几个 Item 就会创建一个 Native 视图
- 通过实验发现,嵌入视图确实会对渲染性能造成影响,即便是以 Release 构建,当展示的 Native 视图增多时,卡顿会变得明显。
- 这个卡顿的主要原因是多线程阻塞,Flutter 的渲染模型是多线程的,当有 Native 视图嵌入式,融入了 Android 的渲染模型,需要在两套模型之间做等待,导致性能下降
- 当只有一两个 Native 视图的时候,性能下降并不明显,在 Release 下还是非常流畅的
- 以 Release 模式运行:flutter run --release
在上面代码中,我还做了几个优化:
- 将 AndroidView 封到一个 StatefulWidget 中,主要是混入了 AutomaticKeepAliveClientMixin,这个 mixin 用与 ListView Item,作用是防止被回收。加了之后,当加载嵌入 Native 视图的时候,只有首次有性能开销,后续就没有性能开销了。
- 第二个改进是给 AndroidView 还加了一个 LayoutBuilder,本意是让 Item 先渲染,AndroidView 进行懒加载,但加上后效果不明显,应该跟我想的不太一样。
总之,加 AutomaticKeepAliveClientMixin 效果还是非常明显的。
Android View 文档
需要 Android API 20 以上。嵌入 Android 视图是一个有开销的操作,如果能用 Flutter 实现尽量用 Flutter 实现。
被嵌入的 Android View 像普通 Flutter 组件一样绘制,应用变换也是可以的。
组件会充满可用空间,父组件提供绘制边界。
组件参与 Flutter 的手势竞技场。
Android 视图对象由。PlatformViewFactory 创建,插件可以通过 PlatformViewFactory 的 registerViewFactory 方法注册工厂。
platform view 的生命周期跟 State 的生命周期一样。当 State dispose,platform view 被懒释放(有 Native 侧垃圾回收器最终回收)。
混合集成开发
开发方式参见文档。
iOS 侧接入
Android 底层实现原理
基于引擎 1.20.4 代码进行分析。
PlatformViewsController
shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
管理 Platform Views。每个 FlutterPluginRegistry 有一个单独的 PlatformViewsController 实例。一个 PlatformViewsController 最多可以添加到一个 FlutterView 上。
构造
哪里构造?
- FlutterPluginRegistry 的构造函数
- FlutterEngine 构造方法
textureRegistry 属性
在 attach 方法中获取到实例并存入属性:
public void attach(
Context context, TextureRegistry textureRegistry, @NonNull DartExecutor dartExecutor) {
调用方:
- FlutterPluginRegistry.attach
createVirtualDisplayForPlatformView
// 查找 Factory
PlatformViewFactory viewFactory = registry.getFactory(request.viewType);
// 解码创建参数
createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params);
// 创建 SurfaceTextureEntry
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);
}
});
vdController.onFlutterViewAttached(flutterView);
vdControllers.put(request.viewId, vdController);
// 获取 Android 视图
View platformView = vdController.getView();
platformView.setLayoutDirection(request.direction);
contextToPlatformView.put(platformView.getContext(), platformView);
// 返回的是纹理 id
return textureEntry.id();
FlutterView
FlutterView 实现了 TextureRegistry 接口,是 Flutter 中外接纹理 Android Native 侧的管理方。
createSurfaceTexture
创建纹理:
@Override
public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() {
final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
surfaceTexture.detachFromGLContext();
final SurfaceTextureRegistryEntry entry =
new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture);
mNativeView.getFlutterJNI().registerTexture(entry.id(), surfaceTexture);
return entry;
}
其中:
- SurfaceTexture 对象
- 创建一个 SurfaceTextureRegistryEntry,其中纹理 id 自增。
- 调用 FlutterJNI 把创建的纹理注册进去了
- 最后把 SurfaceTextureRegistryEntry 给返回去了
createSurfaceTexture 调用处
FlutterJNI
注册 RegisterTexture 方法
public void registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture) {
ensureRunningOnMainThread();
ensureAttachedToNative();
nativeRegisterTexture(nativePlatformViewId, textureId, surfaceTexture);
}
private native void nativeRegisterTexture(
long nativePlatformViewId, long textureId, @NonNull SurfaceTexture surfaceTexture);
PlatformViewAndroidJNIImpl
注册 RegisterTexture 方法
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterJNI
……
{
.name = "nativeRegisterTexture",
.signature = "(JJLandroid/graphics/SurfaceTexture;)V",
.fnPtr = reinterpret_cast<void*>(&RegisterTexture),
},
……
三个参数:
- name FlutterJNI 中注册方法
- signature,Flutter JNI 中对应方法签名
- fnPtr,C 侧响应函数
RegisterTexture
实现:
static void RegisterTexture(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jlong texture_id,
jobject surface_texture) {
ANDROID_SHELL_HOLDER->GetPlatformView()->RegisterExternalTexture(
static_cast<int64_t>(texture_id), //
fml::jni::JavaObjectWeakGlobalRef(env, surface_texture) //
);
}
只用到两个对象,纹理 id 和纹理。
PlatformViewAndroid
shell/platform/android/platform_view_android.h
RegisterExternalTexture
注册纹理,方法签名:
void RegisterExternalTexture(
int64_t texture_id,
const fml::jni::JavaObjectWeakGlobalRef& surface_texture);
传入两个参数:
- 纹理 id
- 一个 Java 对象,对应的纹理
实现:
void PlatformViewAndroid::RegisterExternalTexture(
int64_t texture_id,
const fml::jni::JavaObjectWeakGlobalRef& surface_texture) {
RegisterTexture(std::make_shared<AndroidExternalTextureGL>(
texture_id, surface_texture, std::move(jni_facade_)));
}
其中:
- 将纹理封装到了 AndroidExternalTextureGL 对象中
- 调用 PlatformView 的 RegisterTexture 方法
通用底层逻辑
PlatformView
shell/common/platform_view.h
OnPlatformViewRegisterTexture
通知 delegate,embbeder 指定了一个纹理(texture)想让 rasterizer 将它合成到 Flutter Layer tree 里面。
每个 texture 都要有唯一标识。
当 rasterizer 在层级结构里面遇到一个外接纹理,它给 embbeder 一个机会来在将画面合成到屏幕之前,在 raster 线程进行纹理更新。
方法签名:
virtual void OnPlatformViewRegisterTexture(
std::shared_ptr<Texture> texture) = 0;
通过 PlatformView 的 RegisterTexture 方法进行纹理注册。 OnPlatformViewRegisterTexture 在 shell/common/shell.cc 中实现。其具体工作是切换到 raster 线程,想 raster 的 textureRegister 里面注册纹理:
if (auto* registry = rasterizer->GetTextureRegistry()) {
registry->RegisterTexture(texture);
}