FlutterView 源码解析

FlutterView 技术分享

Android 平台上 Flutter UI 的承载核心


一、FlutterView 概述

FlutterView 是 Android 平台上承载 Flutter UI 的核心组件,继承自 FrameLayout,负责连接 Flutter 引擎与 Android 视图系统。

关键特性:

  1. 多渲染模式支持

    • Surface 模式:使用 SurfaceView,性能最佳

    • Texture 模式:使用 TextureView,支持动画和视图层级混合

    • ImageView 模式:后台静态渲染,节省资源

  2. 全生命周期管理

    • 引擎绑定/解绑

    • 状态保存/恢复

    • 跨 Activity/Fragment 复用

  3. 平台集成桥梁

    • 触摸事件处理

    • 键盘输入管理

    • 无障碍服务支持

    • 折叠屏/刘海屏适配

| 场景 | 推荐模式 | 优势 |
| 常规页面 | Surface | 60fps 满帧率,功耗最低 |
| 包含动画的页面 | Texture | 支持变换/透明度/视图混合 |

后台态 ImageView 内存占用降低 40%,避免空渲染

二、核心实现机制

1. 引擎绑定机制

java 复制代码
// Activity/Fragment 中  
override fun onStart() {  
    super.onStart()  
    flutterView.attachToFlutterEngine(flutterEngine)  
}  

override fun onStop() {  
    super.onStop()  
    if (isChangingConfigurations.not()) {  
        flutterView.detachFromFlutterEngine()  
    }  
}  

初始化各种插件,绑定渲染引擎

java 复制代码
// 绑定引擎(完整流程)  
public void attachToFlutterEngine(@NonNull FlutterEngine engine) {  
    // 1. 渲染器连接  
    renderSurface.attachToRenderer(engine.getRenderer());  
    engine.getRenderer().addIsDisplayingFlutterUiListener(uiListener);  
    
    // 2. 初始化事件处理器  
    textInputPlugin = new TextInputPlugin(this, engine.getTextInputChannel());  
    accessibilityBridge = new AccessibilityBridge(...);  
    
    // 3. 平台视图控制器连接  
    engine.getPlatformViewsController().attachToView(this);  
    
    // 4. 状态同步  
    sendUserSettingsToFlutter();  
    sendViewportMetricsToFlutter();  
}  

// 解绑时的资源清理  
public void detachFromFlutterEngine() {  
    // 1. 释放无障碍服务  
    accessibilityBridge.release();  
    
    // 2. 断开渲染连接  
    flutterEngine.getRenderer().removeIsDisplayingFlutterUiListener(uiListener);  
    renderSurface.detachFromRenderer();  
    
    // 3. 销毁插件实例  
    textInputPlugin.destroy();  
    keyboardManager.destroy();  
}  

三、关键技术点

1.渲染模式切换

java 复制代码
// 切换到静态图像渲染(用于后台优化)  
public void convertToImageView() {  
    renderSurface.pause();  
    if (flutterImageView == null) {  
        flutterImageView = new FlutterImageView(...);  
        addView(flutterImageView);  
    }  
    previousRenderSurface = renderSurface;  
    renderSurface = flutterImageView;  
    renderSurface.attachToRenderer(flutterEngine.getRenderer());  
}  

// 视口数据同步(关键性能点)  
private void sendViewportMetricsToFlutter() {  
    viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density;  
    viewportMetrics.physicalTouchSlop = ViewConfiguration.getScaledTouchSlop();  
    flutterEngine.getRenderer().setViewportMetrics(viewportMetrics);  
}  

2. 刘海屏兼容处理

java 复制代码
// 刘海屏适配(API 28+)  
@TargetApi(API_LEVELS.API_28)  
protected void handleDisplayCutout(WindowInsets insets) {  
    if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) {  
        DisplayCutout cutout = insets.getDisplayCutout();  
        for (Rect bounds : cutout.getBoundingRects()) {  
            viewportMetrics.displayFeatures.add(  
                new DisplayFeature(bounds, DisplayFeatureType.CUTOUT)  
            );  
        }  
        sendViewportMetricsToFlutter();  
    }  
}  

// 折叠屏状态同步  
private void handleFoldingFeature(FoldingFeature feature) {  
    DisplayFeatureType type = (feature.getOcclusionType() == FULL) ?  
        DisplayFeatureType.HINGE : DisplayFeatureType.FOLD;  
    
    viewportMetrics.displayFeatures.add(  
        new DisplayFeature(feature.getBounds(), type, getFeatureState(feature))  
    );  
}  

3. 性能选择性重绘

java 复制代码
// 选择性重绘(减少不必要的 GPU 调用)  
private void resetWillNotDraw(boolean isAccessibilityEnabled,  
                             boolean isTouchExplorationEnabled) {  
    // 无障碍模式下需要强制重绘  
    setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));  
}  

// 触摸事件优化(减少事件传递层级)  
@Override  
public boolean onTouchEvent(MotionEvent event) {  
    requestUnbufferedDispatch(event); // 跳过缓冲直接处理  
    return androidTouchProcessor.onTouchEvent(event);  
}  

4. 无障碍深度支持

java 复制代码
// 复杂视图树的无障碍节点查找  
@SuppressLint("DiscouragedPrivateApi")  
private View findViewByAccessibilityIdRootedAtCurrentView(int id, View view) {  
    // 深度优先搜索遍历视图树  
    if (view.getAccessibilityViewId() == id) return view;  
    if (view instanceof ViewGroup) {  
        for (int i = 0; i < ((ViewGroup)view).getChildCount(); i++) {  
            View result = findViewByAccessibilityId(...);  
            if (result != null) return result;  
        }  
    }  
    return null;  
}  

// 悬停事件转无障碍事件  
@Override  
public boolean onHoverEvent(MotionEvent event) {  
    return accessibilityBridge.onAccessibilityHoverEvent(event);  
}  

5. 键盘问题解决方案

java 复制代码
// 键盘高度精确计算  
private int guessBottomKeyboardInset(WindowInsets insets) {  
    int screenHeight = getRootView().getHeight();  
    return (insets.getSystemWindowInsetBottom() > screenHeight * 0.18) ?  
        insets.getSystemWindowInsetBottom() : 0; // 过滤掉导航栏  
}  

// 在 onApplyWindowInsets 中同步  
viewportMetrics.viewInsetBottom = guessBottomKeyboardInset(insets);  
sendViewportMetricsToFlutter();  

6.触摸事件处理

java 复制代码
  /**
   * Invoked by Android when a user touch event occurs.
   *
   * <p>Flutter handles all of its own gesture detection and processing, therefore this method
   * forwards all {@link MotionEvent} data from Android to Flutter.
   */
  @Override
  public boolean onTouchEvent(@NonNull MotionEvent event) {
    if (!isAttachedToFlutterEngine()) {
      return super.onTouchEvent(event);
    }

    requestUnbufferedDispatch(event);

    return androidTouchProcessor.onTouchEvent(event);
  }

将触摸各种参数封装成一个数据包,发送到引擎

java 复制代码
  /**
   * Sends the given {@link MotionEvent} data to Flutter in a format that Flutter understands.
   *
   * @param event The motion event from the view.
   * @param transformMatrix Applies to the view that originated the event. It's used to transform
   *     the gesture pointers into screen coordinates.
   * @return True if the event was handled.
   */
  public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transformMatrix) {
    int pointerCount = event.getPointerCount();

    // The following packing code must match the struct in pointer_data.h.

    // Prepare a data packet of the appropriate size and order.
    ByteBuffer packet =
        ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
    packet.order(ByteOrder.LITTLE_ENDIAN);

    int maskedAction = event.getActionMasked();
    int pointerChange = getPointerChangeForAction(event.getActionMasked());
    boolean updateForSinglePointer =
        maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN;
    boolean updateForMultiplePointers =
        !updateForSinglePointer
            && (maskedAction == MotionEvent.ACTION_UP
                || maskedAction == MotionEvent.ACTION_POINTER_UP);
    if (updateForSinglePointer) {
      // ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only.
      addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
    } else if (updateForMultiplePointers) {
      // ACTION_UP and ACTION_POINTER_UP may contain position updates for other pointers.
      // We are converting these updates to move events here in order to preserve this data.
      // We also mark these events with a flag in order to help the framework reassemble
      // the original Android event later, should it need to forward it to a PlatformView.
      for (int p = 0; p < pointerCount; p++) {
        if (p != event.getActionIndex() && event.getToolType(p) == MotionEvent.TOOL_TYPE_FINGER) {
          addPointerForIndex(
              event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, transformMatrix, packet);
        }
      }
      // It's important that we're sending the UP event last. This allows PlatformView
      // to correctly batch everything back into the original Android event if needed.
      addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
    } else {
      // ACTION_MOVE may not actually mean all pointers have moved
      // but it's the responsibility of a later part of the system to
      // ignore 0-deltas if desired.
      for (int p = 0; p < pointerCount; p++) {
        addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet);
      }
    }

    // Verify that the packet is the expected size.
    if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
      throw new AssertionError("Packet position is not on field boundary");
    }

    // Send the packet to Flutter.
    renderer.dispatchPointerDataPacket(packet, packet.position());

    return true;
  }
java 复制代码
  public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
    flutterJNI.dispatchPointerDataPacket(buffer, position);
  }

五、设计思想启示

1. 分层架构设计

相关推荐
LawrenceLan2 小时前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹3 小时前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者963 小时前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
行者966 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者968 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难8 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios