8

NA嵌入Flutter页面

 3 years ago
source link: https://segmentfault.com/a/1190000040524513
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  • 01.Android承载flutter容器
  • 02.过时的NA跳转flutter方案
  • 03.升级版本NA跳转Flutter处理
  • 04.如何处理NA跳转flutter传参
  • 05.思考遇到的几个问题分析
  • 06.Flutter页面关闭时Crash
  • 07.Android引入flutter本质
  • 08.Flutter启动加载流程和优化

00.推荐

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...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK