其实 2017 年的时候就已经接触 Flutter 了, 但也只是写了个 HelloWorld, 一方面是 Flutter 在那时候还只是 preview 版本, 另一方面 ReactNative 在那时候非常火热, 忙于用 ReactNative 重构项目, 错过了入坑 Flutter 的第一梯队. 在谷歌的 2018IO 大会上 Flutter 再一次成为了跨平台方案的焦点, 而 ReactNative 也在随着 Airbnb 的弃用热度逐渐冷却, 其实在写下这篇文章的时候我已经再次入坑了不短的一段时间, Flutter 的各种特性也基本上都接触到了, demo 项目也写了一些, 但致使我迫不及待的写下这篇文章的直接原因是 Flutter 的这个能力: Flutter 能够无感知的嵌入到 Android 工程中, 不管是从开发者角度还是用户角度, 你甚至可以只从一个 view 开始来让 Flutter 参与到你的项目中去, 接着替换或者开发某一个页面甚至功能, 然后你就会对它爱不释手, 让你会有用它重构项目和开发新项目的冲动.
用户: 毫秒级的加载速度, 无论是 view 还是页面, 基本上和原生无异.
开发: 只作为一个 module 引入工程, 代码入侵极小, Android 工程和 Flutter 工程互不相干.
注意: 当前日期是 2018-07-29,flutter 的 beta 版本还没有加入这个新功能, 使用命令 flutter channel [分支] 切换到 dev 或 master 分支才能使用, 如果你阅读本篇文章离这个时间点是很久之后可以忽略这段.
创建一个 Android 工程模拟你的现有工程
为了让 Android 工程和 Flutter 工程互不干扰, 这里不再以 Android 工程为工程的跟目录, 而是让 Android 工程和平级的 Flutter 工程的公共目录作为根目录. 最终的目录结构应该是下面这样的
你的项目根目录 (随便什么你喜欢的地方)
原生安卓工程 (FlutterInAndroid)
Flutter 工程 (my_flutter)
复制代码
所以首先在你的项目根目录下用 AS 创建一个新的 Android 原生项目, 可以勾选上 kotlin 支持, 这样更舒服. 创建完成后你会得到一个这样的结构
你的项目根目录 (随便什么你喜欢的地方)
FlutterInAndroid
复制代码
FlutterInAndroid 目录内是一个完整的 Android 工程
module 模式创建 Flutter 工程
接下来使用 Flutter 命令来创建 module 工程, 在你的项目根目录下执行:
flutter create -t module my_flutter
复制代码
创建完成后你会得到一个这样的结构
你的项目根目录 (随便什么你喜欢的地方)
- FlutterInAndroid
- my_flutter
复制代码
my_flutter 是一个 Flutter 的 module 工程, 用来供 Android 项目引入
在 Android 工程中引入依赖
在 FlutterInAndroid 这个 Android 工程的 setting.gradle 文件中追加 flutter 工程的引入 你的项目跟目录 / FlutterInAndroid/setting.gradle
- include ':app'
- // 加入下面配置
- setBinding(new Binding([gradle: this]))
- evaluate(new File(
- settingsDir.parentFile,
- 'my_flutter/.android/include_flutter.groovy'
- ))
复制代码
在 app 的 build.gradle 文件中加入工程依赖 你的项目跟目录 / FlutterInAndroid/app/build.gradle
- ...
- dependencies {
- ...
- // 加入下面配置
- implementation project(':flutter')
- }
复制代码
使用 AS 打开 FlutterInAndroid 工程, 重新构建项目, 即可成功的将 Flutter 加入 Android 工程.
在 Android 工程中创建 Flutter 的 View
Flutter 提供了两种方式让 Android 工程来引用组件, 一种是 View, 一种是 Fragment, 这里选用 View 来进行讲解, Fragment 同理. 这里我们用两种方式来引入 FLutter, 本质是还是是作为一个 view 引入布局还是将 FlutterView 作为 Activity 的根 View.
以单个 view 引入布局
val flutterView = Flutter.createView(this,lifecycle,"route1")
复制代码
通过上面很简单的一个方法, 我们就能通过 Flutter 创建出一个 view, 这个方法提供三个参数, 第一个是 Activity, 第二个参数是一个 Lifecycle 对象, 我们之间取 Activity 的 lifecycle 即可, 第三个参数是告诉 Flutter 我们要创建一个什么样的 view, 这个字符串参数可以在 Flutter 工程中获取得到. 创建出这个 FlutterView 之后就可以按常规的操作来将它加入到任何你想要的布局中去了.
以根 view 作为 Activity
创建一个空的 Activity, 用 Flutter 创建一个 View 作为页面的根 View:
- class FlutterActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_flutter)
- val flutterView = Flutter.createView(this@FlutterActivity,lifecycle,"route1")
- val layout = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
- addContentView(flutterView, layout)
- }
- }
复制代码
这里我们并没有使用 setContentView 而是是用了 addContentView 这个方法, 原因是这样的: 虽然 FLutter 的加载速度非常快, 但是这个过程依然存在, 在创建 FLutterView 之前我们先给 ContentView 设置了一个 R.layout.activity_flutter 布局, 这个布局可以作为 FlutterView 加载完成之前展示给用户的界面, 当然大部分情况下用户根本感知不到这个界面 Flutter 已经加载完成了, 但我们仍需要它, 因为 debug 模式下造成 Flutter 的加载速度并不是非常快, 这个界面可以给开发人员看, 还有就是如果没有这个界面的话在 Activity 的加载过程会出现一个黑色的闪屏, 而这个情况对用户来说并不友好.
在 Flutter 工程中根据不同的 route 创建不同的组件
用 AndroidStudio 在你的项目跟目录 / my_flutter 打开 Flutter 工程, 这时候 AndroidStudio 插件会识别到 Flutter 工程并以 Flutter 工程进行加载. 忽略掉. android 和. ios 文件夹之后你会发现, 这个 FLutter 工程和完整的 Flutter 工程并没有任何不同, 你依然能够以完整 Flutter 工程的流程来进行 Flutter 开发并启动调试, 这是一个非常人性化的设计. 上面我们在原生 Android 工程中以 View 的形式调用了 Flutter, 而 Flutter 本质上是只有一个入口的, 也就是 main.dart 文件中的 main 函数:
void main() => runApp(new MyApp());
复制代码
我们的目的是根据原生工程的调用让 Flutter 生成不同的组件作为 View 来供原生工程使用, 那么我们就可以从这个 main 函数来入手. 通过文档我们可以通过 window 的全局变量中获取到当前的 routeName, 这个值正是上面通过原生工程传给 Flutter 的标识, 有了这个标识就可以简单的做判断来进行不同的组件创建了:
- import 'dart:ui';
- import 'package:flutter/material.dart';
- void main() => runApp(_widgetForRoute(window.defaultRouteName));
- // 根据不同的标识创建不同的组件给原生工程调用
- Widget _widgetForRoute(String route) {
- switch (route) {
- case 'route1':
- return SomeWidget(...);
- case 'route2':
- return SomeOtherWidget(...);
- default:
- return Center(
- child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
- );
- }
- }
复制代码
让 Flutter 模块支持热加载
首先在 Flutter 目录下启动监听服务, 在你的项目根目录 / my_flutter 下执行
flutter attach
复制代码
执行后, 监听服务会等待并监听 debug 应用中 flutter 的状态 然后在打开 FlutterInAndroid 项目的 AS 中以正常方式调试运行, 在真机或模拟器中运行 app 后并不会立即出发 flutter 的监听服务, 当 flutter 的 view 或 Fragment 激活时才会触发. 当 flutter 的监听服务和 app 建立连接后, 终端会出现如下输出:
- $ flutter attach -d W8
- Waiting for a connection from Flutter on PLK UL00...
- Done.
- Syncing files to device PLK UL00... 8.7s
- To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
- An Observatory debugger and profiler on PLK UL00 is available at: http://127.0.0.1:54218/
- For a more detailed help message, press "h". To quit, press "q".
复制代码
这时我们修改 flutter 工程中的 dart 代码文件, 保存后在终端中点击 r 键即可进行热加载, R 键进行热重启.
签名打包
引入 flutter 工程后, 对 Android 原生工程的构建基本上没有影响, 打包按常规操作即可.
Flutter 创建的 module 工程中的 Android 工程与纯 Flutter 工程的中 Android 工程的比较
区别 | Flutter 的 module 工程中的 Android 工程 | 纯 Flutter 工程中的 Android 工程 |
---|---|---|
文件夹名称 | .android | android |
包含的 module | app 和 Flutter | app |
说明 1 | app 只提供了入口 Activity,Flutter 包含了插件扩展及原生工程调用的接口 | app 包含入口 Activity 及插件扩展 |
说明 2 | app 供 Flutter 自身开发调试,Flutter 作为 module 供 Android 原生调用 | app 作为 Android 工程运行及打包 |
为了方便描述我们称前者为 module 工程, 后者为完整工程.
由此可见, 虽然 module 工程中提供了名为 Flutter 的 module 供原生工程调用, 但仍然保留了 app 工程, 这样非常大程度的方便了 flutter 工程师来单独开发 flutter 项目, 无需依赖任何原生的调用, 自身即可启动调试.
参考 官方 wiki http://orh51lve9.bkt.clouddn.com/QQ20180831-173525-HD.gif?imageMogr2/thumbnail/!70p
来源: https://juejin.im/post/5b8910fae51d4538b63d3871