目录介绍

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

fluter Utils 工具类库:https://github.com/yangchong211/YCFlutterUtils

flutter 混合项目代码案例:https://github.com/yangchong211/YCHybridFlutter

NA嵌入Flutter页面的更多相关文章

  1. WebIM(5)----将WebIM嵌入到页面中

    在之前的文章中,已经开发了一个简单的WebIM,但是这个WebIM是在独立的页面中的,今天发布的WebIM是一个可以嵌入到自己网页中的版本,你只需添加少量的代码,就可以在页面中嵌入一个WebIM.不过 ...

  2. iPhone的App嵌入html页面问题

    测试环境:iPhone ios 11.0.3 问题:iPhone App嵌入HTML页面,页面拉动到底部时,手势从屏幕底部边缘开始往上拉动,页面出现白色图层,且html页面一屏外的会卡住,无法滚动,需 ...

  3. react native (2) 嵌入h5页面 设置顶部导航

    嵌入h5页面 1.新建好页面 2. import { WebView } from 'react-native'; 3.<WebView source={{ uri: '要引入的页面路径' }} ...

  4. vue在页面嵌入别的页面或者是视频2

    vue在页面嵌入别的页面或者是视频 以下是嵌入页面 <iframe name="myiframe" id="myrame" src="http: ...

  5. Saiku通过iframe嵌入web页面(六)

    Saiku通过iframe嵌入系统页面 前提: Saiku已安装好,并且配置了数据源,熟练了saiku的基本使用. 一.将整个Saiku嵌入页面 在web项目中,新建index.jsp页面,内容如下: ...

  6. SVG系列教程:SVG简介与嵌入HTML页面的方式

    地址:http://www.w3cplus.com/html5/svg-introduction-and-embedded-html-page.html 随着技术向前的推进,SVG相关的讨论也越渐频繁 ...

  7. Flex嵌入HTML页面

    这段时间一直在苦心研究Flex,今天突然想,我们平时都是把swf放到网页中,怎么才能把网页嵌入到Flex中呢?我查了一些资料,然后经过自己的不懈努力,终于搞定. 为了方便,写了个嵌入HTML页面的代理 ...

  8. SSI注入--嵌入HTML页面中的指令,类似jsp、asp对现有HTML页面增加动态生成内容,见后面例子

    SSI注入漏洞总结 from:https://www.mi1k7ea.com/2019/09/28/SSI%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E ...

  9. 安卓 apk 嵌入H5页面只显示部分

    安卓 apk 嵌入H5页面只显示部分(有空白页出现) 解决方案 没有加载的是js部分,需要在安卓那边加上一串代码 webView.getSetting().setDomStorageEnabled(t ...

  10. flutter页面间跳转和传参-Navigator的使用

    flutter页面间跳转和传参-Navigator的使用 概述 flutter中的默认导航分成两种,一种是命名的路由,一种是构建路由. 命名路由 这种路由需要一开始现在创建App的时候定义 new M ...

随机推荐

  1. Kafka-如何重设消费者位移(重设OFFSET)

    1. 为什么要重设消费者组位移? 我们知道,Kafka 和传统的消息引擎在设计上是有很大区别的,其中一个比较显著的区别就是,Kafka 的消费者读取消息是可以重演的(replayable). 像 Ra ...

  2. Hbase-执行hbase shell命令时提示:ERROR: KeeperErrorCode = NoNode for /hbase/master

    1.问题描述 执行hbase  shell命令时提示: ERROR: KeeperErrorCode = NoNode for /hbase/master 2.问题原因 这是与因为服务器重启后Hado ...

  3. Pandas练习

    背景介绍 本数据集包括了2015年至2017年我国36个主要一线城市.特区的一些年度数据,包括产值.人口.就业.教育.医疗.经济贸易.房地产投资等方面. 包含文件: 2015年国内主要城市年度数据.c ...

  4. Python异步编程原理篇之IO多路复用模块selector

    selector 简介 selector 是一个实现了IO复用模型的python包,实现了IO多路复用模型的 select.poll 和 epoll 等函数. 它允许程序同时监听多个文件描述符(例如套 ...

  5. Ubuntu下通过Wine安装LTSpice 17.1.8

    LTSpice LTSpice 是常用的电路模拟软件, 但是只有 Windows 版本和 Mac 版本, 在 Linux 下需要用 Wine 运行. 以下说明如何在 Ubuntu 下安装最新的 LTS ...

  6. 适用于Spring Boot Jar的启停部署脚本

    shell脚本参数 使用-z或-n对一个变量判空时, 若直接使用[ -n ARG ]这种形式,当{ARG}中有空格将会报错, line 27: [: sd: binary operator expec ...

  7. Swoole从入门到入土(15)——WebSocket服务器[初步接触]

    WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议.换句话说,Websocket让web可以与服务端实现长连接. 在Swoole中,通过内置的 WebSock ...

  8. RMAN REPORT NEED BACKUP DAYS 5

    47.You issue the following command on the RMAN prompt. REPORT NEED BACKUP DAYS 5; Which statement is ...

  9. 突破Windows的极限

    偶然碰到这类技术博客,甚感欣慰,但奈何技术水平达不到,很多都难以理解,故记录在此,用作日后学习. 国内有类似的中文翻译,比如:突破Windows极限:物理内存 但是外文链接已经失效,看不到原汁原味的英 ...

  10. win32-改变显示器的亮度

    调用SetMonitorBrightness 代码示例: #pragma comment(lib, "dxva2.lib") #include <windows.h> ...