8天让iOS开发者上手Flutter之八

2,707 阅读8分钟

介绍四种Flutter Project

先介绍一下这四个的差别和使用的场景。 image.png

Flutter App

如果是想要使用flutter开发一个新的项目,那么就选择Flutter App。就像我们之前的wechat_demo

Flutter Module

但是很多时候我们目前手上的原生APP可能会很庞大,不是随便就可以重新使用flutter重新再写一遍的。但是又想在现有的APP中体验一下flutter,那么就选择Flutter Module

Flutter Package

Flutter 支持使用其他开发者向 Flutter 和 Dart 生态系统贡献的共享 package(类似于iOS中的第三方库吧,AFNetworking,YYKit等等...),这意味着你可以快速构建应用而不是一切从零开始。

Package最低要求是包含一个 pubspec.yaml 文件。此外,一个 package 可以包含依赖关系 (在 pubspec.yaml 文件里声明)、 Dart 库、应用、资源、测试、图片和例子等。 pub.dev 上列出了很多 package,由 Google 工程师和 Flutter 和 Dart 社区的开发者开发和发布,你可以用在自己的应用里。

Flutter Plugin

插件 (plugin) 是 package 的一种,全称是 plugin package,我们简称为 plugin,中文叫插件。插件 (plugin package) 是一种特别的 package,特别指那些帮助你获得原生平台特性的 package。插件可以为 Android (使用 Kotlin 或 Java 语言)、 iOS (使用 Swift 或 Objective-C 语言)、Web、macOS、Windows、Linux 平台,或其任意组合的平台编写。比如:某个插件可以为 Flutter 应用提供使用原生平台的摄像头的功能。

使用Flutter Module进行混合开发

我们前七天开发的项目就是Flutter App,今天主要介绍如何使用Flutter Module进行混合开发。混合开发官方推荐只使用单个Flutter页面,多个Flutter页面也能支持,但有可能会出现稳定性、性能问题以及 API 仍然可能变动的问题。请大家谨慎使用。官方介绍链接

iOS原生项目中导入Flutter

准备一个原生iOS项目

进行混合开发就一定需要一个原生项目,根据你的需求,可以使用目前手上的APP,也可以临时创建一个Demo来先练练手。我们这里就临时新建一个iOS原生Demo。 image.png 这个原生项目很简单,就这么两行代码。

新建Flutter Module项目

新建一个Flutter Module项目,项目名就叫module。 image.png 最好将两个项目放在同级目录下,因为原生项目需要知道Flutter Module项目的位置。 image.png

使用cocoapods建立关联

在原生项目Demo里建立Podfile文件,然后编辑Podfile。

flutter_application_path = 'Podfile相对flutter module的路径'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

在每个需要集成flutter module的target添加如下代码

target '你的APP' do
  install_all_flutter_pods(flutter_application_path)
end

编辑完之后的Podfile如下。 image.png 然后执行pod install命令 image.png 如上图所示就表示集成好了。就接下来就是如何在原生项目中展示flutter的内容了。

展示单个Flutter页面

展示单个Flutter页面的方式有两种,一种是使用FlutterEngine的方式,一种是不使用FlutterEngine的方式,其实不使用FlutterEngine的说法是不严谨的,只是我们没有使用代码创建FlutterEngine而已,Flutter框架内部自己创建了FlutterEngine,但是为了方便记忆,我们可以这么理解。但是这种不使用FlutterEngine的方式官方并不推荐。因为按需创建FlutterEngine 的话,在 FlutterViewController 被 present 出来之后,第一帧图像渲染完之前,将会引入明显的延迟。但是当 Flutter 页面很少被展示时,当对决定何时启动 Dart VM 没有好的启发时,当 Flutter 无需在页面(view controller)之间保持状态时,此方式可能会有用。

不使用FlutterEngine展示单个Flutter页面(官方并不推荐)

来到ViewController.m文件中导入Flutter的头文件 image.png 直接创建FlutterViewController对象,并使用present和push两种方式展示 image.png APP显示如下图所示:
present方式显示如图:

push方式显示如图:

使用FlutterEngine展示单个Flutter页面

创建FlutterEngine

创建 FlutterEngine 的合适位置取决于您的应用。作为示例,我们将在应用启动的AppDelegate中创建一个 FlutterEngine,并作为属性暴露给外界。

AppDelegate.himage.pngAppDelegate.mimage.png

展示FlutterViewController

image.png 现在,你的 iOS 应用中集成了一个 Flutter 页面。

展示多个Flutter页面

使用上面的方式,最终展示的Flutter页面都是同一个页面。而在实际混合开发中,想要展示多个不同的Flutter页面的需求应该是普遍的。那么Flutter能做到吗?答案是可以的,官方的说法是自Flutter 2.0.0开始,可以同时添加多个Flutter实例。由于稳定性,性能问题以及API任然可能变动,请谨慎使用。链接

使用FlutterEngineGroup

展示多个Flutter页面需要使用到FlutterEngineGroup来创建FlutterEngine,而不是上面直接使用FlutterEngine的方式。 在AppDelegate.himage.pngAppDelegate.mimage.png

新建一个flutter页面

来到Android Studio中的Flutter module项目,新建一个page_two.dart文件,并做些简单的展示 image.png PageTwo页面写好之后,还需要到main.dart文件中声明一个新的dart入口。这样才能在原生中找到这个PageTwo页面,代码如图: image.png
page_two就是PageTwo的入口函数,在原生中待会就会用到。

展示两个Flutter页面

回到原生项目的ViewController.m文件中,实现如下代码: image.png 在第0行,我们创建engine的时候,并没有传入Entrypoint参数,所以就会展示默认的入口函数main对应的页面。在第1行中,我们创建engine的时候,指定了入口函数为page_two,那么就会展示page_two()所对应的页面。

present展示的页面如图:

push展示的页面如图:

Flutter与iOS端通信

Flutter和iOS端的通信主要是使用各种通道。有MethodChannelBasicMessageChannel,EventChannel等等,这里主要介绍前面两种通道。通道通过名称来建立联系,所以通道的名称需要确保是唯一的。如果有同名的通道,通信会受到干扰

MethodChannel 方法通道

方法通道主要用了传递方法名称和参数。在前面我们新建PageTwo的时候,就已经创建了一个MethodChannel了,名字为page_two_channel。然后在文本‘返回上一页’的点击手势里面调用了一个方法invokeMethod()。这个方法可以传递多个参数,第一个参数就是方法名称,后面是方法参数。我们这里没有参数,就只传入一个方法名pop。那么Flutter端的工作就完成了,再回到我们的iOS原生端,ViewController.m里面。实现如下代码: image.png 在iOS中给MethodChannel加了一个Flutter前缀。然后初始化FlutterMethodChannel的时候,传入的名字一定要和flutter端是一致的,不然是无法建立通讯的。然后调用setMethodCallHandler:方法传入一个block参数。block参数会在channel.invokeMethod()的时候被调用。block参数call就是对invokeMethod参数的一个封装。callmethod就是方法名,还有一个arguments就是方法参数。这样就可以根据方法名进行判断后作出处理了。现在是从flutter向iOS原生通信,实现了在flutter中点击方法实现了pop回到上个页面的。相反的,也可以从iOS端向flutter端通信。在iOS端,使用channel调用invokeMethod()就可以向flutter发送消息了,在flutter端同样设置channelsetMethodCallHandler就可以接收了。flutter端setMethodCallHandler代码如下: image.png 这里说一句,这些通道不能在无状态的Widget里使用,改为有状态的就可以了。

BasicMessageChannel 基础消息通道

除了上面提到的MethodChannel,你还可以使用BasicMessageChannel,它支持使用自定义消息编解码器进行基本的异步消息传递。 此外,您可以使用专门的BinaryCodecStringCodec和 JSONMessageCodec类,或创建自己的编解码器。

BasicMessageChannel这个通道的使用方法跟上面的MethodChannel是一样的,不同点是在flutter中创建BasicMessageChannel这个通道需要传入一个编解码器,使用一个标准的编解码器StandardMessageCodec可以将基本的数据类型进行编解码。BasicMessageChannel通道可以实现基础数据类型的通讯。iOS和flutter的基础数据类型对应编解码关系: image.png

总结

本来还想写一个混合开发的实际案例的,但是鉴于本人目前的重点并不是flutter,而且网上也有较多的介绍这种混合开发的实际案例,所以这里篇文章就写到这里了。这8篇文章的内容应该说只是一个iOSer入门flutter的教程,只是些皮毛东西,离掌握flutter,Dart还差的很远。而flutter的出现也并不是说会完全取代Android和iOS开发者,flutter只能用来搭建一套各平台统一的UI,而各个平台相关的功能,和硬件相关的需求,都依然需要各个平台的开发者去实现。