FlutterView 源码解析

发布于:2025-07-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

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. 引擎绑定机制

// Activity/Fragment 中  
override fun onStart() {  
    super.onStart()  
    flutterView.attachToFlutterEngine(flutterEngine)  
}  

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

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

// 绑定引擎(完整流程)  
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.渲染模式切换

// 切换到静态图像渲染(用于后台优化)  
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. 刘海屏兼容处理

// 刘海屏适配(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. 性能选择性重绘

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

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

4. 无障碍深度支持

// 复杂视图树的无障碍节点查找  
@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. 键盘问题解决方案

// 键盘高度精确计算  
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.触摸事件处理

  /**
   * 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);
  }

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

  /**
   * 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;
  }
  public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
    flutterJNI.dispatchPointerDataPacket(buffer, position);
  }

五、设计思想启示

1. 分层架构设计


网站公告

今日签到

点亮在社区的每一天
去签到