在上一篇文章中,我與你介紹了 Flutter 工程的資源管理機制。在 Flutter 中,資源采用先聲明后使用的機制,在 pubspec.yaml 顯式地聲明資源路徑后,才可以使用。
對于圖片,Flutter 基于像素密度,設立不同分辨率的目錄分開管理,但只需要在 pubspec.yaml 聲明一次;而字體則基于樣式支持,除了正常字體,還可以支持粗體、斜體等樣式。最后,由于 Flutter 需要原生運行環境,因此對于在其啟動之前所需的啟動圖和圖標這兩類特殊資源,我們還需要分別去原生工程中進行相應的設置。
其實,除了管理這些資源外,pubspec.yaml 更為重要的作用是管理 Flutter 工程代碼的依賴,比如第三方庫、Dart 運行環境、Flutter SDK 版本都可以通過它來進行統一管理。所以,pubspec.yaml 與 iOS 中的 Podfile、Android 中的 build.gradle、前端的 package.json 在功能上是類似的。
那么,今天這篇文章,我就主要與你分享,在 Flutter 中如何通過配置文件來管理工程代碼依賴。
## Pub
Dart 提供了包管理工具 Pub,用來管理代碼和資源。從本質上說,包(package)實際上就是一個包含了 pubspec.yaml 文件的目錄,其內部可以包含代碼、資源、腳本、測試和文檔等文件。包中包含了需要被外部依賴的功能抽象,也可以依賴其他包。
與 Android 中的 JCenter/Maven、iOS 中的 CocoaPods、前端中的 npm 庫類似,Dart 提供了官方的包倉庫 Pub。通過 Pub,我們可以很方便地查找到有用的第三方包。
當然,這并不意味著我們可以簡單地拿別人的庫來拼湊成一個應用程序。**Dart 提供包管理工具 Pub 的真正目的是,讓你能夠找到真正好用的、經過線上大量驗證的庫,復用他人的成果來縮短開發周期,提升軟件質量。**
在 Dart 中,庫和應用都屬于包。pubspec.yaml 是包的配置文件,包含了包的元數據(比如,包的名稱和版本)、運行環境(也就是 Dart SDK 與 Fluter SDK 版本)、外部依賴、內部配置(比如,資源管理)。
在下面的例子中,我們聲明了一個 flutter\_app\_example 的應用配置文件,其版本為 1.0,Dart 運行環境支持 2.1 至 3.0 之間,依賴 flutter 和 cupertino\_icon:
~~~
name: flutter_app_example # 應用名稱
description: A new Flutter application. # 應用描述
version: 1.0.0
#Dart 運行環境區間
environment:
sdk: ">=2.1.0 <3.0.0"
#Flutter 依賴庫
dependencies:
flutter:
sdk: flutter
cupertino_icons: ">0.1.1"
~~~
運行環境和依賴庫 cupertino\_icons 冒號后面的部分是版本約束信息,由一組空格分隔的版本描述組成,可以支持指定版本、版本號區間,以及任意版本這三種版本約束方式。比如上面的例子中,cupertino\_icons 引用了大于 0.1.1 的版本。
需要注意的是,由于元數據與名稱使用空格分隔,因此版本號中不能出現空格;同時又由于大于符號“>”也是 YAML 語法中的折疊換行符號,因此在指定版本范圍的時候,必須使用引號, 比如">=2.1.0 < 3.0.0"。
**對于包,我們通常是指定版本區間,而很少直接指定特定版本**,因為包升級變化很頻繁,如果有其他的包直接或間接依賴這個包的其他版本時,就會經常發生沖突。
而**對于運行環境,如果是團隊多人協作的工程,建議將 Dart 與 Flutter 的 SDK 環境寫死,統一團隊的開發環境**,避免因為跨 SDK 版本出現的 API 差異進而導致工程問題。
比如,在上面的示例中,我們可以將 Dart SDK 寫死為 2.3.0,Flutter SDK 寫死為 1.2.1。
~~~
environment:
sdk: 2.3.0
flutter: 1.2.1
~~~
基于版本的方式引用第三方包,需要在其 Pub 上進行公開發布,我們可以訪問[https://pub.dev/](https://pub.dev/)來獲取可用的第三方包。而對于不對外公開發布,或者目前處于開發調試階段的包,我們需要設置數據源,使用本地路徑或 Git 地址的方式進行包聲明。
在下面的例子中,我們分別以路徑依賴以及 Git 依賴的方式,聲明了 package1 和 package2 這兩個包:
~~~
dependencies:
package1:
path: ../package1/ # 路徑依賴
date_format:
git:
url: https://github.com/xxx/package2.git #git 依賴
~~~
在開發應用時,我們可以不寫明具體的版本號,而是以區間的方式聲明包的依賴;但對于一個程序而言,其運行時具體引用哪個版本的依賴包必須要確定下來。因此,**除了管理第三方依賴,包管理工具 Pub 的另一個職責是,找出一組同時滿足每個包版本約束的包版本。**包版本一旦確定,接下來就是下載對應版本的包了。
對于 dependencies 中的不同數據源,Dart 會使用不同的方式進行管理,最終會將遠端的包全部下載到本地。比如,對于 Git 聲明依賴的方式,Pub 會 clone Git 倉庫;對于版本號的方式,Pub 則會從 pub.dartlang.org 下載包。如果包還有其他的依賴包,比如 package1 包還依賴 package3 包,Pub 也會一并下載。
然后,在完成了所有依賴包的下載后,**Pub 會在應用的根目錄下創建.packages 文件**,將依賴的包名與系統緩存中的包文件路徑進行映射,方便后續維護。
最后,**Pub 會自動創建 pubspec.lock 文件**。pubspec.lock 文件的作用類似 iOS 的 Podfile.lock 或前端的 package-lock.json 文件,用于記錄當前狀態下實際安裝的各個直接依賴、間接依賴的包的具體來源和版本號。
比較活躍的第三方包的升級通常比較頻繁,因此對于多人協作的 Flutter 應用來說,我們需要把 pubspec.lock 文件也一并提交到代碼版本管理中,這樣團隊中的所有人在使用這個應用時安裝的所有依賴都是完全一樣的,以避免出現庫函數找不到或者其他的依賴錯誤。
**除了提供功能和代碼維度的依賴之外,包還可以提供資源的依賴**。在依賴包中的 pubspec.yaml 文件已經聲明了同樣資源的情況下,為節省應用程序安裝包大小,我們需要復用依賴包中的資源。
在下面的例子中,我們的應用程序依賴了一個名為 package4 的包,而它的目錄結構是這樣的:
~~~
pubspec.yaml
└──assets
├──2.0x
│ └── placeholder.png
└──3.0x
└── placeholder.png
~~~
其中,placeholder.png 是可復用資源。因此,在應用程序中,我們可以通過 Image 和 AssetImage 提供的 package 參數,根據設備實際分辨率去加載圖像。
~~~
Image.asset('assets/placeholder.png', package: 'package4');
AssetImage('assets/placeholder.png', package: 'package4');
例子
~~~
## 例子
接下來,我們通過一個日期格式化的例子,來演示如何使用第三方庫。
在 Flutter 中,提供了表達日期的數據結構[DateTime](https://api.flutter.dev/flutter/dart-core/DateTime-class.html),這個類擁有極大的表示范圍,可以表達 1970-01-01 UTC 時間后 100,000,000 天內的任意時刻。不過,如果我們想要格式化顯示日期和時間,DateTime 并沒有提供非常方便的方法,我們不得不自己取出年、月、日、時、分、秒,來定制顯示方式。
值得慶幸的是,我們可以通過 date\_format 這個第三方包來實現我們的訴求:date\_format 提供了若干常用的日期格式化方法,可以很方便地實現格式化日期的功能。
**首先**,我們在 Pub 上找到 date\_format 這個包,確定其使用說明:
:-: 
圖 1 date\_format 使用說明
date\_format 包最新的版本是 1.0.6,于是**接下來**我們把 date\_format 添加到 pubspec.yaml 中:
~~~
dependencies:
date_format: 1.0.6
~~~
**隨后**,IDE(Android Studio)監測到了配置文件的改動,提醒我們進行安裝包依賴更新。于是,我們點擊 Get dependencies,下載 date\_format :
:-: 
圖 2 下載安裝包依賴
下載完成后,我們就可以在工程中使用 date\_format 來進行日期的格式化了:
~~~
print(formatDate(DateTime.now(), [mm, '月', dd, '日', hh, ':', n]));
// 輸出 2019 年 06 月 30 日 01:56
print(formatDate(DateTime.now(), [m, '月第', w, '周']));
// 輸出 6 月第 5 周
~~~
## 總結
好了,今天的分享就到這里。我們簡單回顧一下今天的內容。
在 Flutter 中,資源與工程代碼依賴屬于包管理范疇,采用包的配置文件 pubspec.yaml 進行統一管理。
我們可以通過 pubspec.yaml 設置包的元數據(比如,包的名稱和版本)、運行環境(比如,Dart SDK 與 Fluter SDK 版本)、外部依賴和內部配置。
對于依賴的指定,可以以區間的方式確定版本兼容范圍,也可以指定本地路徑、Git、Pub 這三種不同的數據源,包管理工具會找出同時滿足每個依賴包版本約束的包版本,然后依次下載,并通過.packages 文件建立下載緩存與包名的映射,最后統一將當前狀態下,實際安裝的各個包的具體來源和版本號記錄至 pubspec.lock 文件。
現代編程語言大都自帶第依賴管理機制,其核心功能是為工程中所有直接或間接依賴的代碼庫找到合適的版本,但這并不容易。就比如前端的依賴管理器 npm 的早期版本,就曾因為不太合理的算法設計,導致計算依賴耗時過長,依賴文件夾也高速膨脹,一度被開發者們戲稱為“黑洞”。而 Dart 使用的 Pub 依賴管理機制所采用的[PubGrub 算法](https://github.com/dart-lang/pub/blob/master/doc/solver.md)則解決了這些問題,因此被稱為下一代版本依賴解決算法,在 2018 年底被蘋果公司吸納,成為 Swift 所采用的[依賴管理器算法](https://github.com/apple/swift-package-manager/pull/1918)。
當然,如果你的工程里的依賴比較多,并且依賴關系比較復雜,即使再優秀的依賴解決算法也需要花費較長的時間才能計算出合適的依賴庫版本。如果我們想減少依賴管理器為你尋找代碼庫依賴版本所耗費的時間,一個簡單的做法就是從源頭抓起,在 pubspec.yaml 文件中固定那些依賴關系復雜的第三方庫們,及它們遞歸依賴的第三方庫的版本號。
## 思考題
最后,我給你留下兩道思考題吧。
1. pubspec.yaml、.packages 與 pubspec.lock 這三個文件,在包管理中的具體作用是什么?
2. .packages 與 pubspec.lock 是否需要做代碼版本管理呢?為什么?
- 前言
- 開篇詞
- 預習篇
- 01丨預習篇 · 從0開始搭建Flutter工程環境
- 02丨預習篇 · Dart語言概覽
- Flutter開發起步
- 03丨深入理解跨平臺方案的歷史發展邏輯
- 04丨Flutter區別于其他方案的關鍵技術是什么?
- 05丨從標準模板入手,體會Flutter代碼是如何運行在原生系統上的
- Dart語言基礎
- 06丨基礎語法與類型變量:Dart是如何表示信息的?
- 07丨函數、類與運算符:Dart是如何處理信息的?
- 08丨綜合案例:掌握Dart核心特性
- Flutter基礎
- 09丨Widget,構建Flutter界面的基石
- 10丨Widget中的State到底是什么?
- 11丨提到生命周期,我們是在說什么?
- 12丨經典控件(一):文本、圖片和按鈕在Flutter中怎么用?
- 13丨ListView在Flutter中是什么?
- 14 丨 經典布局:如何定義子控件在父容器中排版位置?
- 15 丨 組合與自繪,我該選用何種方式自定義Widget?
- 16 丨 從夜間模式說起,如何定制不同風格的App主題?
- 17丨依賴管理(一):圖片、配置和字體在Flutter中怎么用?
- 18丨依賴管理(二):第三方組件庫在Flutter中要如何管理?
- 19丨用戶交互事件該如何響應?
- 20丨關于跨組件傳遞數據,你只需要記住這三招
- 21丨路由與導航,Flutter是這樣實現頁面切換的
- Flutter進階
- 22丨如何構造炫酷的動畫效果?
- 23丨單線程模型怎么保證UI運行流暢?
- 24丨HTTP網絡編程與JSON解析
- 25丨本地存儲與數據庫的使用和優化
- 26丨如何在Dart層兼容Android-iOS平臺特定實現?(一)
- 27丨如何在Dart層兼容Android-iOS平臺特定實現?(二)
- 28丨如何在原生應用中混編Flutter工程?
- 29丨混合開發,該用何種方案管理導航棧?
- 30丨為什么需要做狀態管理,怎么做?
- 31丨如何實現原生推送能力?
- 32丨適配國際化,除了多語言我們還需要注意什么
- 33丨如何適配不同分辨率的手機屏幕?
- 34丨如何理解Flutter的編譯模式?
- 35丨HotReload是怎么做到的?
- 36丨如何通過工具鏈優化開發調試效率?
- 37丨如何檢測并優化FlutterApp的整體性能表現?
- 38丨如何通過自動化測試提高交付質量?
- Flutter綜合應用
- 39丨線上出現問題,該如何做好異常捕獲與信息采集?
- 40丨衡量FlutterApp線上質量,我們需要關注這三個指標
- 41丨組件化和平臺化,該如何組織合理穩定的Flutter工程結構?
- 42丨如何構建高效的FlutterApp打包發布環境?
- 43丨如何構建自己的Flutter混合開發框架(一)?
- 44丨如何構建自己的Flutter混合開發框架(二)?
- 結束語
- 結束語丨勿畏難,勿輕略