Flutter 中的依賴管理

前言

一個應(yīng)用程序主要由兩部分內(nèi)容組成:代碼和資源。代碼關(guān)注邏輯功能,而如圖片、字符串、字體、配置文件等資源則關(guān)注視覺功能。

資源外部化,即把代碼與資源分離,是現(xiàn)代 UI 框架的主流設(shè)計理念。因為這樣不僅有利于單獨維護資源,還可以對特定設(shè)備提供更準(zhǔn)確的兼容性支持,使得我們的應(yīng)用程序可以自動根據(jù)實際運行環(huán)境來組織視覺功能,適應(yīng)不同的屏幕大小和密度等。

隨著各類配置各異的終端設(shè)備越來越多,資源管理也越來越重要。今天就學(xué)習(xí)一下 Flutter 中的圖片、配置和字體的管理機制。


資源管理

在移動開發(fā)中,常見的資源類型包括 JSON 文件、配置文件、圖標(biāo)、圖片以及字體文件等。它們都會被打包到 App 安裝包中,而 App 中的代碼可以在運行時訪問這些資源。

在 Android平臺中,為了區(qū)分不同分辨率的手機設(shè)備,使用以 drawable 分辨率命名的文件夾來分別存放不同分辨率的圖片,其他類型的資源也都有各自的存放方式,比如布局文件放在 res/layout 目錄下,資源描述文件放在 res/values 目錄下,原始文件放在 assets 目錄下等。

而在 Flutter 中,資源管理則簡單得多:資源(assets)可以是任意類型的文件,比如 JSON 配置文件或是字體文件等,而不僅僅是圖片。·

而關(guān)于資源的存放位置,F(xiàn)lutter 并沒有像 Android 那樣預(yù)先定義資源的目錄結(jié)構(gòu),所以我們可以把資源存放在項目中的任意目錄下,只需要使用根目錄下的 pubspec.yaml 文件,對這些資源的所在位置進行顯示聲明就可以了,以幫助 Flutter 識別出這些資源。

而在指定路徑名的過程中,我們即可以對每一個文件進行挨個指定,也可以采用子目錄批量指定的方式。

接下來,以一個示例說明挨個指定的和批量指定這兩種方式的區(qū)別。

如下所示,我們將資源放入 assets 目錄下,其中,兩張圖片 background.png、loading.gif 與 JSON 文件 result.json 在 assets 根目錄,而另一張圖片 food_icon.jpg 則在 assets 的子目錄 icons 下。

Flutter 資源結(jié)構(gòu)圖

通過單個文件聲明的,我們需要完整展開資源的相對路徑;而對于目錄批量指定的方式,只需要在目錄名后加路徑分隔符就可以了:

flutter:
  assets:
    - assets/background.jpg  # 挨個指定資源路徑
    - assets/loading.gif  # 挨個指定資源路徑
    - assets/result.json  # 挨個指定資源路徑
    - assets/icons/  # 子目錄批量指定
    - assets/  # 根目錄也是可以批量指定的

需要注意的是,目錄批量指定并不遞歸,只有在該目錄下的文件才可以被包括,如果下面還有子目錄的話,需要單獨聲明子目錄下的文件。

完成資源的聲明后,我們就可以在代碼中訪問它們了。但是,在 Flutter 中,對不同類型的資源文件處理方式略有差異

對于圖片類資源的訪問,可以使用 Image.asset 構(gòu)造方法完成圖片資源的加載及顯示,在 Flutter 經(jīng)典控件學(xué)習(xí)(文本、圖片、按鈕) 一文中已經(jīng)學(xué)習(xí)過了。

而對于其他資源文件的加載,我們可以通過 Flutter 應(yīng)用的主資源 Bundle 對象 rootBundle,來直接訪問。

對于字符串文件資源,使用 loadString 方法;而對于二進制文件資源,則通過 load 方法。

以下代碼演示了獲取 result.json 文件,并將其打印的過程:

// 使用 rootBundle 需要導(dǎo)入
import 'package:flutter/services.dart';  

rootBundle.loadString('assets/result.json').then((msg) => print(msg));

打印結(jié)果如下:

打印結(jié)果

與 Android、iOS 開發(fā)類似,Flutter 也遵循了基于像素密度的管理方式,如 1.0x、2.0x、3.0x 或其他任意倍數(shù),F(xiàn)lutter 可以根據(jù)當(dāng)前設(shè)備分辨率加載最接近設(shè)備像素比例的圖片資源。而為了讓 Flutter 更好地識別,我們的資源目錄應(yīng)該將 1.0x、2.0x 與 3.0x 的圖片資源分開管理。

以 background.jpg 圖片為例,這張圖片位于 assets 目錄下。如果想讓 Flutter 適配不同的分辨率,我們需要將其他分辨率的圖片放到對應(yīng)的分辨率子目錄中,如下所示:

assets
├── background.jpg    //1.0x 圖
├── 2.0x
│   └── background.jpg  //2.0x 圖
└── 3.0x
    └── background.jpg  //3.0x 圖

而在 pubspec.yaml 文件聲明這個圖片資源時,僅聲明 1.0x 圖資源即可:

flutter:
  assets:
    - assets/background.jpg   #1.0x 圖資源

1.0x 分辨率的圖片是資源標(biāo)識符,而 Flutter 則會根據(jù)實際屏幕像素比例加載相應(yīng)分辨率的圖片。這時,如果主資源缺少某個分辨率資源,F(xiàn)lutter 會在剩余的分辨率資源中選擇最接近的分辨率資源去加載。

舉個例子,如果我們的 App 包只包括了 2.0x 資源,對于屏幕像素比為 3.0 的設(shè)備,則會自動降級讀取 2.0x 的資源。不過需要注意的是,即使我們的 App 包沒有包含 1.0x 資源,我們?nèi)匀恍枰裆厦婺菢釉?pubspec.yaml 中將它顯示地聲明出來,因為它是資源的標(biāo)識符。

字體則是另外一類較為常用的資源。手機操作系統(tǒng)一般只有默認的幾種字體,在大部分情況下可以滿足我們的正常需求。但是,在一些特殊的情況下,我們可能需要使用自定義字體來提升視覺體驗。

在 Flutter 中,使用自定義字體同樣需要在 pubspec.yaml 文件中提前聲明。需要注意的是,字體實際上是字符圖形的映射。所以,除了正常字體文件外,如果你的應(yīng)用需要支持粗體和斜體,同樣也需要有對應(yīng)的粗體和斜體字體文件。

在將 RobotoCondensed 字體擺放至 assets 目錄下的 fonts 子目錄后,下面的代碼演示了如何將支持斜體與粗體的 RobotoCondensed 字體加到我們的應(yīng)用中:

fonts:
  - family: RobotoCondensed  # 字體名字
    fonts:
      - asset: assets/fonts/RobotoCondensed-Regular.ttf # 普通字體
      - asset: assets/fonts/RobotoCondensed-Italic.ttf 
        style: italic  # 斜體
      - asset: assets/fonts/RobotoCondensed-Bold.ttf 
        weight: 700  # 粗體

這些聲明其實都對應(yīng)著 TextStyle 中的樣式屬性,如字體名 family 對應(yīng)著 fontFamily 屬性、斜體 italic 與正常 normal 對應(yīng)著 style 屬性、字體粗細 weight 對應(yīng)著 fontWeight 屬性等。在使用時,我們只需要在 TextStyle 中指定對應(yīng)的字體即可:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        // Row 控件,用來水平擺放子 Widget
        body: Column(
          children: <Widget>[
            Text("This is RobotoCondensed",
                style: TextStyle(
                  fontSize: 20,
                  fontFamily: 'RobotoCondensed', // 普通字體
                )),
            Text("This is RobotoCondensed",
                style: TextStyle(
                  fontSize: 20,
                  fontFamily: 'RobotoCondensed',
                  fontWeight: FontWeight.w700, // 粗體
                )),
            Text("This is RobotoCondensed italic",
                style: TextStyle(
                  fontSize: 20,
                  fontFamily: 'RobotoCondensed',
                  fontStyle: FontStyle.italic, // 斜體
                )),
          ],
        ));
  }
}
自定義字體

第三方庫依賴管理

其實,除了管理這些資源外,pubspec.yaml 更為重要的作用是管理 Flutter 工程代碼的依賴,比如第三方庫、Dart 運行環(huán)境、Flutter SDK 版本都可以通過它來進行統(tǒng)一管理。所以,pubspec.yaml 與 iOS 中的 Podfile、Android 中的 build.gradle、前端的 package.json 在功能上是類似的。

Pub

Dart 提供了包管理工具 Pub,用來管理代碼和資源。從本質(zhì)上說,包(package)實際上就是一個包含了 pubspec.yaml 文件的目錄,其內(nèi)部可以包含代碼、資源、腳本、測試和文檔等文件。包中包含了需要被外部依賴的功能抽象,也可以依賴其他包。

與 Android 中的 JCenter/Maven、iOS 中的 CocoaPods、前端中的 npm 庫類似,Dart 提供了官方的包倉庫 Pub。通過 Pub,我們可以很方便地查找到有用的第三方包。

當(dāng)然,這并不意味著我們可以簡單地拿別人的庫來拼湊成一個應(yīng)用程序。Dart 提供包管理工具 Pub 的真正目的是,讓你能夠找到真正好用的、經(jīng)過線上大量驗證的庫,復(fù)用他人的成果來縮短開發(fā)周期,提升軟件質(zhì)量,而不是重復(fù)造輪子。

在 Dart 中,庫和應(yīng)用都屬于包。pubspec.yaml 是包的配置文件,包含了包的元數(shù)據(jù)(比如,包的名稱和版本)、運行環(huán)境(也就是 Dart SDK 與 Fluter SDK 版本)、外部依賴、內(nèi)部配置(比如,資源管理)。

看下面的配置,聲明了一個 flutter_assets 的應(yīng)用配置文件,其版本為 1.0,Dart 運行環(huán)境區(qū)間 2.1 至 3.0 之間,依賴 flutter 和 cupertino_icons:

# 應(yīng)用名稱
name: flutter_assets
# 應(yīng)用描述
description: A new Flutter application.
# 應(yīng)用版本
version: 1.0.0
# Dart 運行環(huán)境區(qū)間
environment:
  sdk: ">=2.1.0 <3.0.0"
# Flutter 依賴庫
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  • 對于包,我們通常是指定版本區(qū)間,而很少直接指定特定版本,因為包升級變化很頻繁,如果有其他的包直接或間接依賴這個包的其他版本時,就會經(jīng)常發(fā)生沖突。

  • 對于運行環(huán)境,如果是團隊多人協(xié)作的工程,建議將 Dart 與 Flutter 的 SDK 環(huán)境寫死,統(tǒng)一團隊的開發(fā)環(huán)境,避免因為跨 SDK 版本出現(xiàn)的 API 差異進而導(dǎo)致工程問題。

比如,在上面的示例中,我們可以將 Dart SDK 寫死為 2.3.0,F(xiàn)lutter SDK 寫死為 1.2.1。

environment:
  sdk: 2.3.0
  flutter: 1.2.1

基于版本的方式引用第三方包,需要在其 Pub 上進行公開發(fā)布,我們可以訪問 https://pub.dev/
來獲取可用的第三方包。而對于不對外公開發(fā)布,或者目前處于開發(fā)調(diào)試階段的包,我們需要設(shè)置數(shù)據(jù)源,使用本地路徑或 Git 地址的方式進行包聲明。

在下面的例子中,我們分別以路徑依賴以及 Git 依賴的方式,聲明了 package1 和 package2 這兩個包:

dependencies:
  package1:
    path: ../package1/  # 路徑依賴
  date_format:
    git:
      url: https://github.com/xxx/package2.git #git 依賴

在開發(fā)應(yīng)用時,我們可以不寫明具體的版本號,而是以區(qū)間的方式聲明包的依賴;但對于一個程序而言,其運行時具體引用哪個版本的依賴包必須要確定下來。因此,除了管理第三方依賴,包管理工具 Pub 的另一個職責(zé)是,找出一組同時滿足每個包版本約束的包版本。包版本一旦確定,接下來就是下載對應(yīng)版本的包了。

對于 dependencies 中的不同數(shù)據(jù)源,Dart 會使用不同的方式進行管理,最終會將遠端的包全部下載到本地。比如,對于 Git 聲明依賴的方式,Pub 會 clone Git 倉庫;對于版本號的方式,Pub 則會從 pub.dartlang.org 下載包。

然后,Pub 會在應(yīng)用的根目錄下創(chuàng)建.packages 文件,將依賴的包名與系統(tǒng)緩存中的包文件路徑進行映射,方便后續(xù)維護。

最后,Pub 會自動創(chuàng)建 pubspec.lock 文件。pubspec.lock 文件的作用類似 iOS 的 Podfile.lock 或前端的 package-lock.json 文件,用于記錄當(dāng)前狀態(tài)下實際安裝的各個直接依賴、間接依賴的包的具體來源和版本號。

比較活躍的第三方包的升級通常比較頻繁,因此對于多人協(xié)作的 Flutter 應(yīng)用來說,我們需要把 pubspec.lock 文件也一并提交到代碼版本管理中,這樣團隊中的所有人在使用這個應(yīng)用時安裝的所有依賴都是完全一樣的,以避免出現(xiàn)庫函數(shù)找不到或者其他的依賴錯誤。

除了提供功能和代碼維度的依賴之外,包還可以提供資源的依賴。在依賴包中的 pubspec.yaml 文件已經(jīng)聲明了同樣資源的情況下,為節(jié)省應(yīng)用程序安裝包大小,我們需要復(fù)用依賴包中的資源。

例子

在 Flutter 中,提供了表達日期的數(shù)據(jù)結(jié)構(gòu)DateTime
,這個類擁有極大的表示范圍,可以表達 1970-01-01 UTC 時間后 100,000,000 天內(nèi)的任意時刻。不過,如果我們想要格式化顯示日期和時間,DateTime 并沒有提供非常方便的方法,我們不得不自己取出年、月、日、時、分、秒,來定制顯示方式。

值得慶幸的是,我們可以通過 date_format 這個第三方包來實現(xiàn)我們的訴求:date_format 提供了若干常用的日期格式化方法,可以很方便地實現(xiàn)格式化日期的功能。

首先,我們在 Pub 上找到 date_format 這個包,確定其使用說明:

date_format

date_format 包最新的版本是 1.0.6,于是接下來我們把 date_format 添加到 pubspec.yaml 中:

隨后,IDE(Android Studio)監(jiān)測到了配置文件的改動,提醒我們進行安裝包依賴更新。于是,我們點擊 Get dependencies,下載 date_format :

下載完成后,我們就可以在工程中使用 date_format 來進行日期的格式化了:

print(formatDate(DateTime.now(), [mm, '月', dd, '日', hh, ':', n]));
// 輸出 2019 年 06 月 30 日 01:56
print(formatDate(DateTime.now(), [m, '月第', w, '周']));
// 輸出 6 月第 5 周

總結(jié)

現(xiàn)代編程語言大都自帶第依賴管理機制,其核心功能是為工程中所有直接或間接依賴的代碼庫找到合適的版本,但這并不容易。就比如前端的依賴管理器 npm 的早期版本,就曾因為不太合理的算法設(shè)計,導(dǎo)致計算依賴耗時過長,依賴文件夾也高速膨脹,一度被開發(fā)者們戲稱為“黑洞”。而 Dart 使用的 Pub 依賴管理機制所采用的PubGrub
則解決了這些問題,因此被稱為下一代版本依賴解決算法,在 2018 年底被蘋果公司吸納,成為 Swift 所采用的依賴管理器算法

當(dāng)然,如果工程里的依賴比較多,并且依賴關(guān)系比較復(fù)雜,即使再優(yōu)秀的依賴解決算法也需要花費較長的時間才能計算出合適的依賴庫版本。如果我們想減少依賴管理器為你尋找代碼庫依賴版本所耗費的時間,一個簡單的做法就是從源頭抓起,在 pubspec.yaml 文件中固定那些依賴關(guān)系復(fù)雜的第三方庫們,及它們遞歸依賴的第三方庫的版本號。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內(nèi)容