NA嵌入Flutter頁面

目錄介紹

  • 01.Android承載flutter容器
  • 02.過時的NA跳轉(zhuǎn)flutter方案
  • 03.升級版本NA跳轉(zhuǎn)Flutter處理
  • 04.如何處理NA跳轉(zhuǎn)flutter傳參
  • 05.思考遇到的幾個問題分析
  • 06.Flutter頁面關(guān)閉時Crash
  • 07.Android引入flutter本質(zhì)
  • 08.Flutter啟動加載流程和優(yōu)化

00.推薦

01.Android承載flutter容器

  • Android中如何承載flutter頁面呢
    • 第一種情況:從Android中弄一個容器枚尼,打開一個新的頁面增蹭,裝載一個新的flutter頁面闹究。
    • 第二種情況:從Android中弄一個容器,在NA的頁面中信认,裝載一個flutter頁面【椋【一個頁面嫁赏,有一部分是NA,有一部分是Flutter】
  • 如何將Flutter編寫的頁面嵌入到Activity中
    • 官方提供了兩種方式:通過FlutterView和FlutterFragment油挥。

02.過時的NA跳轉(zhuǎn)flutter方案

2.1 使用FlutterView

  • NA添加FlutterView
    • 在NA創(chuàng)建一個Activity潦蝇,在onCreate中創(chuàng)建FlutterView然后添加到布局中。
    • Flutter.createView()方法返回的是一個FlutterView深寥,它繼承自View攘乒,我們可以把它當(dāng)做一個普通的View。
    • Flutter.createView()方法的第三個參數(shù)傳入了"yc_route"字符串惋鹅,表示路由名稱则酝,它確定了Flutter中要顯示的Widget。
    private void addFlutterView() {
        // 通過FlutterView引入Flutter編寫的頁面
        // Flutter.createView()方法返回的是一個FlutterView负饲,它繼承自View堤魁,我們可以把它當(dāng)做一個普通的View
        // Flutter.createView()方法的第三個參數(shù)傳入了"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()方法洞坑,根據(jù)傳入的route字符串顯示相應(yīng)的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頁面');
      }
    }
    
  • 跳轉(zhuǎn)flutter所在activity黑屏
    • debug包這種情況比較明顯迟杂,但是release加載很快,可以在進(jìn)入Flutter頁面的時候提供一個加載loading

2.2 使用FlutterFragment

  • NA添加FlutterView
    • 在NA創(chuàng)建一個Activity本慕,在onCreate中創(chuàng)建FlutterFragment然后添加到布局中排拷。
    • Flutter.createFragment()方法傳入的參數(shù)同樣表示路由名稱,用于確定Flutter要顯示的Widget锅尘,返回一個FlutterFragment监氢,該類繼承自Fragment,將該Fragment添加到Activity中就可以了藤违。
    private void addFlutterFragment(){
        // 通過FlutterFragment引入Flutter編寫的頁面
        FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
        // Flutter.createFragment()方法傳入的參數(shù)同樣表示路由名稱浪腐,用于確定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版本的更新璧榄,上面介紹的內(nèi)容中存在一些API已經(jīng)被廢棄的情況特漩。簡單查了一下了解到這個錯誤是Flutter 1.12版本廢棄了io.flutter.facade包導(dǎo)致的吧雹,F(xiàn)lutter.createView和Flutter.createFragment這兩個api找不到,固現(xiàn)在已經(jīng)不使用呢……
  • NA跳轉(zhuǎn)flutter如何添加參數(shù)
    • NA涂身,這個傳遞參數(shù)只需要在路由后面拼接參數(shù)即可吮炕。
    • Flutter,這個接收參數(shù)只需要解析參數(shù)即可访得。
    • 下面升級版本FlutterView的使用案例中會說到龙亲,可以接著往下看……

03.升級版本NA跳轉(zhuǎn)Flutter處理

3.1 使用新版本FlutterView

  • 新版本簡單說明
    • 通過FlutterView引入Flutter頁面,以前我們是通過io.flutter.facade包中Flutter類的createView()方法創(chuàng)建出一個FlutterView悍抑,然后添加到Activity的布局中鳄炉,但是由于io.flutter.facade包的廢棄,該方法已經(jīng)無法使用搜骡。
    • 官方的文檔有說明目前不提供在View級別引入Flutter的便捷API拂盯,因此如果可能的話,我們應(yīng)該避免使用FlutterView记靡,但是通過FlutterView引入Flutter頁面也是可行的谈竿。
    • 需要注意,這里的FlutterView位于io.flutter.embedding.android包中摸吠,和此前我們所創(chuàng)建的FlutterView(位于io.flutter.view包中)是不一樣的空凸。
  • NA添加FlutterView
    • 在NA創(chuàng)建一個Activity,在onCreate中創(chuàng)建FlutterView然后添加到布局中寸痢。
    • 調(diào)用FlutterView的attachToFlutterEngine()方法呀洲,這個方法的作用就是將Flutter編寫的UI頁面顯示到FlutterView中,注意到這里傳入了一個flutterEngine參數(shù)啼止,它又是什么呢道逗?flutterEngine的類型為FlutterEngine,字面意思就是Flutter引擎献烦,它負(fù)責(zé)在Android端執(zhí)行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包中
        // 和此前我們所創(chuàng)建的FlutterView(位于io.flutter.view包中)是不一樣的巩那。
        // 通過查看FlutterView的源碼可以發(fā)現(xiàn)它繼承自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");
    
        // 關(guān)鍵代碼拢操,將Flutter頁面顯示到FlutterView中
        // 這個方法的作用就是將Flutter編寫的UI頁面顯示到FlutterView中
        // flutterEngine的類型為FlutterEngine锦亦,字面意思就是Flutter引擎
        // 它負(fù)責(zé)在Android端執(zhí)行Dart代碼,將Flutter編寫的UI顯示到FlutterView/FlutterActivity/FlutterFragment中令境。
        flutterView.attachToFlutterEngine(flutterEngine);
    
        // FlutterEngine加載的路由名稱為"/"杠园,我們可以通過下面的代碼指定初始路由名稱
        // 傳參的情況沒有變化,直接在路由名稱后面拼接參數(shù)就可以
        // todo 放在這里不生效舔庶,思考為什么
        // flutterEngine.getNavigationChannel().setInitialRoute("yc");
    }
    
  • Flutter添加頁面
    • 在runApp()方法中通過window.defaultRouteName可以獲取到在Flutter.createView()方法中傳入的路由名稱抛蚁,即"yc_route"陈醒,
    • 之后編寫了一個_widgetForRoute()方法,根據(jù)傳入的route字符串顯示相應(yīng)的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()創(chuàng)建出FlutterFragment,創(chuàng)建出的Fragment顯示的路由名稱為"/"肚逸,如果我們需要指定其他路由名稱就不能使用這個方法了爷辙。
    • FlutterFragment.withNewEngine()
      • 通過FlutterFragment.withNewEngine()獲取到NewEngineFragmentBuilder對象,使用建造者模式構(gòu)造出FlutterFragment對象朦促,可以通過initialRoute()方法指定初始路由名稱膝晾。
      • 使用的withNewEngine()方法從名稱上也能看出每次都是創(chuàng)建一個新的FlutterEngine對象來顯示Flutter UI,但是從官方文檔中可以了解到每個FlutterEngine對象在顯示出Flutter UI之前是需要一個warm-up(簡單理解為預(yù)熱)期的务冕,這會導(dǎo)致屏幕呈現(xiàn)短暫的空白血当,解決方式就是預(yù)先創(chuàng)建并啟動FlutterEngine,完成warm-up過程禀忆,然后將這個FlutterEngine緩存起來臊旭,之后使用這個FlutterEngine來顯示出Flutter UI。
    • FlutterFragment.withCachedEngine
      • 執(zhí)行的FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)就是將FlutterEngine緩存起來箩退,這里傳入的"my_engine_id"就相當(dāng)于緩存名稱离熏。
      • 之后調(diào)用FlutterFragment.withCachedEngine("my_engine_id").build();獲取緩存的FlutterFragment對象
  • NA添加FlutterFragment
    • 在NA創(chuàng)建一個Activity,在onCreate中創(chuàng)建FlutterFragment然后添加到布局中乏德。
    • Flutter.createFragment()方法傳入的參數(shù)同樣表示路由名稱撤奸,用于確定Flutter要顯示的Widget,返回一個FlutterFragment喊括,該類繼承自Fragment,將該Fragment添加到Activity中就可以了矢棚。
    private void addFlutterView() {
        // 通過FlutterFragment引入Flutter編寫的頁面
        // 通過FlutterFragment.createDefault()創(chuàng)建出FlutterFragment
        // 需要注意這里的FlutterFragment位于io.flutter.embedding.android包中
        //FlutterFragment flutterFragment = FlutterFragment.createDefault();
    
        // 通過FlutterFragment.withNewEngine()獲取到NewEngineFragmentBuilder對象
        FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine();
        // 使用建造者模式構(gòu)造出FlutterFragment對象郑什,可以通過initialRoute()方法指定初始路由名稱。
        // 傳遞參數(shù)只需要在路由名稱后面進(jìn)行拼接蒲肋。
        FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute("yc");
        FlutterFragment flutterFragment = initialRoute.build();
    
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.rl_flutter, flutterFragment)
                .commit();
    
    
        // 存在的問題
        // 使用的withNewEngine()方法從名稱上也能看出每次都是創(chuàng)建一個新的FlutterEngine對象來顯示Flutter UI蘑拯,
        // 但是從官方文檔中我們可以了解到每個FlutterEngine對象在顯示出Flutter UI之前
        // 是需要一個warm-up(不知道能不能翻譯為預(yù)熱)期的,這會導(dǎo)致屏幕呈現(xiàn)短暫的空白兜粘,
        // 解決方式就是預(yù)先創(chuàng)建并啟動FlutterEngine申窘,完成warm-up過程,然后將這個FlutterEngine緩存起來孔轴,
        // 之后使用這個FlutterEngine來顯示出Flutter UI剃法。
        // 解決方案看:FlutterFragmentCachedActivity
    
    
        // 如何獲取到FlutterEngine對象呢?FlutterFragment中定義了一個getFlutterEngine()方法路鹰,
        // 從方法名來看大概就是獲取FlutterEngine對象贷洲。
        // 嘗試過創(chuàng)建MethodChannel時傳入flutterFragment.getFlutterEngine().getDartExecutor()收厨,
        // 運(yùn)行后會直接拋出空指針異常,異常產(chǎn)生的位置在FlutterFragment的getFlutterEngine()方法中
        // 錯誤原因是這里的delegate為null优构,全局搜索一下诵叁,發(fā)現(xiàn)在FlutterFragment的onAttach()方法中會對delegate賦值,也就是說明此時沒有執(zhí)行onAttach()方法钦椭。
        // 猜測這就是由于上面提到過的FlutterEngine的warm-up機(jī)制拧额,這是一個耗時過程,
        // 因此FlutterFragment并不會立刻執(zhí)行onAttach()方法彪腔,導(dǎo)致我們在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法會拋出異常势腮。
        // todo 調(diào)用下面這句話會空指針崩潰
        // 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捎拯,代碼如下所示
    /**
     * 和介紹的創(chuàng)建FlutterFragment的三種方式是對應(yīng)的
     *
     * FlutterActivity顯示的Flutter路由是在創(chuàng)建Intent對象時指定的,
     * 優(yōu)點就是使用起來更簡單,缺點就是不夠靈活,
     * 無法像FlutterView/FlutterFragment那樣只是作為原生頁面中的一部分展示,
     * 因此這種方式更適合整個頁面都是由Flutter編寫的場景盲厌。
     */
    private void test(){
        // 方式一署照、FlutterActivity顯示的路由名稱為"/",不可設(shè)置
        /*startActivity(
                FlutterActivity.createDefaultIntent(this)
        );*/
    
        // 方式二吗浩、FlutterActivity顯示的路由名稱可設(shè)置建芙,每次都創(chuàng)建一個新的FlutterEngine對象
        startActivity(
                FlutterActivity
                        .withNewEngine()
                        .initialRoute("yc")
                        .build(this)
        );
    
        // 方式三、FlutterActivity顯示的路由名稱可設(shè)置懂扼,使用緩存好的FlutterEngine對象
        /*startActivity(
                FlutterActivity
                        .withCachedEngine("my_engine_id")
                        .build(this)
        );*/
    }
    
  • 使用這種方式特點
    • 這種方式不需要我們自己創(chuàng)建一個Activity禁荸,F(xiàn)lutterActivity顯示的Flutter路由是在創(chuàng)建Intent對象時指定的,優(yōu)點就是使用起來更簡單阀湿,缺點就是不夠靈活赶熟,無法像FlutterView/FlutterFragment那樣只是作為原生頁面中的一部分展示,因此這種方式更適合整個頁面都是由Flutter編寫的場景陷嘴。

3.4 補(bǔ)充說明問題

  • 將Flutter版本更新到了1.17映砖,發(fā)現(xiàn)上述代碼運(yùn)行后FlutterView無法顯示,這個是為什么呢灾挨?
    • 和官方提供的示例flutter_view進(jìn)行了對比邑退,才發(fā)現(xiàn)缺少了下面的代碼:
    @Override
    protected void onResume() {
        super.onResume();
        // flutterEngine.getLifecycleChannel()獲取到的是一個LifecycleChannel對象,類比于MethodChannel劳澄,
        // 作用大概就是將Flutter和原生端的生命周期相互聯(lián)系起來地技。
        flutterEngine.getLifecycleChannel().appIsResumed();
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        flutterEngine.getLifecycleChannel().appIsInactive();
    }
    
    @Override
    protected void onStop() {
        super.onStop();
        flutterEngine.getLifecycleChannel().appIsPaused();
    }
    
  • 可能和生命周期有關(guān)系
    • flutterEngine.getLifecycleChannel()獲取到的是一個LifecycleChannel對象,類比于MethodChannel秒拔,作用大概就是將Flutter和原生端的生命周期相互聯(lián)系起來莫矗。
    • 這里分別在onResume()、onPause()和onStop()方法中調(diào)用了LifecycleChannel的appIsResumed()、appIsInactive()和appIsPaused()方法趣苏,作用就是同步Flutter端與原生端的生命周期狡相。添加上述代碼后,F(xiàn)lutterView就可以正常顯示了食磕。
  • 為何在之后版本要添加
    • 可能是FlutterVIew的渲染機(jī)制有了一些變化尽棕,在接收到原生端對應(yīng)生命周期方法中發(fā)送的通知才會顯示,具體原理還是要對比一下現(xiàn)在和以前的源碼彬伦。

04.如何處理NA跳轉(zhuǎn)flutter傳參

4.1 NA如何傳遞參數(shù)給Flutter滔悉?

  • 如果需要在頁面跳轉(zhuǎn)時傳遞參數(shù)呢,如何在Flutter代碼中獲取到原生代碼中的參數(shù)呢单绑?其實很簡單回官,只需要在route后面拼接上參數(shù)就可以了。
  • 以創(chuàng)建FlutterView的方式為例搂橙。
    NavigationChannel navigationChannel = flutterEngine.getNavigationChannel();
    String route = "yc?{\"name\":\"楊充\"}";
    navigationChannel.setInitialRoute(route);
    
  • 以創(chuàng)建FlutterFragment的方式為例
    FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine();
    // 使用建造者模式構(gòu)造出FlutterFragment對象歉提,可以通過initialRoute()方法指定初始路由名稱。
    // 傳遞參數(shù)只需要在路由名稱后面進(jìn)行拼接区转。
    String route = "yc?{\"author\":\"楊充\"}";
    FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute(route);
    FlutterFragment flutterFragment = initialRoute.build();
    

4.2 傳遞參數(shù)注意事項

  • 將路由名稱和參數(shù)間用“苔巨?”隔開,就像瀏覽器中的url一樣废离,參數(shù)使用了Json格式傳遞侄泽,原因就是方便Flutter端解析,而且對于一些復(fù)雜的數(shù)據(jù)蜻韭,比如自定義對象悼尾,使用Json序列化也很好實現(xiàn)。

4.3 Flutter接收傳遞參數(shù)

  • 這時候Flutter端通過window.defaultRouteName獲取到的就是路由名稱+參數(shù)了肖方,我們需要將路由名稱和參數(shù)分開闺魏,這就只是單純的字符串處理。
      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('?'));
        // 參數(shù)Json字符串
        String paramsJson = url.indexOf('?') == -1 ? '{}' : url.substring(url.indexOf('?') + 1);
        // 解析參數(shù)
        Map<String, dynamic> params = json.decode(paramsJson);
        params["route"] = route;
        return params;
      }
    
  • 通過"?"將路由名稱和參數(shù)分開,將參數(shù)對應(yīng)的Json字符串解析為Map對象活翩,需要導(dǎo)入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 調(diào)用下面這句話會空指針崩潰
        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
    • 翻看了一下源碼,發(fā)現(xiàn)在FlutterFragment的onAttach()方法中會對delegate賦值吨岭,也就是說明此時沒有執(zhí)行onAttach()方法拉宗。
  • 問題分析
    • FlutterEngine的warm-up機(jī)制,這是一個耗時過程,因此FlutterFragment并不會立刻執(zhí)行onAttach()方法旦事,導(dǎo)致我們在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法會拋出異常魁巩。
  • 如何解決問題
    • 想要解決問題,那就要等到FlutterFragment執(zhí)行完onAttach()方法在調(diào)用getFlutterEngine姐浮。那么怎么去監(jiān)聽這個方法執(zhí)行完呢谷遂?

06.Flutter頁面關(guān)閉時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) {
            // 打斷內(nèi)存泄漏
            ((FixFlutterTextureView) mRenderSurface).setSurfaceTextureListener(null);
        }
    }
    
  • https://blog.csdn.net/cxz200367/article/details/105998930

07.Android引入flutter本質(zhì)

  • 如何理解Android引入flutter頁面
    • Android項目引入Flutter本質(zhì)上是將Flutter編寫的Widget嵌入到Activity中,類似于WebView卖鲤,容器Activity相當(dāng)于WebView肾扰,route相當(dāng)于url,有兩種方式FlutterView和FlutterFragment蛋逾。頁面間的跳轉(zhuǎn)和傳參可以借助MethodChannel來實現(xiàn)集晚。

08.Flutter啟動加載優(yōu)化

8.1 分析flutter的啟動頁面流程

  • 通過flutter引擎,整個flutter引擎的相關(guān)初始化工作在onCreate方法里開始的
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        this.switchLaunchThemeForNormalTheme();
        super.onCreate(savedInstanceState);
        this.lifecycle.handleLifecycleEvent(Event.ON_CREATE);
        this.delegate = new FlutterActivityAndFragmentDelegate(this);
        //創(chuàng)建綁定引擎等
        delegate.onAttach(this);
        //用于插件区匣、框架恢復(fù)狀態(tài)
        delegate.onActivityCreated(savedInstanceState);
        //設(shè)置窗口背景透明偷拔,隱藏 status bar
        configureWindowForTransparency();
        //從這里分析,這里是咱們的入口
        setContentView(createFlutterView());
        this.configureStatusBarForFullscreenFlutterExperience();
    }
    
  • 然后接著往下看亏钩,會調(diào)用到FlutterActivityAndFragmentDelegate類的onCreateView方法
    • FlutterActivityAndFragmentDelegate類莲绰,flutter的初始化、啟動等操作都是委托給它的铸屉。
    • 大致了解到钉蒲,創(chuàng)建了一個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 應(yīng)用在surface上顯示顷啼,所以會進(jìn)入到這里
            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 {
            //否則,應(yīng)用在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);
        //創(chuàng)建一個閃屏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.");
        //所創(chuàng)建surface 綁定到engine上
        this.flutterView.attachToFlutterEngine(this.flutterEngine);
        return this.flutterSplashView;
    }
    
  • 隨后我們再創(chuàng)建一個FlutterSplashView (繼承FrameLayout)昌屉。重要看調(diào)用displayFlutterViewWithSplash()方法钙蒙。
    • 看到這里可知,通過splashScreen(是個接口)间驮,具體看接口實現(xiàn)類躬厌,然后創(chuàng)建一個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布局呢竞帽?在創(chuàng)建FlutterSplashView時扛施,添加了一個完成事件的監(jiān)聽,當(dāng)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);
    }
    
  • 得出結(jié)論
    • 可以發(fā)現(xiàn)在閃屏頁的顯示到引擎的啟動及flutter 頁面的顯示會有一個很長的過程疙渣,而直到flutter 頁面的顯示,這個閃屏頁才會被移除掉堆巧。

8.2 如何優(yōu)化flutter啟動屏

  • 第一種方案
    • Flutter由于引擎的創(chuàng)建和初始化需要一定時間妄荔,所以也提供了一個過渡方案(默認(rèn)是白屏)泼菌。如下所示,你可以設(shè)置一下背景
    AndroidManifest.xml下的
    <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"/>
    
  • 第二種方案
    @Nullable
    @Override
    public SplashScreen provideSplashScreen() {
        //創(chuàng)建自定義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();
        }
    }
    

fluter Utils 工具類庫:https://github.com/yangchong211/YCFlutterUtils

flutter 混合項目代碼案例:https://github.com/yangchong211/YCHybridFlutter

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啦租,一起剝皮案震驚了整個濱河市哗伯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篷角,老刑警劉巖焊刹,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異内地,居然都是意外死亡伴澄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門阱缓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來非凌,“玉大人,你說我怎么就攤上這事荆针〕ㄎ耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵航背,是天一觀的道長喉悴。 經(jīng)常有香客問我,道長玖媚,這世上最難降的妖魔是什么箕肃? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮今魔,結(jié)果婚禮上勺像,老公的妹妹穿的比我還像新娘。我一直安慰自己错森,他們只是感情好吟宦,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涩维,像睡著了一般殃姓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓦阐,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蜗侈,我揣著相機(jī)與錄音,去河邊找鬼睡蟋。 笑死宛篇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的薄湿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼豺瘤!你這毒婦竟也來了吆倦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坐求,失蹤者是張志新(化名)和其女友劉穎蚕泽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桥嗤,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡须妻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泛领。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荒吏。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渊鞋,靈堂內(nèi)的尸體忽然破棺而出绰更,到底是詐尸還是另有隱情,我是刑警寧澤锡宋,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布儡湾,位于F島的核電站,受9級特大地震影響执俩,放射性物質(zhì)發(fā)生泄漏徐钠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一役首、第九天 我趴在偏房一處隱蔽的房頂上張望尝丐。 院中可真熱鬧,春花似錦宋税、人聲如沸摊崭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呢簸。三九已至,卻和暖如春乏屯,著一層夾襖步出監(jiān)牢的瞬間根时,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工辰晕, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蛤迎,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓含友,卻偏偏與公主長得像替裆,于是被迫代替她去往敵國和親校辩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容