Flutter 即學即用系列博客——03 在舊有項目引入 Flutter

前言

其實若是打算在實際項目中引入 Flutter,徹底將舊有項目改形成純 Flutter 項目的可能性比較小,更多的是在舊有項目引入 Flutter。html

所以本篇咱們就說一說如何在舊有項目引入 Flutter。java

官方 WIKI 有說明,可是裏面坑仍是很多的,變化也是存在的。android

所以就讓咱們來看一看。git

目錄

1. 按照官網實現基本引入

Add Flutter to existing appsgithub

上面爲GitHub WIKI 的引入方式,經過 Module 的形式進行引入。shell

能夠看出文檔仍是在不斷更新的。app

下面咱們說下具體的步驟:ide

第一步:建立 Flutter Module工具

假設已經存在的 Android 項目路徑爲 /Users/nesger/Desktop/nesger_folder/project/studio/MyApp,那麼咱們在同級目錄下面建立 Flutter Module。在終端執行以下命令:佈局

cd /Users/nesger/Desktop/nesger_folder/project/studio/ 
flutter create -t module my_flutter

執行命令以後,就建立了一個帶有 dart 代碼的 Flutter Module,而且可以看到一個隱藏的文件夾 .android。

第二步:讓主 APP 依賴 Flutter Module

這裏,主 APP 指的就是 Android 項目 MyApp

在 MyApp 的 settings.gradle 添加下面代碼:

setBinding(new Binding([gradle: this]))          
evaluate(new File(                                          
  settingsDir.parentFile,                                 
  'my_flutter/.android/include_flutter.groovy'   
))

在須要使用 Flutter Module 的 MyApp 的對應 Module 添加依賴,好比本例子中就是到 MyApp 中的 app 的 build.gradle 添加

dependencies {
  implementation project(':flutter')
}

添加完以後有個報錯以下:

Manifest merger failed : uses-sdk:minSdkVersion 15 cannot be smaller than version 16 declared in library [:flutter] /Users/nesger/Desktop/nesger_folder/project/studio/my_flutter/.android/Flutter/build/intermediates/merged_manifests/debug/processDebugManifest/merged/AndroidManifest.xml as the library might be using APIs not available in 15
	Suggestion: use a compatible library with a minSdk of at most 15,
		or increase this project's minSdk version to at least 16,
		or use tools:overrideLibrary="com.nesger.myflutter" to force usage (may lead to runtime failures)

從這裏能夠看到是因爲咱們 MyApp 的 uses-sdk:minSdkVersion 與 Flutter Module 的不一致。

控制檯也給出瞭解決方法,咱們這裏簡單的升下咱們 MyApp 的 uses-sdk:minSdkVersion 便可。

改完編譯就沒問題了。

第三步:使用 Flutter Module 提供的 API 在主 APP 中建立 FlutterView

咱們的主界面佈局以下,就是有一個按鈕而已。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <Button
        android:onClick="onClick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="create flutter view"
        />

</RelativeLayout>

而後在代碼裏面對應位置添加以下代碼:

View flutterView = Flutter.createView(
      MainActivity.this,
      getLifecycle(),
      "route1"
    );
    FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
    layout.leftMargin = 100;
    layout.topMargin = 200;
    addContentView(flutterView, layout);

運行到手機上面,能夠看到下面效果:

點擊按鈕以後,能夠看到 Flutter 頁面顯示出來了

到這裏咱們基本就實現了在舊有項目引入 Flutter 了。

那麼上面的代碼有個地方,就是**"route1"**究竟是什麼呢?

顧名思義,你能夠認爲是一個路由。也就是用來區分不一樣 Flutter 頁面的。

假設你的 Flutter 有多個頁面,那麼你如何肯定要加載哪一個頁面呢?就能夠經過這個來區分。

因此在 Flutter Module 的 main.dart 文件裏面,對於存在多個頁面的狀況,咱們能夠寫下面的模板代碼:

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),
      );
  }
}

這段代碼咱們能夠重點關注 switch 那一塊代碼。這裏會根據不一樣的路由,返回不一樣的頁面。

第四步:熱重載和調試 dart 代碼

首先定位到 Flutter Module 路徑,這裏爲**/Users/nesger/Desktop/nesger_folder/project/studio/my_flutter**。
接着執行命令flutter attach,會看到控制檯輸出

Waiting for a connection from Flutter on SM G9350...

而後咱們直接運行或者以 debug 模式運行項目。
接着點擊按鈕,觸發 Flutter 代碼,會看到控制檯輸出

Done. Syncing files to device SM G9350... 1.2s

To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R". An Observatory debugger and profiler on SM G9350 is available at: http://127.0.0.1:53562/ For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

這個跟咱們以前講到的熱重載相似,這裏就不重複了。

除了直接運行舊有項目來啓動 Flutter 以外,其實更多時候咱們編寫 Flutter 是獨立的,能夠直接運行 Flutter 來調試和修改 dart 代碼。

我通常傾向於直接執行 flutter run,而不是按照官網那樣經過 flutter attach,而後以 debug 模型啓動舊有項目。

等到 Flutter Module 都調試 OK 以後,再和舊有項目一塊兒運行查看效果。

2. 修改配置容許 Flutter Module 在任意位置

你們能夠看到,官網的例子的 Flutter Module 是在與 Android 原項目同層級的目錄下面建立的。
這樣其實對於咱們開發不是很方便。

首先,咱們須要在 Android Studio 分別打開兩個項目,這樣不方便修改和調試 dart 代碼。
其次,通常在公司裏面,項目都是用 git 之類的項目管理工具來管理的。若是按照官網的例子,其餘開發者下載原項目的代碼以後還須要額外下載 Flutter 代碼倉庫。

因此其實更多的狀況,咱們但願 Flutter Module 是在咱們主項目下面當成主項目的代碼來使用,這樣不只方便修改和調試,並且其餘開發者也不須要進行額外處理。

簡單回顧一下上面的引入步驟:

1.建立 Module
2.修改項目的 settings.gradle
3.添加 flutter module 依賴

其中重點須要關注的就是 2 了。由於 2 裏面指定的一個文件是跟路徑相關的。

咱們在 MyApp 項目下面建立 sub 文件夾,移動以前的 module 到 sub 文件夾下面。

執行下面命令:(確保當前在 MyApp 項目下面)

mkdir sub  
cd sub/  
mv ../../my_flutter .

執行完以後 module 的位置就變化了。你會發現代碼裏面 Flutter 相關代碼和包都報錯了。clean 一下,會有報錯:

java.io.FileNotFoundException: /Users/nesger/Desktop/nesger_folder/project/studio/my_flutter/.android/include_flutter.groovy

提示文件找不到。

這是必然的,由於咱們剛剛遷移了 flutter module 的位置。

因此說要容許 Flutter Module 配置在任意位置,重點就是第二步項目的 settings.gradle 的配置了。或者說 include_flutter.groovy 文件的位置是否指定正確。

咱們看下配置信息:

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

new File(settingsDir.parentFile,'my_flutter/.android/include_flutter.groovy' ) 解讀下這句話的意思就是指定 include_flutter.groovy 的所在位置。這裏的意思是在 settings 文件所在目錄(settingsDir)的父目錄有個文件(settingsDir.parentFilemy_flutter/.android/include_flutter.groovy。看下下面的文件放置位置圖就清楚了:

因此官網在跟項目同級建立 flutter module 是沒問題的。可是咱們如今改了,應該怎樣設置呢?

上下圖,而後你們考慮一下答案,再往下翻,相信聰明的你必定知道,改法有多種,下面提供一下幾種方案。

Tips:注意相對路徑的使用,重點是找到 include_flutter.groovy

解法一:(推薦)

include ':app'
setBinding(new Binding([gradle: this]))         
evaluate(new File(                                        
        settingsDir,                                  
        'sub/my_flutter/.android/include_flutter.groovy'    
))

在 settings 所在目錄有 sub/my_flutter/.android/include_flutter.groovy 文件

解法二:

include ':app'
setBinding(new Binding([gradle: this]))        
evaluate(new File(                                          
        settingsDir.parentFile,                                   
        'MyApp/sub/my_flutter/.android/include_flutter.groovy'   
))

在 settings 所在目錄的父目錄有 MyApp/sub/my_flutter/.android/include_flutter.groovy 文件

有了上面圖文並茂的講解加上一個實際的 Sample,相信無論 flutter module 放在哪裏你到能夠關聯到了。

3. 引入本身項目報錯處理方法

咱們新建一個 Android 項目而後按照上述導入能夠正常運行。

然而,理想很豐滿,現實很骨感,本人在導入到實際工程項目時,一運行到 Flutter 相關代碼,控制檯就報出下面信息,而且 APP crash。

2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: Build fingerprint: 'samsung/hero2qltezc/hero2qltechn:8.0.0/R16NW/G9350ZCS3CRJ2:user/release-keys'
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: Revision: '15'
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: ABI: 'arm'
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: pid: 3072, tid: 3072, name: pkgname  >>> pkgname <<<
2019-02-15 09:35:00.355 4366-4366/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG: Abort message: '[FATAL:flutter/shell/common/shell.cc(212)] Check failed: vm. Must be able to initialize the VM.
    '
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG:     r0 00000000  r1 00000c00  r2 00000006  r3 00000008
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG:     r4 00000c00  r5 00000c00  r6 fff50940  r7 0000010c
2019-02-15 09:35:00.360 4366-4366/? A/DEBUG:     r8 00000000  r9 fff50d04  sl d74ec880  fp fff51048
2019-02-15 09:35:00.361 4366-4366/? A/DEBUG:     ip 00000000  sp fff50930  lr e9ebea17  pc e9eefb74  cpsr 200f0010
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG: backtrace:
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG:     #00 pc 0004bb74  /system/lib/libc.so (tgkill+12)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG:     #01 pc 0001aa13  /system/lib/libc.so (abort+54)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG:     #02 pc 0053ea03  /data/app/pkgname-nNNvK7M4bKRp1ys0OFeS7g==/lib/arm/libflutter.so (offset 0x4e5000)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG:     #03 pc 00536ba3  /data/app/pkgname-nNNvK7M4bKRp1ys0OFeS7g==/lib/arm/libflutter.so (offset 0x4e5000)
2019-02-15 09:35:00.365 4366-4366/? A/DEBUG:     #04 pc 0005d295  /data/app/pkgname-nNNvK7M4bKRp1ys0OFeS7g==/oat/arm/base.odex (offset 0x49000)

其中 pkgname 是實際項目包名,這裏作了替換

此刻的心情見下圖:

在通過了搜索引擎的搜索和 GitHub 上面 flutter 的相關 Issues 閱讀,最終得出瞭解決方案。

通用解決步驟:

  1. 本項目執行清理命令。./gradlew clean
  2. 進入 flutter module 項目執行清理命令。flutter packages get;flutter clean
  3. 進入 flutter module 的 .android 項目執行清理命令和打包操做。./gradlew clean;./gradlew assemble
  4. 回到本項目執行打包命令。./gradlew assemble

經過實際例子來加深認識吧。仍是以咱們上面的 MyApp 爲例進行說明。module 如今是在 MyApp 下面的 sub 目錄下面。
那麼咱們直接在 terminal 執行下面命令便可:

./gradlew clean;cd sub/my_flutter/;flutter packages get;flutter clean;cd -;cd sub/my_flutter/.android/;./gradlew clean;./gradlew assemble;cd -;./gradlew assemble

分號分隔了每條命令,總結起來就是

清理項目;進入flutter module;更新包信息和清理;返回當前目錄;進入flutter module .android 項目目錄;清理打包;返回當前目錄;打包

後續假設你 flutter module 沒有更新過。那麼之後修改本地項目以後,就直接執行**./gradlew assemble**。
切記不要執行 clean 或者 rebuild 。也不要點擊 IDE 運行按鈕。由於 IDE 運行按鈕會默認先 clean。

固然上面的 assemble 命令學習 Android 的都懂,就是打出全部安裝包。若是你只要 debug 包,能夠改成 assembleDebug。
另外若是你要安裝到設備,能夠改成 installDebug。
這裏就不展開了。

這裏先留個懸念,打出的 debug 包能夠用,可是 release 包依然會 crash。緣由在後面混淆文章咱們再講。

4.推薦集成管理方式

咱們知道,通常公司對於項目都有對應的管理工具。

這裏假設項目是經過 GitLab 進行管理的。

那麼咱們要如何集成呢?

以上面爲例子,假設 MyApp 項目下面有 sub 子目錄,子目錄下面建立了 my_flutter 模塊。

由於 my_flutter 模塊是跨平臺使用的,除了 Android 端,iOS 端也要用。所以大機率會放到 GitLab 倉庫上面。

因此如何來保證你本地的 my_flutter 是最新的,同時你作的修改可以同步到 MyApp GitLab 同時又同步到 my_flutter GitLab 呢?

這邊推薦使用 git subtree 來管理。

涉及代碼倉庫公用的都推薦 git subtree 來管理。

如何使用呢?(以咱們上面的例子來講明)

1)在主項目倉庫新增子倉庫。
git subtree add --prefix=sub/my_flutter 子倉庫git地址 master --squash
(--squash參數表示不拉取歷史信息,而只生成一條commit信息。)

上面的子倉庫git地址指的是 my_flutter 所放的地址。

接下來執行git status能夠看到有 commit 記錄。

而後能夠執行git push命令將新建立的子倉庫推送到 MyApp 的代碼倉庫中。

2)拉取子倉庫更新

使用git subtree pull命令。

好比這裏 my_flutter 更新了,使用以下命令拉取:

git subtree pull --prefix=sub/my_flutter 子倉庫git地址 master --squash

表示從 master 分支拉取更新。若是你想從 develop 或者其餘分支拉取更新,則作對應修改便可。

3)推送更新到子倉庫

使用git subtree push命令。

好比這裏本地 my_flutter 修改了,使用以下命令推送:

git subtree push --prefix=sub/my_flutter 子倉庫git地址 develop

表示將更新推送到 develop 分支。若是你想推送到其餘分支,則將 develop 改成對應推送分支名便可。

4)簡化 git subtree 命令

你們能夠看到上面的命令中子倉庫 git 地址比較固定並且每一個命令都有用到。

而且相對比較長,好比 https://github.com/nesger/FlutterNote.git 這個。

所以,咱們能夠給這個起個 alias(別名)。

舉個例子,假設上面的子倉庫git地址https://github.com/nesger/FlutterNote.git,那麼咱們能夠執行以下操做:

git remote add -f my_flutter https://github.com/nesger/FlutterNote.git

這樣上面的原命令

git subtree add --prefix=sub/my_flutter https://github.com/nesger/FlutterNote.git  master --squash
git subtree pull --prefix=sub/my_flutter https://github.com/nesger/FlutterNote.git master --squash  
git subtree push --prefix=sub/my_flutter https://github.com/nesger/FlutterNote.git develop

能夠對應修改成:

git subtree add --prefix=sub/my_flutter my_flutter master --squash
git subtree pull --prefix=sub/my_flutter my_flutter master --squash  
git subtree push --prefix=sub/my_flutter my_flutter develop

能夠看到命令簡化了不少。尤爲這個命令使用比較頻繁。能夠提升效率。

舒適提示:

在使用git subtree pull命令進行子倉庫更新以前,須要保證本地沒有修改。

什麼意思?

就是你在本地執行**git status .**時提示沒有修改的文件。
這個時候你再去拉取才不會拉取失敗。不然會有下面提示:

Working tree has modifications.  Cannot add.

因此通常 flutter module 有更新後,先推送到主項目倉庫,再推送到子倉庫。

若是是臨時不重要修改,則先 revert 或者將修改文件保存在另外位置。

總之拉取子倉庫更新的時候本地不要有修改的文件。

上述git subtree相關命令都是在主項目的目錄下面執行的。

更多閱讀: Flutter 即學即用系列博客——01 環境搭建
Flutter 即學即用系列博客——02 一個純 Flutter Demo 說明

原文出處:https://www.cnblogs.com/nesger/p/10388401.html

相關文章
相關標籤/搜索