Flutter混合开发详解
混合开发简介
使用flutter从零开始开发app是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有app的历史沉淀,全面转向flutter是不现实的。因此使用flutter去统一android、ios技术栈,把它作为已有原生app的扩展能力,通过有序推进来提升移动终端的开发效率。
目前,想要在已有的原生app里嵌入一些flutter页面主要有两种方案。一种是将原生工程作为flutter工程的子工程,由flutter进行统一管理,这种模式称为统一管理模式。另一种是将flutter工程作为原生工程的子模块,维持原有的原生工程管理方式不变,这种模式被称为三端分离模式。
在flutter框架出现早期,由于官方提供的混编方式以及资料有限,国内较早使用flutter进行混合开发的团队大多使用的是统一管理模式。但是,随着业务迭代的深入,统一管理模式的弊端也随之显露,不仅三端(android、ios和flutter)代码耦合严重,相关工具链耗时也随之大幅增长,最终导致开发效率降低。所以,后续使用flutter进行混合开发的团队大多使用三端代码分离的模式来进行依赖治理,最终实现flutter工程的轻量级接入。
除了可以轻量级接入外,三端代码分离模式还可以把flutter模块作为原生工程的子模块,从而快速地接入flutter模块,降低原生工程的改造成本。在完成对flutter模块的接入后,flutter工程可以使用android studio进行开发,无需再打开原生工程就可以对dart代码和原生代码进行开发调试。
使用三端分离模式进行flutter混合开发的关键是抽离flutter工程,将不同平台的构建产物依照标准组件化的形式进行管理,即android使用aar、ios使用pod。也就是说,flutter的混编方案其实就是将flutter模块打包成aar或者pod库,然后在原生工程像引用其他第三方原生组件库那样引入flutter模块即可。
flutter模块
默认情况下,新创建的flutter工程会包含flutter目录和原生工程的目录。在这种情况下,原生工程会依赖flutter工程的库和资源,并且无法脱离flutter工程独立构建和运行。
在混合开发中,原生工程对flutter的依赖主要分为两部分。一个是flutter的库和引擎,主要包含flutter的framework 库和引擎库;另一个是flutter模块工程,即flutter混合开发中的flutter功能模块,主要包括flutter工程lib目录下的dart代码实现。
对于原生工程来说,集成flutter只需要在同级目录创建一个flutter模块,然后构建ios和android各自的flutter依赖库即可。接下来,我们只需要在原生项目的同级目录下,执行flutter提供的构建模块命令创建flutter模块即可,如下所示。
flutter create -t module flutter_library
其中,flutter_library为flutter模块名。执行上面的命令后,会在原生工程的同级目录下生成一个flutter_library模块工程。flutter模块也是flutter工程,使用android studio打开它,其目录如下图所示。
可以看到,和普通的flutter工程相比,flutter模块工程也内嵌了android工程和ios工程,只不过默认情况下,android工程和ios工程是隐藏的。因此,对于flutter模块工程来说,也可以像普通工程一样使用 android studio进行开发和调试。
同时,相比普通的flutter工程,flutter模块工程的android工程目录下多了一个flutter目录,此目录下的build.gradle配置就是我们构建aar时的打包配置。同样,在flutter模块工程的ios工程目录下也会找到一个flutter目录,这也是flutter模块工程既能像flutter普通工程一样使用android studio进行开发调试,又能打包构建aar或pod的原因。
android集成flutter
在原生android工程中集成flutter,原生工程对flutter的依赖主要包括两部分,分别是flutter库和引擎,以及flutter工程构建产物。
- flutter库和引擎:包含icudtl.dat、libflutter.so以及一些class文件,最终这些文件都会被封装到flutter.jar中。
- flutter工程产物:包括应用程序数据段 isolate_snapshot_data、应用程序指令段 isolate_snapshot_instr、虚拟机数据段vm_snapshot_data、虚拟机指令段vm_snapshot_instr以及资源文件flutter_assets。
和原生android工程集成其他插件库的方式一样,在原生android工程中引入flutter模块需要先在settings.gradle中添加如下代码。
setbinding(new binding([gradle: this])) evaluate(new file( settingsdir.parentfile, 'flutter_library/.android/include_flutter.groovy'))
其中,flutter_library为我们创建的flutter模块。然后,在原生android工程的app目录的build.gradle文件中添加如下依赖。
dependencies { implementation project(":flutter") }
然后编译并运行原生android工程,如果没有任何错误则说明集成flutter模块成功。需要说明的是,由于flutter支持的最低版本为16,所以需要将android项目的minsdkversion修改为16。
如果出现“程序包android.support.annotation不存在”的错误,需要使用如下的命令来创建flutter模块,因为最新版本的android默认使用androidx来管理包。
flutter create --androidx -t module flutter_library
对于android原生工程,如果还没有升级到androidx,可以在原生android工程上右键,然后依次选择【refactor】→【migrate to androidx】将android工程升级到androidx包管理。
在原生android工程中成功添加flutter模块依赖后,打开原生android工程,并在应用的入口mainactivity文件中添加如下代码。
public class mainactivity extends appcompatactivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); view flutterview = flutter.createview(this, getlifecycle(), "route1"); framelayout.layoutparams layoutparams = new framelayout.layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent); addcontentview(flutterview, layoutparams); } }
通过flutter提供的createview()方法,可以将flutter页面构建成android能够识别的视图,然后将这个视图使用android提供的addcontentview()方法添加到父窗口即可。重新运行原生android工程,最终效果如下图所示。
如果原生android的mainactivity加载的是一个framelayout,那么加载只需要将flutter页面构建成一个fragment即可,如下所示。
public class mainactivity extends appcompatactivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); fragmenttransaction ft= getsupportfragmentmanager().begintransaction(); ft.replace(r.id.fragment_container, flutter.createfragment("hello flutter")); ft.commit(); } }
除了使用flutter模块方式集成外,还可以将flutter模块打包成aar,然后再添加依赖。在flutter_library根目录下执行aar打包构建命令即可抽取flutter依赖,如下所示。
flutter build apk --debug
此命令的作用是将flutter库和引擎以及工程产物编译成一个aar包,上面命令编译的aar包是debug版本,如果需要构建release版本,只需要把命令中的debug换成release即可。
打包构建的flutter-debug.aar位于.android/flutter/build/outputs/aar/目录下,可以把它拷贝到原生android工程的app/libs目录下,然后在原生android工程的app目录的打包配置build.gradle中添加对它的依赖,如下所示。
dependencies { implementation(name: 'flutter-debug', ext: 'aar') }
然后重新编译一下项目,如果没有任何错误提示则说明flutter模块被成功集成到android原生工程中。
ios集成flutter
原生ios工程对flutter的依赖包含flutter库和引擎,以及flutter工程编译产物。其中,flutter 库和引擎指的是flutter.framework等,flutter工程编译产物指的是 app.framework等。
在原生ios工程中集成flutter需要先配置好cocoapods,cocoapods是ios的类库管理工具,用来管理第三方开源库。在原生ios工程中执行pod init命令创建一个podfile文件,然后在podfile文件中添加flutter模块依赖,如下所示。
flutter_application_path = '../flutter_ library/ load file.join(flutter_application_path, '.ios', 'flutter', 'podhelper.rb') target 'iosdemo' do # comment the next line if you don't want to use dynamic frameworks use_frameworks! install_all_flutter_pods(flutter_application_path) # pods for iosdemo … //省略其他脚本 end '
然后,关闭原生ios工程,并在原生ios工程的根目录执行pod install命令安装所需的依赖包。安装完成后,使用xcode打开iosdemo.xcworkspace原生工程。
默认情况下,flutter是不支持bitcode的,bitcode是一种ios编译程序的中间代码,在原生ios工程中集成flutter需要禁用bitcode。在xcode中依次选择【tagets】→【build setttings】→【build options】→【enable bitcode】来禁用bitcode,如下图所示。
如果使用的是flutter早期的版本,还需要添加build phase来支持构建dart代码。依次选择【taggets】→【build settings】→【enable phases】,然后点击左上角的加号新建一个“new run script phase”,添加如下脚本代码。
"$flutter_root/packages/flutter_tools/bin/xcode_backend.sh" build "$flutter_root/packages/flutter_tools/bin/xcode_backend.sh" embed
不过,最新版本的flutter已经不需要再添加脚本了。重新运行原生ios工程,如果没有任何错误则说明ios成功集成flutter模块。
除了使用flutter模块方式外,还可以将flutter模块打包成可以依赖的动态库,然后再使用cocoapods添加动态库。首先,在flutter_library根目录下执行打包构建命令生成framework动态库,如下所示。
flutter build ios --debug
上面命令是将flutter工程编译成flutter.framework和app.framework动态库。如果要生成release版本,只需要把命令中的debug换成release即可。
然后,在原生ios工程的根目录下创建一个名为flutterengine的目录,并把生成的两个framework动态库文件拷贝进去。不过,ios生成模块化产物要比android多一个步骤,因为需要把flutter工程编译生成的库手动封装成一个pod。首先,在flutter_ library该目录下创建flutterengine.podspec,然后添加如下脚本代码。
pod::spec.new do |s| s.name = 'flutterengine' s.version = '0.1.0' s.summary = 'flutterengine' s.description = <<-desc todo: add long description of the pod here. desc s.homepage = 'https://github.com/xx/flutterengine' s.license = { :type => 'mit', :file => 'license' } s.author = { 'xzh' => '1044817967@qq.com' } s.source = { :git => "", :tag => "#{s.version}" } s.ios.deployment_target = '9.0' s.ios.vendored_frameworks = 'app.framework', 'flutter.framework' end
然后,执行pod lib lint命令即可拉取flutter模块所需的组件。接下来,在原生ios工程的podfile文件添加生成的库即可。
target 'iosdemo' do pod 'flutterengine', :path => './' end
重新执行pod install命令安装依赖库,原生ios工程集成flutter模块就完成了。接下来,使用xcode打开viewcontroller.m文件,然后添加如下代码。
#import "viewcontroller.h" #import <flutter/flutter.h> #import <flutterpluginregistrant/generatedpluginregistrant.h> @interface viewcontroller () @end @implementation viewcontroller - (void)viewdidload { [super viewdidload]; uibutton *button = [[uibutton alloc]init]; [button settitle:@"加载flutter模块" forstate:uicontrolstatenormal]; button.backgroundcolor=[uicolor redcolor]; button.frame = cgrectmake(50, 50, 200, 100); [button settitlecolor:[uicolor redcolor] forstate:uicontrolstatehighlighted]; [button addtarget:self action:@selector(buttonprint) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:button]; } - (void)buttonprint{ flutterviewcontroller * fluttervc = [[flutterviewcontroller alloc]init]; [fluttervc setinitialroute:@"defaultroute"]; [self presentviewcontroller:fluttervc animated:true completion:nil]; } @end
在上面的代码中,我们在原生ios中创建了一个按钮,点击按钮时就会跳转到flutter页面,最终效果如下图所示。
默认情况下,flutter为提供了两种调用方式,分别是flutterviewcontroller和flutterengine。对于flutterviewcontroller来说,打开viewcontroller.m文件,在里面添加一个加载flutter页面的方法并且添加一个按钮看来调用。
flutter模块调试
众所周知,flutter的优势之一就是在开发过程中使用热重载功能来实现快速调试。默认情况下,在原生工程中集成flutter模块后热重载功能是失效的,需要重新运行原生工程才能看到效果。如此一来,flutter开发的热重载优势就失去了,并且开发效率也随之降低。
那么,能不能在混合项目中开启flutter的热重载呢?答案是可以的,只需要经过如下步骤即可开启热重载功能。首先,关闭原生应用,此处所说的关闭是指关闭应用的进程,而不是简单的退出应用。在flutter模块的根目录中输入flutter attach命令,然后再次打开原生应用,就会看到连接成功的提示,如下图所示。
如果同时连接了多台设备,可以使用flutter attach -d 命令来指定连接的设备。接下来,只需要按r键即可执行热重载,按r键即可执行热重启,按d键即可断开连接。
在flutter工程中,我们可以直接点击debug按钮来进行代码调试,但在混合项目中,直接点击debug按钮是不起作用的。此时,可以使用android studio提供的flutter attach按钮来建立与flutter模块的连接,进行实现对flutter模块的代码调试,如图下图所示。
上面只是完成了在原生工程中引入flutter模块,具体开发时还会遇到与flutter模块的通信问题、路由管理问题,以及打包等。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 防盗链接ASP函数