目錄介紹
- 01.Android承載flutter容器
- 02.過時的NA跳轉flutter方案
- 03.升級版本NA跳轉Flutter處理
- 04.如何處理NA跳轉flutter傳參
- 05.思考遇到的幾個問題分析
- 06.Flutter頁面關閉時Crash
- 07.Android引入flutter本質
- 08.Flutter啓動加載流程和優化
00.推薦
- fluter Utils 工具類庫:https://github.com/yangchong211/YCFlutterUtils
- flutter 混合項目代碼案例:https://github.com/yangchong211/YCHybridFlutter
01.Android承載flutter容器
- Android中如何承載flutter頁面呢
- 第一種情況:從Android中弄一個容器,打開一個新的頁面,裝載一個新的flutter頁面。
- 第二種情況:從Android中弄一個容器,在NA的頁面中,裝載一個flutter頁面。【一個頁面,有一部分是NA,有一部分是Flutter】
- 如何將Flutter編寫的頁面嵌入到Activity中
- 官方提供了兩種方式:通過FlutterView和FlutterFragment。
02.過時的NA跳轉flutter方案
2.1 使用FlutterView
- NA添加FlutterView
- 在NA創建一個Activity,在onCreate中創建FlutterView然後添加到佈局中。
- Flutter.createView()方法返回的是一個FlutterView,它繼承自View,我們可以把它當做一個普通的View。
- Flutter.createView()方法的第三個參數傳入了"yc_route"字符串,表示路由名稱,它確定了Flutter中要顯示的Widget。
private void addFlutterView() { // 通過FlutterView引入Flutter編寫的頁面 // Flutter.createView()方法返回的是一個FlutterView,它繼承自View,我們可以把它當做一個普通的View // Flutter.createView()方法的第三個參數傳入了"yc_route"字符串,表示路由名稱,它確定了Flutter中要顯示的Widget flutterView = Flutter.createView(this, getLifecycle(), INIT_ROUTE); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); //添加到佈局中 frameLayout.addView(flutterView, layoutParams); //addContentView(flutterView, layout); }
- Flutter添加頁面
- 在runApp()方法中通過window.defaultRouteName可以獲取到在Flutter.createView()方法中傳入的路由名稱,即"yc_route",
- 之後編寫了一個_widgetForRoute()方法,根據傳入的route字符串顯示相應的Widget。
import 'dart:ui'; import 'package:flutter/material.dart'; void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widget _widgetForRoute(String route) { switch (route) { case 'yc_route': return MyHomePage(title: '匹配到了,這個是flutter頁面'); } }
- 跳轉flutter所在activity黑屏
- debug包這種情況比較明顯,但是release加載很快,可以在進入Flutter頁面的時候提供一個加載loading
2.2 使用FlutterFragment
- NA添加FlutterView
- 在NA創建一個Activity,在onCreate中創建FlutterFragment然後添加到佈局中。
- Flutter.createFragment()方法傳入的參數同樣表示路由名稱,用於確定Flutter要顯示的Widget,返回一個FlutterFragment,該類繼承自Fragment,將該Fragment添加到Activity中就可以了。
private void addFlutterFragment(){ // 通過FlutterFragment引入Flutter編寫的頁面 FragmentTransaction tx = getSupportFragmentManager().beginTransaction(); // Flutter.createFragment()方法傳入的參數同樣表示路由名稱,用於確定Flutter要顯示的Widget // 返回一個FlutterFragment,該類繼承自Fragment,將該Fragment添加到Activity中就可以了。 FlutterFragment flutterFragment = Flutter.createFragment(INIT_ROUTE); tx.replace(R.id.rl_flutter, flutterFragment); tx.commit(); }
- Flutter添加頁面,這個同上
2.3 需要注意的問題
- Flutter版本升級兼容問題
- 由於Flutter版本的更新,上面介紹的內容中存在一些API已經被廢棄的情況。簡單查了一下了解到這個錯誤是Flutter 1.12版本廢棄了io.flutter.facade包導致的,Flutter.createView和Flutter.createFragment這兩個api找不到,固現在已經不使用呢……
- NA跳轉flutter如何添加參數
- NA,這個傳遞參數只需要在路由後面拼接參數即可。
- Flutter,這個接收參數只需要解析參數即可。
- 下面升級版本FlutterView的使用案例中會說到,可以接着往下看……
03.升級版本NA跳轉Flutter處理
3.1 使用新版本FlutterView
- 新版本簡單說明
- 通過FlutterView引入Flutter頁面,以前我們是通過io.flutter.facade包中Flutter類的createView()方法創建出一個FlutterView,然後添加到Activity的佈局中,但是由於io.flutter.facade包的廢棄,該方法已經無法使用。
- 官方的文檔有說明目前不提供在View級別引入Flutter的便捷API,因此如果可能的話,我們應該避免使用FlutterView,但是通過FlutterView引入Flutter頁面也是可行的。
- 需要注意,這裏的FlutterView位於io.flutter.embedding.android包中,和此前我們所創建的FlutterView(位於io.flutter.view包中)是不一樣的。
- NA添加FlutterView
- 在NA創建一個Activity,在onCreate中創建FlutterView然後添加到佈局中。
- 調用FlutterView的attachToFlutterEngine()方法,這個方法的作用就是將Flutter編寫的UI頁面顯示到FlutterView中,注意到這裏傳入了一個flutterEngine參數,它又是什麼呢?flutterEngine的類型爲FlutterEngine,字面意思就是Flutter引擎,它負責在Android端執行Dart代碼,將Flutter編寫的UI顯示到FlutterView的容器中。
private void addFlutterView() { flutterEngine = new FlutterEngine(this); binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger(); flutterEngine.getNavigationChannel().setInitialRoute("yc"); flutterEngine.getDartExecutor().executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ); // 通過FlutterView引入Flutter編寫的頁面 // 這裏的FlutterView位於io.flutter.embedding.android包中 // 和此前我們所創建的FlutterView(位於io.flutter.view包中)是不一樣的。 // 通過查看FlutterView的源碼可以發現它繼承自FrameLayout,因此像一個普通的View那樣添加就可以了。 flutterView = new FlutterView(this); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rlFlutter.addView(flutterView, lp); //flutterEngine.getNavigationChannel().setInitialRoute("yc"); // 關鍵代碼,將Flutter頁面顯示到FlutterView中 // 這個方法的作用就是將Flutter編寫的UI頁面顯示到FlutterView中 // flutterEngine的類型爲FlutterEngine,字面意思就是Flutter引擎 // 它負責在Android端執行Dart代碼,將Flutter編寫的UI顯示到FlutterView/FlutterActivity/FlutterFragment中。 flutterView.attachToFlutterEngine(flutterEngine); // FlutterEngine加載的路由名稱爲"/",我們可以通過下面的代碼指定初始路由名稱 // 傳參的情況沒有變化,直接在路由名稱後面拼接參數就可以 // todo 放在這裏不生效,思考爲什麼 // flutterEngine.getNavigationChannel().setInitialRoute("yc"); }
- Flutter添加頁面
- 在runApp()方法中通過window.defaultRouteName可以獲取到在Flutter.createView()方法中傳入的路由名稱,即"yc_route",
- 之後編寫了一個_widgetForRoute()方法,根據傳入的route字符串顯示相應的Widget。
import 'dart:ui'; import 'package:flutter/material.dart'; void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widget _widgetForRoute(String route) { switch (route) { case 'yc_route': return MyHomePage(title: '匹配到了,這個是flutter頁面'); } }
3.2 使用新版本FlutterFragment
- NA有幾種添加方式
- FlutterFragment.createDefault()
- 通過FlutterFragment.createDefault()創建出FlutterFragment,創建出的Fragment顯示的路由名稱爲"/",如果我們需要指定其他路由名稱就不能使用這個方法了。
- FlutterFragment.withNewEngine()
- 通過FlutterFragment.withNewEngine()獲取到NewEngineFragmentBuilder對象,使用建造者模式構造出FlutterFragment對象,可以通過initialRoute()方法指定初始路由名稱。
- 使用的withNewEngine()方法從名稱上也能看出每次都是創建一個新的FlutterEngine對象來顯示Flutter UI,但是從官方文檔中可以瞭解到每個FlutterEngine對象在顯示出Flutter UI之前是需要一個warm-up(簡單理解爲預熱)期的,這會導致屏幕呈現短暫的空白,解決方式就是預先創建並啓動FlutterEngine,完成warm-up過程,然後將這個FlutterEngine緩存起來,之後使用這個FlutterEngine來顯示出Flutter UI。
- FlutterFragment.withCachedEngine
- 執行的FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)就是將FlutterEngine緩存起來,這裏傳入的"my_engine_id"就相當於緩存名稱。
- 之後調用FlutterFragment.withCachedEngine("my_engine_id").build();獲取緩存的FlutterFragment對象
- FlutterFragment.createDefault()
- NA添加FlutterFragment
- 在NA創建一個Activity,在onCreate中創建FlutterFragment然後添加到佈局中。
- Flutter.createFragment()方法傳入的參數同樣表示路由名稱,用於確定Flutter要顯示的Widget,返回一個FlutterFragment,該類繼承自Fragment,將該Fragment添加到Activity中就可以了。
private void addFlutterView() { // 通過FlutterFragment引入Flutter編寫的頁面 // 通過FlutterFragment.createDefault()創建出FlutterFragment // 需要注意這裏的FlutterFragment位於io.flutter.embedding.android包中 //FlutterFragment flutterFragment = FlutterFragment.createDefault(); // 通過FlutterFragment.withNewEngine()獲取到NewEngineFragmentBuilder對象 FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine(); // 使用建造者模式構造出FlutterFragment對象,可以通過initialRoute()方法指定初始路由名稱。 // 傳遞參數只需要在路由名稱後面進行拼接。 FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute("yc"); FlutterFragment flutterFragment = initialRoute.build(); getSupportFragmentManager() .beginTransaction() .add(R.id.rl_flutter, flutterFragment) .commit(); // 存在的問題 // 使用的withNewEngine()方法從名稱上也能看出每次都是創建一個新的FlutterEngine對象來顯示Flutter UI, // 但是從官方文檔中我們可以瞭解到每個FlutterEngine對象在顯示出Flutter UI之前 // 是需要一個warm-up(不知道能不能翻譯爲預熱)期的,這會導致屏幕呈現短暫的空白, // 解決方式就是預先創建並啓動FlutterEngine,完成warm-up過程,然後將這個FlutterEngine緩存起來, // 之後使用這個FlutterEngine來顯示出Flutter UI。 // 解決方案看:FlutterFragmentCachedActivity // 如何獲取到FlutterEngine對象呢?FlutterFragment中定義了一個getFlutterEngine()方法, // 從方法名來看大概就是獲取FlutterEngine對象。 // 嘗試過創建MethodChannel時傳入flutterFragment.getFlutterEngine().getDartExecutor(), // 運行後會直接拋出空指針異常,異常產生的位置在FlutterFragment的getFlutterEngine()方法中 // 錯誤原因是這裏的delegate爲null,全局搜索一下,發現在FlutterFragment的onAttach()方法中會對delegate賦值,也就是說明此時沒有執行onAttach()方法。 // 猜測這就是由於上面提到過的FlutterEngine的warm-up機制,這是一個耗時過程, // 因此FlutterFragment並不會立刻執行onAttach()方法,導致我們在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法會拋出異常。 // todo 調用下面這句話會空指針崩潰 // FlutterEngine flutterEngine = flutterFragment.getFlutterEngine(); }
- Flutter添加頁面
- 這個同上
3.3 使用新版本FlutterActivity
- 原生引入Flutter頁面方式
- 使用FlutterActivity,這裏的FlutterActivity也是位於io.flutter.embedding.android包下的。
- 首先在清單文件添加代碼
<activity android:name="io.flutter.embedding.android.FlutterActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:theme="@style/AppTheme" android:windowSoftInputMode="adjustResize" />
- 直接啓動這個Activity,代碼如下所示
/** * 和介紹的創建FlutterFragment的三種方式是對應的 * * FlutterActivity顯示的Flutter路由是在創建Intent對象時指定的, * 優點就是使用起來更簡單,缺點就是不夠靈活, * 無法像FlutterView/FlutterFragment那樣只是作爲原生頁面中的一部分展示, * 因此這種方式更適合整個頁面都是由Flutter編寫的場景。 */ private void test(){ // 方式一、FlutterActivity顯示的路由名稱爲"/",不可設置 /*startActivity( FlutterActivity.createDefaultIntent(this) );*/ // 方式二、FlutterActivity顯示的路由名稱可設置,每次都創建一個新的FlutterEngine對象 startActivity( FlutterActivity .withNewEngine() .initialRoute("yc") .build(this) ); // 方式三、FlutterActivity顯示的路由名稱可設置,使用緩存好的FlutterEngine對象 /*startActivity( FlutterActivity .withCachedEngine("my_engine_id") .build(this) );*/ }
- 使用這種方式特點
- 這種方式不需要我們自己創建一個Activity,FlutterActivity顯示的Flutter路由是在創建Intent對象時指定的,優點就是使用起來更簡單,缺點就是不夠靈活,無法像FlutterView/FlutterFragment那樣只是作爲原生頁面中的一部分展示,因此這種方式更適合整個頁面都是由Flutter編寫的場景。
3.4 補充說明問題
- 將Flutter版本更新到了1.17,發現上述代碼運行後FlutterView無法顯示,這個是爲什麼呢?
- 和官方提供的示例flutter_view進行了對比,才發現缺少了下面的代碼:
@Override protected void onResume() { super.onResume(); // flutterEngine.getLifecycleChannel()獲取到的是一個LifecycleChannel對象,類比於MethodChannel, // 作用大概就是將Flutter和原生端的生命週期相互聯繫起來。 flutterEngine.getLifecycleChannel().appIsResumed(); } @Override protected void onPause() { super.onPause(); flutterEngine.getLifecycleChannel().appIsInactive(); } @Override protected void onStop() { super.onStop(); flutterEngine.getLifecycleChannel().appIsPaused(); }
- 可能和生命週期有關係
- flutterEngine.getLifecycleChannel()獲取到的是一個LifecycleChannel對象,類比於MethodChannel,作用大概就是將Flutter和原生端的生命週期相互聯繫起來。
- 這裏分別在onResume()、onPause()和onStop()方法中調用了LifecycleChannel的appIsResumed()、appIsInactive()和appIsPaused()方法,作用就是同步Flutter端與原生端的生命週期。添加上述代碼後,FlutterView就可以正常顯示了。
- 爲何在之後版本要添加
- 可能是FlutterVIew的渲染機制有了一些變化,在接收到原生端對應生命週期方法中發送的通知纔會顯示,具體原理還是要對比一下現在和以前的源碼。
04.如何處理NA跳轉flutter傳參
4.1 NA如何傳遞參數給Flutter?
- 如果需要在頁面跳轉時傳遞參數呢,如何在Flutter代碼中獲取到原生代碼中的參數呢?其實很簡單,只需要在route後面拼接上參數就可以了。
- 以創建FlutterView的方式爲例。
NavigationChannel navigationChannel = flutterEngine.getNavigationChannel(); String route = "yc?{\"name\":\"楊充\"}"; navigationChannel.setInitialRoute(route);
- 以創建FlutterFragment的方式爲例
FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine(); // 使用建造者模式構造出FlutterFragment對象,可以通過initialRoute()方法指定初始路由名稱。 // 傳遞參數只需要在路由名稱後面進行拼接。 String route = "yc?{\"author\":\"楊充\"}"; FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute(route); FlutterFragment flutterFragment = initialRoute.build();
4.2 傳遞參數注意事項
- 將路由名稱和參數間用“?”隔開,就像瀏覽器中的url一樣,參數使用了Json格式傳遞,原因就是方便Flutter端解析,而且對於一些複雜的數據,比如自定義對象,使用Json序列化也很好實現。
4.3 Flutter接收傳遞參數
- 這時候Flutter端通過window.defaultRouteName獲取到的就是路由名稱+參數了,我們需要將路由名稱和參數分開,這就只是單純的字符串處理。
Widget _widgetForRoute() { //var route = window.defaultRouteName; Map<String, dynamic> router = parseRouter(); var route = router["route"]; switch (route) { case 'yc': return AboutMePage(title: '匹配到了,這個是flutter頁面',params : router); } } Map<String, dynamic> parseRouter(){ String url = window.defaultRouteName; // route名稱,路由path路徑名稱 String route = url.indexOf('?') == -1 ? url : url.substring(0, url.indexOf('?')); // 參數Json字符串 String paramsJson = url.indexOf('?') == -1 ? '{}' : url.substring(url.indexOf('?') + 1); // 解析參數 Map<String, dynamic> params = json.decode(paramsJson); params["route"] = route; return params; }
- 通過"?"將路由名稱和參數分開,將參數對應的Json字符串解析爲Map對象,需要導入dart:convert包。
05.思考遇到的幾個問題分析
5.1 setInitialRoute生效問題
- flutterEngine.getNavigationChannel().setInitialRoute("yc")生效問題
//第一種是生效的 private void addFlutterView() { flutterEngine = new FlutterEngine(this); binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger(); flutterEngine.getNavigationChannel().setInitialRoute("yc"); flutterEngine.getDartExecutor().executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ); flutterView = new FlutterView(this); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rlFlutter.addView(flutterView, lp); flutterView.attachToFlutterEngine(flutterEngine); } //第二種是不生效的 private void addFlutterView() { flutterEngine = new FlutterEngine(this); binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger(); flutterEngine.getDartExecutor().executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ); flutterView = new FlutterView(this); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rlFlutter.addView(flutterView, lp); // todo 放在這裏不生效,思考爲什麼 flutterEngine.getNavigationChannel().setInitialRoute("yc"); flutterView.attachToFlutterEngine(flutterEngine); // todo 放在這裏不生效,思考爲什麼 // flutterEngine.getNavigationChannel().setInitialRoute("yc"); }
5.2 flutterFragment.getFlutterEngine()空指針
- 使用場景分析
private void createChannel() { // todo 調用下面這句話會空指針崩潰 FlutterEngine flutterEngine = flutterFragment.getFlutterEngine(); BinaryMessenger binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger(); nativeChannel = new MethodChannel(binaryMessenger, METHOD_CHANNEL, StandardMethodCodec.INSTANCE); } //源碼 @Nullable public FlutterEngine getFlutterEngine() { return delegate.getFlutterEngine(); }
- 錯誤原因是這裏的delegate爲null
- 翻看了一下源碼,發現在FlutterFragment的onAttach()方法中會對delegate賦值,也就是說明此時沒有執行onAttach()方法。
- 問題分析
- FlutterEngine的warm-up機制,這是一個耗時過程,因此FlutterFragment並不會立刻執行onAttach()方法,導致我們在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法會拋出異常。
- 如何解決問題
- 想要解決問題,那就要等到FlutterFragment執行完onAttach()方法在調用getFlutterEngine。那麼怎麼去監聽這個方法執行完呢?
06.Flutter頁面關閉時Crash
- 報錯日誌如下所示
Caused by: java.lang.RuntimeException: Cannot execute operation because FlutterJNI is not attached to native. at io.flutter.embedding.engine.FlutterJNI.ensureAttachedToNative(FlutterJNI.java:259) at io.flutter.embedding.engine.FlutterJNI.onSurfaceDestroyed(FlutterJNI.java:369) at io.flutter.embedding.engine.renderer.FlutterRenderer.stopRenderingToSurface(FlutterRenderer.java:219) at io.flutter.embedding.android.FlutterTextureView.disconnectSurfaceFromRenderer(FlutterTextureView.java:223) at io.flutter.embedding.android.FlutterTextureView.access$400(FlutterTextureView.java:33) at io.flutter.embedding.android.FlutterTextureView$1.onSurfaceTextureDestroyed(FlutterTextureView.java:84) at android.view.TextureView.releaseSurfaceTexture(TextureView.java:261) at android.view.TextureView.onDetachedFromWindowInternal(TextureView.java:232) at android.view.View.dispatchDetachedFromWindow(View.java:22072) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:4747) at android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:6606) at android.view.ViewGroup.removeAllViews(ViewGroup.java:6552) at com.yc.fluttercontainer.FlutterEngineActivity.onDestroy(FlutterEngineActivity.java:292)
- 報錯的代碼如下所示
@Override protected void onDestroy() { super.onDestroy(); if (flutterEngine != null) { flutterEngine.destroy(); } mFlutterContainer.removeAllViews(); mFlutterView.removeAllViews(); if (mRenderSurface != null) { // 打斷內存泄漏 ((FixFlutterTextureView) mRenderSurface).setSurfaceTextureListener(null); } }
- https://blog.csdn.net/cxz200367/article/details/105998930
07.Android引入flutter本質
- 如何理解Android引入flutter頁面
- Android項目引入Flutter本質上是將Flutter編寫的Widget嵌入到Activity中,類似於WebView,容器Activity相當於WebView,route相當於url,有兩種方式FlutterView和FlutterFragment。頁面間的跳轉和傳參可以藉助MethodChannel來實現。
08.Flutter啓動加載優化
8.1 分析flutter的啓動頁面流程
- 通過flutter引擎,整個flutter引擎的相關初始化工作在onCreate方法裏開始的
protected void onCreate(@Nullable Bundle savedInstanceState) { this.switchLaunchThemeForNormalTheme(); super.onCreate(savedInstanceState); this.lifecycle.handleLifecycleEvent(Event.ON_CREATE); this.delegate = new FlutterActivityAndFragmentDelegate(this); //創建綁定引擎等 delegate.onAttach(this); //用於插件、框架恢復狀態 delegate.onActivityCreated(savedInstanceState); //設置窗口背景透明,隱藏 status bar configureWindowForTransparency(); //從這裏分析,這裏是咱們的入口 setContentView(createFlutterView()); this.configureStatusBarForFullscreenFlutterExperience(); }
- 然後接着往下看,會調用到FlutterActivityAndFragmentDelegate類的onCreateView方法
- FlutterActivityAndFragmentDelegate類,flutter的初始化、啓動等操作都是委託給它的。
- 大致瞭解到,創建了一個FlutterSurfaceView 它繼承自surfaceView(我們的flutter頁面也是渲染在這個surface上的)。之後我們用它初始化一個FlutterView,
@NonNull View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.v("FlutterActivityAndFragmentDelegate", "Creating FlutterView."); this.ensureAlive(); if (this.host.getRenderMode() == RenderMode.surface) { //flutter 應用在surface上顯示,所以會進入到這裏 FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(this.host.getActivity(), this.host.getTransparencyMode() == TransparencyMode.transparent); this.host.onFlutterSurfaceViewCreated(flutterSurfaceView); //用flutterSurfaceView 初始化了一個 FlutterView this.flutterView = new FlutterView(this.host.getActivity(), flutterSurfaceView); } else { //否則,應用在TextureView上顯示 FlutterTextureView flutterTextureView = new FlutterTextureView(this.host.getActivity()); this.host.onFlutterTextureViewCreated(flutterTextureView); //用flutterTextureView 初始化了一個 FlutterView this.flutterView = new FlutterView(this.host.getActivity(), flutterTextureView); } this.flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener); //創建一個閃屏view - FlutterSplashView this.flutterSplashView = new FlutterSplashView(this.host.getContext()); if (VERSION.SDK_INT >= 17) { this.flutterSplashView.setId(View.generateViewId()); } else { this.flutterSplashView.setId(486947586); } //顯示閃屏頁 this.flutterSplashView.displayFlutterViewWithSplash(this.flutterView, this.host.provideSplashScreen()); Log.v("FlutterActivityAndFragmentDelegate", "Attaching FlutterEngine to FlutterView."); //所創建surface 綁定到engine上 this.flutterView.attachToFlutterEngine(this.flutterEngine); return this.flutterSplashView; }
- 隨後我們再創建一個FlutterSplashView (繼承FrameLayout)。重要看調用displayFlutterViewWithSplash()方法。
- 看到這裏可知,通過splashScreen(是個接口),具體看接口實現類,然後創建一個splashScreenView,最後添加到flutter的佈局中
public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) { if (this.splashScreenView != null) { this.removeView(this.splashScreenView); } //省略大量代碼 this.flutterView = flutterView; this.addView(flutterView); this.splashScreen = splashScreen; if (splashScreen != null) { if (this.isSplashScreenNeededNow()) { Log.v(TAG, "Showing splash screen UI."); this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState); //添加 splashScreenView this.addView(this.splashScreenView); flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener); } } }
- 那麼什麼時候移除這個啓動Splash佈局呢?在創建FlutterSplashView時,添加了一個完成事件的監聽,當flutter加載成功後纔將它移除。
public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.onTransitionComplete = new Runnable() { public void run() { FlutterSplashView.this.removeView(FlutterSplashView.this.splashScreenView); FlutterSplashView.this.previousCompletedSplashIsolate = FlutterSplashView.this.transitioningIsolateId; } }; this.setSaveEnabled(true); }
- 得出結論
- 可以發現在閃屏頁的顯示到引擎的啓動及flutter 頁面的顯示會有一個很長的過程,而直到flutter 頁面的顯示,這個閃屏頁纔會被移除掉。
8.2 如何優化flutter啓動屏
- 第一種方案
- Flutter由於引擎的創建和初始化需要一定時間,所以也提供了一個過渡方案(默認是白屏)。如下所示,你可以設置一下背景
AndroidManifest.xml下的 <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background"/>
- 第二種方案
@Nullable @Override public SplashScreen provideSplashScreen() { //創建自定義flutter啓動屏view return new FlutterSplashView(); } public class FlutterSplashView implements SplashScreen { @Nullable @Override public View createSplashView(@NonNull Context context, @Nullable Bundle savedInstanceState) { View v = new View(context); v.setBackgroundColor(Color.WHITE); return v; } @Override public void transitionToFlutter(@NonNull Runnable onTransitionComplete) { onTransitionComplete.run(); } }