乐趣区

关于flutter:NA嵌入Flutter页面

目录介绍

  • 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/yangchong2…
  • flutter 混合我的项目代码案例:https://github.com/yangchong2…

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 对象
  • 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/cxz2003…

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();
        }
    }

fluter Utils 工具类库:https://github.com/yangchong2…

flutter 混合我的项目代码案例:https://github.com/yangchong2…

退出移动版