Android工程内嵌Flutter,跨平台的渐进式解决方案

其实2017年的时候就已经接触Flutter了,但也只是写了个HelloWorld,一方面是Flutter在那时候还只是preview版本,另外一方面ReactNative在那时候很是火热,忙于用ReactNative重构项目,错过了入坑Flutter的第一梯队。
在谷歌的2018IO大会上Flutter再一次成为了跨平台方案的焦点,而ReactNative也在随着Airbnb的弃用热度逐渐冷却,其实在写下这篇文章的时候我已经再次入坑了不短的一段时间,Flutter的各类特性也基本上都接触到了,demo项目也写了一些,但导致我火烧眉毛的写下这篇文章的直接缘由是Flutter的这个能力:
Flutter可以无感知的嵌入到Android工程中,不论是从开发者角度仍是用户角度,你甚至能够只从一个view开始来让Flutter参与到你的项目中去,接着替换或者开发某一个页面甚至功能,而后你就会对它爱不释手,让你会有用它重构项目和开发新项目的冲动。android

  • 用户:毫秒级的加载速度,不管是view仍是页面,基本上和原生无异。
  • 开发:只做为一个module引入工程,代码入侵极小,Android工程和Flutter工程互不相干。
    Android工程内嵌Flutter

注意:当前日期是2018-07-29,flutter的beta版本尚未加入这个新功能,使用命令flutter channel [分支]切换到dev或master分支才能使用,若是你阅读本篇文章离这个时间点是好久以后能够忽略这段。ios

建立一个Android工程模拟你的现有工程

为了让Android工程和Flutter工程互不干扰,这里再也不以Android工程为工程的跟目录,而是让Android工程和平级的Flutter工程的公共目录做为根目录。 最终的目录结构应该是下面这样的bash

你的项目根目录(随便什么你喜欢的地方)
  ├── 原生安卓工程(FlutterInAndroid)
  └── Flutter工程 (my_flutter)
复制代码

因此首先在你的项目根目录下用AS建立一个新的Android原生项目,能够勾选上kotlin支持,这样更舒服。 建立完成后你会获得一个这样的结构app

你的项目根目录(随便什么你喜欢的地方)
  └── FlutterInAndroid
复制代码

FlutterInAndroid目录内是一个完整的Android工程ide

module模式建立Flutter工程

接下来使用Flutter命令来建立module工程,在你的项目根目录下执行:函数

flutter create -t module my_flutter
复制代码

建立完成后你会获得一个这样的结构布局

你的项目根目录(随便什么你喜欢的地方)
  ├── FlutterInAndroid
  └── my_flutter
复制代码

my_flutter是一个Flutter的module工程,用来供Android项目引入post

在Android工程中引入依赖

在FlutterInAndroid这个Android工程的setting.gradle文件中追加flutter工程的引入
你的项目跟目录/FlutterInAndroid/setting.gradlegradle

include ':app'
//加入下面配置
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        'my_flutter/.android/include_flutter.groovy'
))

复制代码

在app的build.gradle文件中加入工程依赖
你的项目跟目录/FlutterInAndroid/app/build.gradleui

...
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

相关文章
腾讯NOW直播团队方案
闲鱼团队方案
美团技术团队方案


更多干货移步个人我的博客 www.nightfarmer.top/

相关文章
相关标签/搜索