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/

相關文章
相關標籤/搜索