目录介绍
01.Android承载flutter容器
02.过时的NA跳转flutter方案
03.升级版本NA跳转Flutter处理
04.如何处理NA跳转flutter传参
05.思考遇到的几个问题分析
06.Flutter页面关闭时Crash
07.Android引入flutter本质
08.Flutter启动加载流程和优化
00.推荐
fluter Utils 工具类库: https://github.com/yangchong211/YCFlutterUtils
flutter 混合项目代码案例: https://github.com/yangchong211/YCHybridFlutter
01.Android承载flutter容器
Android中如何承载flutter页面呢
第一种情况:从Android中弄一个容器,打开一个新的页面,装载一个新的flutter页面。
第二种情况:从Android中弄一个容器,在NA的页面中,装载一个flutter页面。【一个页面,有一部分是NA,有一部分是Flutter】
如何将Flutter编写的页面嵌入到Activity中
官方提供了两种方式:通过FlutterView和FlutterFragment。
02.过时的NA跳转flutter方案
2.1 使用FlutterView
-
NA添加FlutterView
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
在NA创建一个Activity,在onCreate中创建FlutterView然后添加到布局中。
Flutter.createView()方法返回的是一个FlutterView,它继承自View,我们可以把它当做一个普通的View。
Flutter.createView()方法的第三个参数传入了"yc_route"字符串,表示路由名称,它确定了Flutter中要显示的Widget。
-
Flutter添加页面
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
在runApp()方法中通过window.defaultRouteName可以获取到在Flutter.createView()方法中传入的路由名称,即"yc_route",
之后编写了一个_widgetForRoute()方法,根据传入的route字符串显示相应的Widget。
跳转flutter所在activity黑屏
debug包这种情况比较明显,但是release加载很快,可以在进入Flutter页面的时候提供一个加载loading
2.2 使用FlutterFragment
-
NA添加FlutterView
1.
2.
3.
4.
5.
6.
7.
8.
9.
在NA创建一个Activity,在onCreate中创建FlutterFragment然后添加到布局中。
Flutter.createFragment()方法传入的参数同样表示路由名称,用于确定Flutter要显示的Widget,返回一个FlutterFragment,该类继承自Fragment,将该Fragment添加到Activity中就可以了。
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
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
在NA创建一个Activity,在onCreate中创建FlutterView然后添加到布局中。
调用FlutterView的attachToFlutterEngine()方法,这个方法的作用就是将Flutter编写的UI页面显示到FlutterView中,注意到这里传入了一个flutterEngine参数,它又是什么呢?flutterEngine的类型为FlutterEngine,字面意思就是Flutter引擎,它负责在Android端执行Dart代码,将Flutter编写的UI显示到FlutterView的容器中。
-
Flutter添加页面
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
在runApp()方法中通过window.defaultRouteName可以获取到在Flutter.createView()方法中传入的路由名称,即"yc_route",
之后编写了一个_widgetForRoute()方法,根据传入的route字符串显示相应的Widget。
3.2 使用新版本FlutterFragment
NA有几种添加方式
执行的FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)就是将FlutterEngine缓存起来,这里传入的"my_engine_id"就相当于缓存名称。
之后调用FlutterFragment.withCachedEngine("my_engine_id").build();获取缓存的FlutterFragment对象
通过FlutterFragment.withNewEngine()获取到NewEngineFragmentBuilder对象,使用建造者模式构造出FlutterFragment对象,可以通过initialRoute()方法指定初始路由名称。
使用的withNewEngine()方法从名称上也能看出每次都是创建一个新的FlutterEngine对象来显示Flutter UI,但是从官方文档中可以了解到每个FlutterEngine对象在显示出Flutter UI之前是需要一个warm-up(简单理解为预热)期的,这会导致屏幕呈现短暂的空白,解决方式就是预先创建并启动FlutterEngine,完成warm-up过程,然后将这个FlutterEngine缓存起来,之后使用这个FlutterEngine来显示出Flutter UI。
通过FlutterFragment.createDefault()创建出FlutterFragment,创建出的Fragment显示的路由名称为"/",如果我们需要指定其他路由名称就不能使用这个方法了。
FlutterFragment.createDefault()
FlutterFragment.withNewEngine()
FlutterFragment.withCachedEngine
-
NA添加FlutterFragment
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
在NA创建一个Activity,在onCreate中创建FlutterFragment然后添加到布局中。
Flutter.createFragment()方法传入的参数同样表示路由名称,用于确定Flutter要显示的Widget,返回一个FlutterFragment,该类继承自Fragment,将该Fragment添加到Activity中就可以了。
Flutter添加页面
这个同上
3.3 使用新版本FlutterActivity
原生引入Flutter页面方式
使用FlutterActivity,这里的FlutterActivity也是位于io.flutter.embedding.android包下的。
-
首先在清单文件添加代码
1.
2.
3.
4.
5.
6.
-
直接启动这个Activity,代码如下所示
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
使用这种方式特点
这种方式不需要我们自己创建一个Activity,FlutterActivity显示的Flutter路由是在创建Intent对象时指定的,优点就是使用起来更简单,缺点就是不够灵活,无法像FlutterView/FlutterFragment那样只是作为原生页面中的一部分展示,因此这种方式更适合整个页面都是由Flutter编写的场景。
3.4 补充说明问题
-
将Flutter版本更新到了1.17,发现上述代码运行后FlutterView无法显示,这个是为什么呢?
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
和官方提供的示例flutter_view进行了对比,才发现缺少了下面的代码:
可能和生命周期有关系
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的方式为例。
1.
2.
3.
-
以创建FlutterFragment的方式为例
1.
2.
3.
4.
5.
6.
4.2 传递参数注意事项
将路由名称和参数间用“?”隔开,就像浏览器中的url一样,参数使用了Json格式传递,原因就是方便Flutter端解析,而且对于一些复杂的数据,比如自定义对象,使用Json序列化也很好实现。
4.3 Flutter接收传递参数
-
这时候Flutter端通过window.defaultRouteName获取到的就是路由名称+参数了,我们需要将路由名称和参数分开,这就只是单纯的字符串处理。
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
通过"?"将路由名称和参数分开,将参数对应的Json字符串解析为Map对象,需要导入dart:convert包。
05.思考遇到的几个问题分析
5.1 setInitialRoute生效问题
-
flutterEngine.getNavigationChannel().setInitialRoute("yc")生效问题
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
5.2 flutterFragment.getFlutterEngine()空指针
-
使用场景分析
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
错误原因是这里的delegate为null
翻看了一下源码,发现在FlutterFragment的onAttach()方法中会对delegate赋值,也就是说明此时没有执行onAttach()方法。
问题分析
FlutterEngine的warm-up机制,这是一个耗时过程,因此FlutterFragment并不会立刻执行onAttach()方法,导致我们在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法会抛出异常。
如何解决问题
想要解决问题,那就要等到FlutterFragment执行完onAttach()方法在调用getFlutterEngine。那么怎么去监听这个方法执行完呢?
06.Flutter页面关闭时Crash
-
报错日志如下所示
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
-
报错的代码如下所示
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
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方法里开始的
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
-
然后接着往下看,会调用到FlutterActivityAndFragmentDelegate类的onCreateView方法
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
FlutterActivityAndFragmentDelegate类,flutter的初始化、启动等操作都是委托给它的。
大致了解到,创建了一个FlutterSurfaceView 它继承自surfaceView(我们的flutter页面也是渲染在这个surface上的)。之后我们用它初始化一个FlutterView,
-
随后我们再创建一个FlutterSplashView (继承FrameLayout)。重要看调用displayFlutterViewWithSplash()方法。
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
那么什么时候移除这个启动Splash布局呢?在创建FlutterSplashView时,添加了一个完成事件的监听,当flutter加载成功后才将它移除。
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
看到这里可知,通过splashScreen(是个接口),具体看接口实现类,然后创建一个splashScreenView,最后添加到flutter的布局中
得出结论
可以发现在闪屏页的显示到引擎的启动及flutter 页面的显示会有一个很长的过程,而直到flutter 页面的显示,这个闪屏页才会被移除掉。
8.2 如何优化flutter启动屏
-
第一种方案
1.
2.
3.
4.
Flutter由于引擎的创建和初始化需要一定时间,所以也提供了一个过渡方案(默认是白屏)。如下所示,你可以设置一下背景
-
第二种方案
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.