Flutter 外掛使用必知必會

升級之路發表於2018-12-24

本文目的

  • 介紹外掛的搜尋方式,三方庫評估的意義和基本思路
  • 介紹如何給應用新增外掛,從原始碼角度看外掛是如何註冊生效的
  • 介紹如何給外掛指定版本和解決版本衝突問題
  • 介紹依賴源的種類,如何從pub/git/本地指定依賴庫
  • 介紹依賴的2種分類方式:直接依賴和傳遞依賴;常規依賴和dev依賴
  • 介紹pub這個包管理工具獲取依賴的流程和 lockfile 檔案的意義

目錄結構

  • 獲取外掛
  • 外掛的使用
  • 依賴的分類
  • 包管理
  • 總結

獲取外掛

這裡的 Flutter 外掛,不是 IDE 中的外掛,而指的是包含平臺特定程式碼的包,用以提供 Flutter 框架所不支援的一些 Native API 的功能。比如常用的 shared_preferences , path_provider 等。

Flutter 框架為我們提供了很多 UI 層的控制和支援,但 APP 的功能並不侷限在顯示上,還需要依賴 Native 平臺的支援,比如檔案系統,攝像頭等硬體呼叫等。所以Flutter為我們提供了一個Platform Channel的機制,使得 Dart 程式碼可以與 Navtive 程式碼進行互動。基於Platform Channel,開發者可以編寫自己需要的 Native 功能,在 Dart 程式碼中統一呼叫。

搜尋途徑

隨著Flutter社群的成長和壯大,Flutter Plugin 的數量和質量也在不斷提高。當你在開發自己的 App 時,如果遇到依賴 Native 的功能時,不妨先考慮去社群搜尋是否有現成的輪子。推薦2個平臺:

pub.dartlang

針對 dart 語言的三方庫平臺,可以選擇 Flutter 型別進行搜尋,更有針對性,每個庫根據 Popularity ,Health, Maintenance 進行打分,是搜尋的首選。首頁還列出了十幾個 Top Popular 的專案,比如 shared_preferences, url_launcher, path_provider,可以說是基礎必備外掛。

github

flutter plugin 為關鍵字搜尋。相對 pub.dartlang,缺少針對性和評分體系(可以根據 issuestar 數進行評估,但相對來說沒有那麼直觀),但庫數量更多,更新更快(比如修復了 bug 不需要等它釋出到 pub 上)。

不同平臺的外掛,依賴的寫法也有不同,下文會說明。

引入前評估

搜尋到自己所需功能的外掛後,我們就直接拿來主義嗎?對待開源三方庫,我的傾向是先做多方對比,嘗試閱讀原始碼,做到心中有數後再引入。在我們團隊,如果需要引入三方庫,都必須提供一個說明文件,進行引入原因說明。

考量要點:

  • 根據 pub score 或者 github 的 issue,star,維護情況進行評估
  • 功能是否滿足需求,API易用性如何
  • 引入後對包大小影響對比
  • 如果庫比較簡單,可以閱讀原始碼,看它的實現方式是否科學規範
  • 看 native 功能是用什麼語言實現,是否 match 團隊的技術棧,比如我司就更偏好 Kotlin/Swift

如果你發現這個庫部分滿足了業務需求,你可以選擇改進它,或者重新造輪子。如果你覺得庫維護者比較勤快和靠譜,專案的底子也不錯,可以嘗試 Fork 這個專案,並給它提 PR

對編寫外掛感興趣的童鞋可以看看這個姊妹篇:Flutter外掛編寫必知必會

外掛的使用

新增依賴

  1. 開啟專案下的pubspec.yaml檔案,在 dependencies 下新增依賴名稱和版本等資訊

  2. 在命令列下執行flutter packages get,或者在 IDE 中 點選 Packages Get 等待它下載完成,並生成外掛註冊程式碼

    • Android:

      • android/app/scr/main/java/io/flutter/plugins目錄下自動生成的GeneratedPluginRegistrant.java中,會新增外掛的註冊程式碼。
      public final class GeneratedPluginRegistrant {
          public static void registerWith(PluginRegistry registry) {
              if (alreadyRegisteredWith(registry)) { return; }
              UrlLauncherPlugin.registerWith(registry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin"));
              ...
          }
          ...
      }
      複製程式碼
      • 在 App 的 MainActivity 建立時會去呼叫這個註冊方法
      class MainActivity : FlutterActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              GeneratedPluginRegistrant.registerWith(this)
          }
      }
      複製程式碼
    • iOS

      • ios/Runner目錄下自動生成的GeneratedPluginRegistrant.m中,會新增外掛的註冊程式碼。
      #import "GeneratedPluginRegistrant.h"
      #import <url_launcher/UrlLauncherPlugin.h>
      
      @implementation GeneratedPluginRegistrant
      
      + (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
      [FLTUrlLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTUrlLauncherPlugin"]];
      }
      
      @end
      複製程式碼
      • AppDelegate.swift啟動後去呼叫註冊
      @UIApplicationMain
      @objc class AppDelegate: FlutterAppDelegate, WXApiDelegate {
      
          override func application(
              _ application: UIApplication,
              didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
              ) -> Bool {
              GeneratedPluginRegistrant.register(with: self)
              return super.application(application, didFinishLaunchingWithOptions: launchOptions)
          }
      }
      複製程式碼
  3. 在專案檔案中 import 所需的包名,並使用

  4. 如果依賴中存在 platform-specific code (Java/Kotlin for Android, Swift/Objective-C for iOS),要確保程式碼能夠編譯進 App ,必須 Restart App,防止發生MissingPluginException異常。Hot reload 或者 Hot restart 只對 dart 程式碼有效。

版本約束

版本格式:主版本號.次版本號.修訂號,版本號遞增規則如下:

  • 主版本號:當你做了不相容的 API 修改,
  • 次版本號:當你做了向下相容的功能性新增,
  • 修訂號:當你做了向下相容的問題修正。

更詳細的參考語義化版本

基本用法:

any # 所有版本,等同於不寫。對pub執行效能有影響,不推薦
1.2.3 # 明確的版本號
'>=1.2.3' # 還有 >1.2.3, <=1.2.3, <1.2.3
^1.2.3 # Caret syntax 等同於 >=1.2.3 <2.0.0
複製程式碼

注意:如果在版本約束中使用了'>','<'字元,一定要加引號,否則無法被當作 YAML 的語法解析

版本衝突

如果專案依賴了 A , B 庫,他們都依賴了一個 C ,但 C 的版本不同,可能會產生版本衝突。pub 會嘗試找到符合所有依賴約束的版本號。如果找不到能匹配的版本,但 AB 庫依賴 C 庫的 API 是一樣的,那麼可以新增一個依賴覆蓋來強制指定某一版本。

注:pub 是 Dart SDK 提供的一個包管理工具

dependencies:
  some_package:
  other_package:
dependency_overrides:
  url_launcher: '0.4.3'
複製程式碼

如果是 Android 平臺的庫依賴衝突,可以在 appgradle 檔案中強制指定版本

configurations.all {
    resolutionStrategy {
        force 'com.google.guava:guava:23.0-android'
    }
}
複製程式碼

注意: iOS 平臺下 CocoaPods 不支援強制版本覆蓋

從不同的依賴源新增依賴

SDK

The SDK source is used for any SDKs that are shipped along with packages, which may themselves be dependencies. Currently, Flutter is the only SDK that is supported.

通俗講,就是 Flutter SDK 自帶的庫。開啟我們 Flutter 的安裝地址,進入flutter/packages可以看到各種包,如flutter,flutter_driver,flutter_test等。

➜  packages git:(stable) ✗ ls
analysis_options.yaml         flutter_localizations
flutter                       flutter_test
flutter_driver                flutter_tools
flutter_goldens               fuchsia_remote_debug_protocol
flutter_goldens_client
➜  packages git:(stable) ✗ pwd
/Users/xxx/flutter/packages
複製程式碼

在 Flutter 專案中寫過測試的同學對上面幾個依賴應該不陌生

dependencies:
  flutter:
    sdk: flutter # 來源於flutter sdk
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_driver:
      sdk: flutter
複製程式碼

Hosted

A hosted package is one that can be downloaded from pub.dartlang.org (or another HTTP server that speaks the same API).

pub

dependencies:
  transmogrify: ^1.4.0 
複製程式碼

自己的伺服器

dependencies:
  transmogrify: ^1.4.0 
  transmogrify:
    hosted:
      name: transmogrify
      url: http://your-package-server.com
    version: ^1.4.0
複製程式碼

Git

dependencies:
  kittens:
    git: git://github.com/munificent/kittens.git
複製程式碼

指定分支

dependencies:
  kittens:
    url: git://github.com/munificent/kittens.git
    ref: some-branch
複製程式碼

pub 預設包目錄在 git 倉庫的根目錄,如果要指定在別的位置,可以用 path 引數

dependencies:
  kittens:
    git:
      url: git://github.com/munificent/cats.git
      path: path/to/kittens
複製程式碼

本地路徑

特別適用在一個人同時開發專案和依賴庫的情況。因為修改依賴庫的程式碼,在專案中就可以即時生效,有利於除錯和提高效率。

dependencies:
  transmogrify:
    path: /Users/me/transmogrify # 也可以相對路徑,相對路徑以 pubspec.yml 檔案為基準
複製程式碼

依賴的分類

直接依賴和傳遞依賴

根據依賴與專案的關係,可以分為以下2類:

  • immediate dependency :專案中直接使用的依賴,即我們在 pubspec 中列出的依賴,包括 dependciesdev_dependencies
  • transitive dependency :直接依賴所需的依賴, pub 會根據 pubspec 中列出的依賴自動為我們獲取。

舉個例子

比如我們專案依賴 A ,而 A 又依賴 BB 又依賴 C 。那麼 A 是我們專案的immediate dependency, BC 就是transitive dependency

我們可以在命令列中輸入命令flutter packages pub deps,檢視專案的依賴樹。 比如我們在專案中引入了一個支援網路快取的圖片庫cached_network_image: ^0.5.0+1

flutter packages pub deps
Dart SDK 2.1.0-dev.9.4.flutter-f9ebf21297
Flutter SDK 1.0.1-pre.2
your_app_name 2.2.0+10 # 專案名稱和版本
|-- cached_network_image 0.5.1 # 直接依賴
|   |-- flutter... # 直接依賴(因為在專案pubspec中也新增了)
|   '-- flutter_cache_manager 0.2.0+1 # 傳遞依賴
|       |-- flutter...
|       |-- http...
|       |-- path_provider...
|       |-- shared_preferences...
|       |-- synchronized 1.5.3
|       '-- uuid 1.0.3
|           |-- convert...
|           '-- crypto...
...
複製程式碼

常規依賴和dev依賴

根據依賴的作用範圍,可以分為:

  • dependencies :常規依賴
  • dev dependencies :開發時所需依賴。它和常規依賴的區別是,專案依賴庫中的 dev dependencies ,對於你的專案來說是不可見的。

舉個例子

專案的 pubspec.yml 如下。如果 A 有一個 dev_dependencies 依賴 dev_C ,專案最終的依賴是 A , dev_B;不包括 dev_C

dependencies:
  A:
dev_dependencies:
  dev_B: 
複製程式碼

適應場景:如果你需要 importlib 或者 bin 目錄,那麼選擇 dependencies ; 如果你只需要 importtestexample 等,那麼就選擇 dev dependencies 。使用 dev dependencies 的好處是能讓依賴樹更小,從而使 pub 執行更快,能跟容易找到滿足所有約束的包版本。

包管理

在獲取一個新的依賴時,pub 會下載滿足版本條件的最新版本,然後把版本資訊新增到一個 lockfile 中。這個 lockfile 檔案叫 pubspec.lock ,位於專案 pubspec.yml 的同級目錄。它列出了專案的每個依賴(包括直接依賴和傳遞依賴)的版本資訊。我們應該把這個 lockfile 新增到版本控制中,這樣不論開發環境,生產環境都能確保使用了相同的依賴版本。

舉個例子

專案的 pubspec.yml 檔案包含如下依賴

...
dependencies:
  flutter:
    sdk: flutter
  cached_network_image: ^0.5.0+1

dev_dependencies:
  flutter_test:
    sdk: flutter
...
複製程式碼

結合上面提到知識,我們來看看 pubspec.lock 的內容。 lockfile 把所有依賴樹打平,並根據首字母排序

cached_network_image:
    dependency: "direct main" # 直接依賴,常規依賴
    description:
      name: cached_network_image
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "0.5.1"
flutter: 
    dependency: "direct main" # 直接依賴,常規依賴
    description: flutter
    source: sdk
    version: "0.0.0"
flutter_test:
    dependency: "direct dev" # 直接依賴,開發依賴
    description: flutter
    source: sdk
    version: "0.0.0"
uuid:
    dependency: transitive # 傳遞依賴
    description:
      name: uuid
      url: "https://pub.flutter-io.cn"
    source: hosted
    version: "1.0.3"
...
複製程式碼

如果非首次獲取依賴,pub 會從 lockfile 中讀取版本。如果想升級到滿足 pubspec.yml 中約束的最新版本,可以執行 flutter packages upgrade 命令,升級後會更新 lockfile 中的版本。

總結

本文我們主要討論了外掛的獲取和選擇,同時分析了外掛是如何使用並生效的。在介紹外掛版本的部分,提到了語義化版本的概念,不論對外掛的使用者還是開發者,都非常有用,推薦大家去細緻的看看。對於依賴的管理, Dart 語言提供了 pub 這個工具,並運用了 lockfile 思想去保證依賴的一致性,也值得大家學習。Flutter在不斷成長,它的生態也在建立,這離不開開源社群的努力,當你發現想要的功能沒有現成的開源庫,不妨自己去寫一寫。

下一篇,我們來講講Flutter外掛編寫必知必會

參考

本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@akindone

相關文章