「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」
在上一节课我们演示了如何在Flutter
中调用原生的界面,在我们日常开发过程中还有可能会在原生项目中,嵌入Flutter
页面,虽然Flutter
不推荐这样使用;接下来我们来实现以下,在原生项目中,嵌入Flutter
页面;
创建Flutter Module
需要注意的是,如果我们的原生需要嵌入Flutter
,那么Flutter
就不能是一个单独的App
,也就是我们创建的Flutter
不能是一个Flutter App
;如果你需要嵌入的Flutter
已经作为App
开发了,那么就需要移植代码;原生嵌入Flutter
,我们的Flutter
需要是一个Flutter Module
:
我们来看一下创建好之后的Flutter Module
的工程目录:
在工程中,仍然会存在android
和ios
的目录,这两个目录是为了让我们调试Flutter Module
而创建的,并且他们是隐藏文件夹 (官方不建议在这两个文件夹中添加相关平台的代码,因为这里边的原生代码不会被打包,只推荐用来作为测试使用);Flutter Module
的入口依然是main.dart
;如果我们需要将原来Flutter App
中的页面嵌入到原生中,那么就需要将Flutter App
工程中的代码一直到lib
文件夹下;
创建原生工程
我们在flutter_module
同级目录下创建一个原生工程:
Flutter
不能使用驼峰命名工程名字,原生可以使用驼峰命名
原生工程默认目录如下:
关联Flutter Module和原生
现在Flutter Module
和原生工程都有了,我们想要将两个工程关联起来,就需要使用到CocoaPods
;我们在原生工程NativeDemo
文件夹下创建Podfile
文件:
修改Podfile
文件内容如下:
flutter_application_path = '../flutter_module' # flutter_module的相对路径
load File.join(flutter_application_path,'.iOS','Flutter','podhelper.rb') # 为iOS工程加载flutter_module这个Flutter工程
platform :ios, '9.0'
target 'NativeDemo' do
install_all_flutter_pods(flutter_application_path) # 加载flutter依赖的相关库
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for NativeDemo
end
接下来执行pod install
命令:
接下来,我们在原生项目中,引用Flutter
,确保项目能编译成功:
我们在ViewController
类中添加两个按钮点击方法,实现如下:
运行结果如下:
我们修改Flutter
代码,然后先使用Android Studio
运行一下Flutter Module
工程,然后在此运行原生项目查看结果:
原生项目中的Flutter
页面发生改变;
Flutter引擎内存问题
我们在调用Flutter
代码的时候,会发现内存暴涨的情况:
在加载了Flutter
页面之后,我们的内存一下子涨了将近80M
,这是因为我们在加载Flutter
页面的时候,需要初始化Flutter
引擎;
在我们编译成功的app
中,Frameworks
目录下的Flutter.framework
就是Flutter
引擎,在运行时,其需要加载到内存中;而且我们发现,当前情况下,即使我们的Flutter
页面消失了,内存依然没有相应的减少;更严重的是,当我们再次加载Flutter
页面时,内存依然会再次暴涨:
我们没加载一次Flutter
页面,内存都会响应的增长(内存泄漏了);
造成这种结果的原因是,我们每次加载Flutter
页面的时候,都是初始化了一个新的FlutterViewController
页面,也就是每一次都初始化了一次引擎(调用FlutterViewController
实质上就是初始化渲染引擎); 这个问题我们稍后解决,我们继续按照开发流程向下继续;
显示不同的Flutter页面
我们在正常的开发流程中,很有可能两个按钮需要显示不同的Flutter
页面,那么应该如何实现呢?这里有两种方式:
第一种:设置路由(不推荐)
使用FlutterViewController
的setInitialRoute
方法初始化路由,代码如下:
这样,我们就在加载Flutter
的时候,给Flutter
路由传递了参数one
和two
;接下来,Flutter
页面最响应的修改:
在Flutter
中通过MyApp
中的window.defaultRouteName
获取原生传递的one
和two
信息赋值给pageIndex
,然后根据pageIndex
显示不同的页面;效果如下:
那么,在Flutter
中我们如何返回原生页面呢?我们在Flutter
中通过MethodChannel
给原生传递消息:
接下来,针对原生代码优化;
我们在调用FlutterViewController
的setInitialroute
方法传递路由的时候,我们从该方法的注释中可以意识到该方法是在初始化引擎之后调用的:
* This method must be called immediately after `initWithProject` and has no
* effect when using `initWithEngine` if the `FlutterEngine` has already been
* run.
也就是说,我们初始化FlutterViewController
的过程就是在初始化Flutter引擎
,所以第一次调用Flutter
页面的时候,会相对卡顿了一下;我们将原生代码修改如下:
- 初始化
FlutterEngine
时一定要调用run
方法来启动引擎; - 初始化
FlutterViewController
的时候,将引擎传递进去; - 在
viewDidload
中先调用一下引擎,提前初始化;防止第一次调用时卡顿的发生;
此时,我们来看一下运行效果:
原生项目启动时,内存就达到了
80M
,说明在原生项目启动时,我们已经初始化了FlutterEngine
,而且第一次调用之后,内存也没有再次大幅增长;
但是,此时我们发现,我们使用setInitialRoute
方法传递的参数没有被Flutter
页面接收到,setInitialRoute
已然失效;我们换一种通信方式
第二种:MethodChannel
根据我们之前使用MethodChannel
进行通信的流程,我们将原生代码修改如下:
在原生代码中使用FlutterMethodChannel
与Flutter
通信;
然后修改Flutter
端代码如下:
在Flutter
端使用MethodChannel
和原生
进行通信;其中PageOne
页面代码如下:
其他页面与此页面代码基本一样;最终运行效果如下:
注意事项
运行报错App.framework
报错有两种情况:
- 模拟器运行,调用
Flutter
时显示白屏,并且报错如下:
Failed to find assets path for "Frameworks/App.framework/flutter_assets"
- 真机运行,直接崩溃,报错如下:
dyld[4112]: Library not loaded: @rpath/App.framework/App
Referenced from: /private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/NativeDemo
Reason: tried: '/usr/lib/swift/App.framework/App' (no such file), '/private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/Frameworks/App.framework/App' (no such file), '/private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/Frameworks/App.framework/App' (no such file), '/private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/Frameworks/App.framework/App' (no such file), '/usr/lib/swift/App.framework/App' (no such file), '/private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/Frameworks/App.framework/App' (no such file), '/private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/Frameworks/App.framework/App' (no such file), '/private/var/containers/Bundle/Application/5106B07B-3EF3-4961-B20F-8DB08991BD63/NativeDemo.app/Frameworks/App.framework/App' (no such file), '/System/Library/Frameworks/App.framework/App' (no such file)
解决方案,使用Android Studio
运行一次Flutter Module
工程,然后在目录flutter_module
->build
->ios
会生成真机或者模拟器的编译文件夹:
将对应的真机或者模拟器文件夹下的App.framework
文件拖入Xcode
中General
->Framewords,Libraries,and Embedded Content
下:
再次运行,即可运行成功;
更新Flutter Module代码问题
我们在原生项目中,通过这种方式引入Flutter Module
工程来使用,如果Flutter Module
工程中代码后续进行了修改,那么直接用Xcode
运行是不会显示最新的代码效果的;
有以下两种方式解决:
- 先使用
Android Studio
来编译运行,然后使用Xcode
运行; Xcode
清空缓存再次运行(有时候不好用);