Flutter Android 端 FlutterView 相關流程原始碼分析

工匠若水發表於2021-08-18

這是我參與8月更文挑戰的第5天,活動詳情檢視: 8月更文挑戰

Flutter 系列文章連載~

背景

前面系列文章我們分析了 FlutterActivity 等相關流程,知道一個 Flutter Android App 的本質是通過 FlutterView 進行渲染。當時由於篇幅限制,我們沒有進入詳細分析,這裡作為一個專題進行簡單分析。

SDK 中同屬於 FlutterView 體系的控制元件大致有如圖這些: 在這裡插入圖片描述

下文主要圍繞上圖進行分析。

FlutterSplashView 相關分析

FlutterSplashView 的主要作用是在 FlutterView render 渲染出來之前顯示一個SplashScreen(本質 Drawable)過渡圖(可以理解成類似開屏圖片)。這個控制元件的呼叫在前面《Flutter Android 端 Activity/Fragment 流程原始碼分析》文章中分析 FlutterActivityAndFragmentDelegate 時有看到過,在其 onCreateView 方法中先例項化了 FlutterSplashView,接著呼叫flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen()),然後把這個 FlutterSplashView 控制元件返回給 FlutterActivity 通過 setContentView 進行設定。下面是其相關流程主要原始碼:

final class FlutterSplashView extends FrameLayout {
  //......
  //步驟1、把給定的splashScreen顯示在flutterView之上,直到flutterView的首幀渲染出來才過渡消失。
  public void displayFlutterViewWithSplash(
      @NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {
    //步驟2、一堆重複呼叫的復位操作。
    if (this.flutterView != null) {
      this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
      removeView(this.flutterView);
    }
    if (splashScreenView != null) {
      removeView(splashScreenView);
    }

    //步驟3、把flutterView新增給當前FlutterSplashView,本質是一個FrameLayout。
    this.flutterView = flutterView;
    addView(flutterView);

    this.splashScreen = splashScreen;

    //步驟4、顯示一個splash screen開屏圖。
    if (splashScreen != null) {
      //步驟5、如果flutterView未渲染出來則條件成立。
      if (isSplashScreenNeededNow()) {
        Log.v(TAG, "Showing splash screen UI.");
        //步驟6、splashScreen是FlutterActivity中實現的DrawableSplashScreen。
        //DrawableSplashScreen中的Drawable本質來自清單檔案meta-data中io.flutter.embedding.android.SplashScreenDrawable配置。
        //DrawableSplashScreen implements SplashScreen,所以就是DrawableSplashScreen的createSplashView方法。
        //因此splashScreenView是DrawableSplashScreenView,繼承自ImageView,設定的圖為Drawable。
        splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
        //步驟7、把ImageView新增到FlutterSplashView中。
        //由於FlutterSplashView是FrameLayout,所以ImageView蓋在步驟3的flutterView之上。
        addView(this.splashScreenView);
        //步驟8、給flutterView新增監聽回撥,等第一幀繪製時觸發。
        //回撥裡面做的事本質就是從開屏過渡消失到flutterView顯示出來。
        flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
      } else if (isSplashScreenTransitionNeededNow()) {
        Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition.");
        //步驟9、同步驟6、7做的事情。
        splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
        addView(splashScreenView);
        //步驟10、由於是中間狀態,所以不用監聽,直接新增後就從開屏過渡消失到flutterView顯示出來。
        transitionToFlutter();
      } else if (!flutterView.isAttachedToFlutterEngine()) {
        Log.v(TAG, "FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached.");
		//步驟11、如果這時候flutter引擎還沒attach上。
		//那就監聽attach,等attach上就開始追加開屏並顯示,等到渲染第一幀開始就結束。
        flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener);
      }
    }
  }

  //單純的判斷flutterView是否渲染出來,沒出來就說明需要過渡介面。
  private boolean isSplashScreenNeededNow() {
    return flutterView != null
        && flutterView.isAttachedToFlutterEngine()
        && !flutterView.hasRenderedFirstFrame()
        && !hasSplashCompleted();
  }

  //判斷是否上一個過渡動畫開屏正在進行中。
  private boolean isSplashScreenTransitionNeededNow() {
    return flutterView != null
        && flutterView.isAttachedToFlutterEngine()
        && splashScreen != null
        && splashScreen.doesSplashViewRememberItsTransition()
        && wasPreviousSplashTransitionInterrupted();
  }
  //......
  //開屏過渡到flutterview顯示
  private void transitionToFlutter() {
    //......
    //步驟12、splashScreen就是DrawableSplashScreen。
    //本質就是DrawableSplashScreenView(即ImageView)做一個預設500ms的alpha漸變透明動畫。
    //動畫完畢回撥onTransitionComplete介面實現,從當前FrameLayout中刪除開屏追加的ImageView,child只剩下FlutterView。
    splashScreen.transitionToFlutter(onTransitionComplete);
  }
  //......
  //等attach上後走進步驟1流程,不解釋。
  @NonNull
  private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener =
      new FlutterView.FlutterEngineAttachmentListener() {
        @Override
        public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) {
          flutterView.removeFlutterEngineAttachmentListener(this);
          displayFlutterViewWithSplash(flutterView, splashScreen);
        }
        //......
      };

  //flutterView的第一幀繪製時觸發,本質就是從開屏過渡消失到flutterView顯示出來。
  @NonNull
  private final FlutterUiDisplayListener flutterUiDisplayListener =
      new FlutterUiDisplayListener() {
        @Override
        public void onFlutterUiDisplayed() {
          if (splashScreen != null) {
            transitionToFlutter();
          }
        }
        //......
      };
  //動畫做完就移除開屏view控制元件。
  @NonNull
  private final Runnable onTransitionComplete =
      new Runnable() {
        @Override
        public void run() {
          removeView(splashScreenView);
          //......
        }
      };
  //......
}
複製程式碼

看完上面程式碼你也就明白為什麼我們在 Android Studio 中檢視 FlutterActivity 的安卓層級樹時,只看到 Activity content 的 child 是 FlutterSplashView,FlutterSplashView 的 child 是 FlutterView,而 FlutterSplashView 的另一個 child DrawableSplashScreenView 不見的原因就是 500ms 動畫之後被 remove 了。如下圖: 在這裡插入圖片描述

FlutterTextureView 相關分析

在前面系列文章中分析 FlutterActivity 時我們知道,FlutterView 建立時依賴一個 FlutterTextureView 或者 FlutterSurfaceView,其判斷條件的本質就是看 FlutterActivity 的 window 窗體背景是否透明(FlutterFragment 時通過 Arguments 的 flutterview_render_mode 引數來決定),不透明就是 surface,透明就是 texture。因此,我們這裡就是針對其 window 透明場景來分析的。

//步驟13、在一個SurfaceTexture上繪製Flutter UI,就是單純的渲染,不處理點選等各種事件。
//想要開始渲染,FlutterTextureView的持有者需要先呼叫attachToRenderer(FlutterRenderer)。
//同理,想要終止渲染,FlutterTextureView的持有者需要先呼叫detachFromRenderer()。
public class FlutterTextureView extends TextureView implements RenderSurface {
  //......
  //步驟14、主要是基於標準監聽的connectSurfaceToRenderer和disconnectSurfaceFromRenderer操作。
  private final SurfaceTextureListener surfaceTextureListener =
      new SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(
            SurfaceTexture surfaceTexture, int width, int height) {
          //......
          if (isAttachedToFlutterRenderer) {
            connectSurfaceToRenderer();
          }
        }
		//......
        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
          //......
          if (isAttachedToFlutterRenderer) {
            disconnectSurfaceFromRenderer();
          }
          return true;
        }
      };

  //......

  //步驟15、在FlutterView的attachToFlutterEngine方法中被呼叫。
  //引數來自FlutterEngine的getRenderer(),型別是FlutterRenderer,裡面本質是SurfaceTexture。
  public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
    //......
    connectSurfaceToRenderer();
    //......
  }

  //步驟16、在FlutterView的detachFromFlutterEngine方法中被呼叫。
  //與步驟15方法成對始終。
  public void detachFromRenderer() {
    //......
    disconnectSurfaceFromRenderer();
    //......
  }

  private void connectSurfaceToRenderer() {
    //......
    renderSurface = new Surface(getSurfaceTexture());
    flutterRenderer.startRenderingToSurface(renderSurface);
  }

  private void disconnectSurfaceFromRenderer() {
    //......
    flutterRenderer.stopRenderingToSurface();
    if (renderSurface != null) {
      renderSurface.release();
      renderSurface = null;
    }
  }
  //......
}
複製程式碼

上面可以看到,FlutterTextureView 的本質就是一個標準的 TextureView,用法也完全一樣,只是渲染資料是通過 FlutterJNI 進行 engine 與 Android Java 層傳遞而已。

FlutterSurfaceView 相關分析

與上面 FlutterTextureView 分析同理,FlutterSurfaceView 自然就是針對其 window 不透明場景來分析的。下面是類似上面概覽原始碼:

//步驟17、在一個Surface上繪製Flutter UI,就是單純的渲染,不處理點選等各種事件。
//想要開始渲染,FlutterSurfaceView的持有者需要先呼叫attachToRenderer(FlutterRenderer)。
//同理,想要終止渲染,FlutterSurfaceView的持有者需要先呼叫detachFromRenderer()。
public class FlutterSurfaceView extends SurfaceView implements RenderSurface {
  //......
  private final SurfaceHolder.Callback surfaceCallback =
      new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
          //......
          connectSurfaceToRenderer();
        }
		//......
        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
          //......
          disconnectSurfaceFromRenderer();
        }
      };

  public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
    //......
    connectSurfaceToRenderer();
  }

  public void detachFromRenderer() {
    //......
    disconnectSurfaceFromRenderer();
  }

  private void connectSurfaceToRenderer() {
    //......
    flutterRenderer.startRenderingToSurface(getHolder().getSurface());
  }

  private void disconnectSurfaceFromRenderer() {
    //......
    flutterRenderer.stopRenderingToSurface();
  }
  //......
}
複製程式碼

可以看到,不多解釋,和 FlutterSurfaceView 基本如出一轍。

FlutterRenderer 相關分析

FlutterRenderer 的主要職責是通過 FlutterEngine 進行渲染關聯處理,與原生平臺提供的 FlutterSurfaceView、FlutterTextureView 進行純 UI 渲染,將 Flutter 畫素繪製到 Android 檢視層次結構。

public class FlutterRenderer implements TextureRegistry {
  //......
  @NonNull private final FlutterJNI flutterJNI;
  @Nullable private Surface surface;
  //......
}
複製程式碼

通過上面原始碼的兩個屬性成員就能看出來他的職責。結合上面小節可以得到一個如下職責抽象架構圖: 在這裡插入圖片描述

FlutterView 相關分析

FlutterView 的作用是在 Android 裝置上顯示一個 Flutter UI,繪製內容來自於 FlutterEngine 提供。FlutterView 有兩種不同的渲染模式(io.flutter.embedding.android.RenderMode#surfaceio.flutter.embedding.android.RenderMode#texture),其中 surface 模式的效能比較高,但是在 z-index 上無法與其他 Android View 進行佈局,沒法進行 animated、transformed 變換;而 texture 模式雖然效能沒有 surface 高,但是沒有 surface 的那些缺點限制。一般儘可能選擇 surface 模式,FlutterView 的預設構造器就是 surface 模式,FlutterActivity 的 window 不透明時預設也是 surface 模式,FlutterFragment 的預設無引數修改情況下也是 surface 模式,不信可以翻看本系列的前面相關文章。

下面我們先看下 FlutterView 的成員和構造初始化相關流程,如下程式碼片段:

public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
  //用來真正渲染繪製檢視的。
  @Nullable private FlutterSurfaceView flutterSurfaceView;
  @Nullable private FlutterTextureView flutterTextureView;
  @Nullable private FlutterImageView flutterImageView;
  @Nullable private RenderSurface renderSurface;
  @Nullable private RenderSurface previousRenderSurface;
  //......
  //用來處理Android View的input and events。
  @Nullable private MouseCursorPlugin mouseCursorPlugin;
  @Nullable private TextInputPlugin textInputPlugin;
  @Nullable private LocalizationPlugin localizationPlugin;
  @Nullable private AndroidKeyProcessor androidKeyProcessor;
  @Nullable private AndroidTouchProcessor androidTouchProcessor;
  @Nullable private AccessibilityBridge accessibilityBridge;
  
  //預設建構函式,預設模式為surface,即FlutterSurfaceView渲染。
  public FlutterView(@NonNull Context context) {
    this(context, null, new FlutterSurfaceView(context));
  }
  //省略一堆各種引數的建構函式
  //......
  //本質就是指定一個RenderSurface,即如下三者之一。
  private void init() {
    if (flutterSurfaceView != null) {
      addView(flutterSurfaceView);
    } else if (flutterTextureView != null) {
      addView(flutterTextureView);
    } else {
      addView(flutterImageView);
    }

    //FlutterView自己需要能接收事件。
    setFocusable(true);
    setFocusableInTouchMode(true);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
    }
  }
  //......
}
複製程式碼

通過上面程式碼我們可以知道,FlutterView 其實就是一個普通的 Android FrameLayout,其內部依據條件被 addView 了一個 View,這個 View 都實現自 RenderSurface 介面,也就是 FlutterSurfaceView、FlutterTextureView、FlutterImageView 之一,預設為 FlutterSurfaceView 而已。所以說真正繪製渲染 FlutterEngine 資料的不是 FlutterView,而是實現 RenderSurface 介面的控制元件,譬如 FlutterSurfaceView。整體 View 層級關係如下圖: 在這裡插入圖片描述 構造完 FlutterView 例項後,我們通過前面的系列文章可以知道,在 FlutterActivityAndFragmentDelegate 的 onCreateView 方法返回給 FlutterActivity 一個 contentView 前 FlutterView 有通過自己的 attachToFlutterEngine 方法與 FlutterEngine 關聯,所以我們看下這個關聯方法(對應還有一個 detachFromFlutterEngine 方法進行取消關聯):

public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
  //......
  public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    //......
	//賦值flutterEngine。
    this.flutterEngine = flutterEngine;

    //從flutterEngine引擎獲取flutterRenderer例項。
    FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
    //renderSurface進行attachToRenderer,本質譬如就是FlutterSurfaceView的attachToRenderer方法。
    renderSurface.attachToRenderer(flutterRenderer);
    //初始化各種plugin。
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      //滑鼠相關外掛。
      mouseCursorPlugin = new MouseCursorPlugin(this, this.flutterEngine.getMouseCursorChannel());
    }
    //輸入相關外掛。
    textInputPlugin =
        new TextInputPlugin(this, this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController());
    //config本地變更等外掛。
    localizationPlugin = this.flutterEngine.getLocalizationPlugin();
    //key及touch事件、accessibility輔助模式相關channel通道處理。
    androidKeyProcessor =
        new AndroidKeyProcessor(this, this.flutterEngine.getKeyEventChannel(), textInputPlugin);
    androidTouchProcessor =
        new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
    accessibilityBridge =
        new AccessibilityBridge(
            this,
            flutterEngine.getAccessibilityChannel(),
            (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
            getContext().getContentResolver(),
            this.flutterEngine.getPlatformViewsController());
    accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
    //各種平臺相關事件初始排程。
    this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
    this.flutterEngine
        .getPlatformViewsController()
        .attachToFlutterRenderer(this.flutterEngine.getRenderer());

    textInputPlugin.getInputMethodManager().restartInput(this);

    sendUserSettingsToFlutter();
    localizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
    sendViewportMetricsToFlutter();

    flutterEngine.getPlatformViewsController().attachToView(this);
    //......
  }
}
複製程式碼

可以看到,FlutterView 與 FlutterEngine 進行 attach 時主要做的事情就是回撥設定、渲染關聯、系統平臺 plugin 初始化關聯等。上面的各種 plugin 我們可以先不用關心細節,知道 attachToFlutterEngine 主要做這些事情即可,後面會專門分析。

接著我們按照標準 Android 平臺的 View 主要方法進行分類分析,先看看 FlutterView 的 onConfigurationChanged 方法,如下:

public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
  //......
  @Nullable private LocalizationPlugin localizationPlugin;

  @Override
  protected void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    //響應系統螢幕渲染或者配置發生變化,譬如分屏、暗黑、多語言啥的。
    if (flutterEngine != null) {
      Log.v(TAG, "Configuration changed. Sending locales and user settings to Flutter.");
      //呼叫LocalizationPlugin外掛設定變更後新的Configuration。
      localizationPlugin.sendLocalesToFlutter(newConfig);
      //把變更傳送到FlutterEngine去,通知引擎。
      sendUserSettingsToFlutter();
    }
  }

  void sendUserSettingsToFlutter() {
    //當前是不是暗黑模式。
    boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES;
    SettingsChannel.PlatformBrightness brightness = isNightModeOn
            ? SettingsChannel.PlatformBrightness.dark : SettingsChannel.PlatformBrightness.light;
    //通過flutterEngine的SettingsChannel傳送變更訊息。
    flutterEngine
        .getSettingsChannel()
        .startMessage()
        .setTextScaleFactor(getResources().getConfiguration().fontScale)
        .setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
        .setPlatformBrightness(brightness)
        .send();
  }
  //......
}
複製程式碼

可以看到,當系統配置發生變更時 FlutterView 自己在安卓端其實不做什麼事的,主要就是負責把事件通知到 flutterEngine 端去,然後 flutterEngine 再傳遞到 dart 響應,從而觸發新的繪製重新整理效果。

由於整體都是這個模式,所以 FlutterView 中的非典型方法我們不再分析,類比即可。下面我們看下事件是怎麼派發的,如下:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
  //......
  return (isAttachedToFlutterEngine() && androidKeyProcessor.onKeyEvent(event))
      || super.dispatchKeyEvent(event);
}

@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
  //......
  return androidTouchProcessor.onTouchEvent(event);
}
複製程式碼

啥感覺?androidTouchProcessor 例項就是前面分析的 FlutterView 中 attachToFlutterEngine 方法裡例項化的,本質就是通過 flutterEngine 的 KeyEventChannel 進行事件分發。到此也就應證了我們前面說的,FlutterView 只是一個在安卓端管理的 View,內部的渲染有專門的 View 負責,內部的事件全部通過原生分發到 flutterEngine 進行 dart 程式碼的觸發處理,然後交回原生平臺渲染。以 FlutterSurfaceView 為例整體互動流程圖很像下面這樣: 在這裡插入圖片描述 通過如上超級抽象圖其實我們就大概明白了 Flutter 框架的精髓(當然,細節還是很複雜的),也印證了一個純 Flutter Android App 在原生平臺側的層級結構是下面這樣: 在這裡插入圖片描述

FlutterImageView 相關分析

分析完 FlutterRenderer、FlutterSurfaceView、FlutterTextureView 及 FlutterView 之後我們再來看看 FlutterImageView,其實他和上面的 FlutterSurfaceView 等工作流程很像,也是 FlutterView 內部的一種繪製成載體,只是有一些自己的獨有特點。FlutterImageView 的主要作用是通過android.media.ImageReader把 Flutter UI 繪製到android.graphics.Canvas上。FlutterView 中 addView 為 FlutterImageView 的方式其實有兩種,一種是前面介紹過的,通過 FlutterView 建構函式引數為 FlutterImageView 的方法實現,另一種是通過呼叫 FlutterView 中的 convertToImageView 方法實現。下面是 FlutterImageView 原始碼中的核心片段:

@TargetApi(19)
public class FlutterImageView extends View implements RenderSurface {
  //......
  //原生控制元件的繪製操作
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //繪製前先更新bitmap資料來源
    if (currentImage != null) {
      updateCurrentBitmap();
    }
    //把bitmap畫到canvas上面
    if (currentBitmap != null) {
      canvas.drawBitmap(currentBitmap, 0, 0, null);
    }
  }

  @TargetApi(29)
  private void updateCurrentBitmap() {
    if (android.os.Build.VERSION.SDK_INT >= 29) {
      final HardwareBuffer buffer = currentImage.getHardwareBuffer();
      currentBitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
      buffer.close();
    } else {
      final Plane[] imagePlanes = currentImage.getPlanes();
      if (imagePlanes.length != 1) {
        return;
      }

      final Plane imagePlane = imagePlanes[0];
      final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
      final int desiredHeight = currentImage.getHeight();

      if (currentBitmap == null
          || currentBitmap.getWidth() != desiredWidth
          || currentBitmap.getHeight() != desiredHeight) {
        currentBitmap =
            Bitmap.createBitmap(
                desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
      }
      ByteBuffer buffer = imagePlane.getBuffer();
      buffer.rewind();
      currentBitmap.copyPixelsFromBuffer(buffer);
    }
  }
  //......
}
複製程式碼

可以看到,FlutterImageView 是一個普通原生 View,也實現了 RenderSurface 介面從而實現類似 FlutterSurfaceView 的特性。它的存在主要是解決我們既需要渲染一個 Flutter UI 又想同時渲染一個 PlatformView(關於 PlatformView 我們後面會有專題文章)的場景,因為 PlatformView 預設實現是在原生 FlutterView 上進行 addView 操作,當我們想在 PlatformView 上繼續蓋一個 Flutter 自己渲染的控制元件就需要使用 FlutterImageView,通過 FlutterImageView 實現了 Surface(ImageReader) 和 Surface 的堆疊。

總結

經過這麼一個篇幅的分析,我們可以簡單粗暴的總結為下圖模式: 在這裡插入圖片描述 這下你懂了嗎?

相關文章