這是我參與8月更文挑戰的第5天,活動詳情檢視: 8月更文挑戰
Flutter 系列文章連載~
- 《Flutter Android 工程結構及應用層編譯原始碼深入分析》
- 《Flutter 命令本質之 Flutter tools 機制原始碼深入分析》
- 《Flutter 的 runApp 與三棵樹誕生流程原始碼分析》
- 《Flutter Android 端 Activity/Fragment 流程原始碼分析》
- 《Flutter Android 端 FlutterInjector 及依賴流程原始碼分析》
- 《Flutter Android 端 FlutterEngine Java 相關流程原始碼分析》
- 《Flutter Android 端 FlutterView 相關流程原始碼分析》
- 《Flutter 繪製動機 VSYNC 流程原始碼全方位分析》
背景
前面系列文章我們分析了 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#surface
和io.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 的堆疊。
總結
經過這麼一個篇幅的分析,我們可以簡單粗暴的總結為下圖模式: 這下你懂了嗎?