1. 程式人生 > 其它 >基於開源專案搭建屬於自己的技術堆疊

基於開源專案搭建屬於自己的技術堆疊

在技術面試的時候肯定都會問到使用了哪些第三方框架,為什麼使用它而不用其他的。身邊朋友就有這樣的親身經歷: 面試官:你們專案中載入圖片都是用的什麼框架? 面試者:Glide 啊(內心竊喜) 面試官:為什麼使用 Glide 而不用其他的? 面試者:(沉默 10s),Glide 好啊,我比較喜歡。(內心不安) 面試官:......(能不能好好聊天了)

這篇博文主要就是針對平常使用到的框架做一個整理和分析其優劣。

為了從整體上進行把握,先來看看一個完整的 APP 整體架構

1. APP 的整體架構

從較高的層次將,一個 APP 的整體架構可以分為兩層,即應用層和基礎框架層。

  • 應用層專注於行業領域的實現,例如金融、支付、地圖導航、社交等,它直接面向用戶,是使用者對產品的第一層感知。
  • 基礎框架層專注於技術領域的實現,提供 APP 公有的特性,避免重複製造輪子,它是使用者對產品的第二層感知,例如效能、穩定性等。

一個理想的 APP 架構,應該擁有如下特點

  • 支援跨平臺開發
  • 具有清晰的層次劃分,同一層模組間充分解耦,模組內部符合面向物件設計六大原則
  • 在功能、效能、穩定性等方面達到綜合最優

基於以上設計原則,我們可以看出 APP 架構圖,最上層是應用層,應用層以下都屬於基礎框架層,基礎框架層包括:元件層、基礎層和跨平臺層。

我們要討論的重點是基礎層,下面開始一步一步地闡述如何基於開源函式庫搭建屬於自己的一個基礎技術堆疊。

2. 技術選型的考量點

首先要明確的是,我們選擇開源函式庫或者第三方 SDK、一般需要綜合考慮一下幾個方面

  • 特性:提供的特性是否滿足專案的需求
  • 可用性,是否提供了簡潔便利的 API,方便開發者整合使用。
  • 效能:效能不能太差,否則專案後面效能優化會過不去,可能回出現需要替換函式庫的情況。
  • 文件:文件應該比較齊全,且可讀性高。
  • 技術支援:遇到問題或者發現 BUG,是否能夠及時得到官方的技術支援是很重要的
  • 大小:引入函式庫會增加 APK 的大小,需要慎重抉擇
  • 方法數:如果函式庫方法數太多,積累起來會導致你的 APP 遇到 64K 問題,應該儘量避免

3. 日誌記錄能力

日誌記錄無論在服務端開發還是移動端開發,都是一個基礎且重要的能力,開發人員在程式碼除錯以及錯誤定位過程中,大多說都要依賴日誌資訊,一個簡潔靈活的日誌記錄模組是相當重要的。Logger 是基於系統 Log 類基礎上進行的封裝,但新增瞭如下超讚的特性。

  • 在 Logcat 中完美的格式化輸出,再也不用擔心和手機其他 APP 或者系統的日誌資訊相混淆了
  • 包含執行緒、類、方法資訊,可以清楚地看到日誌記錄的呼叫堆疊
  • 支援跳轉到原始碼處
  • 支援格式化輸出 JSON、XML 格式資訊

Logcat 截圖

當然 Logger 也不是完備的,它雖然支援格式化輸出 JSON、XML,但並不支援諸如 List、Set、Map 和陣列等常見 Java 集合類的格式化輸出。如何解決呢?可以看下 LogUtils 這個開源庫,它實現了 Logger 缺失的上述特性。

再者,Logger 只支援輸出日誌到 Logcat,但專案開發中往往還存在將日誌儲存到磁碟上的需求,如何將兩者結合起來呢?這是就遇到了 timber 。

timber 是 JakeWharton 開源的一個日誌記錄庫,它的特點是可擴充套件的框架,開發者可以方便快捷的整合不同型別的日誌記錄方式,例如,列印日誌到 Logcat、列印日誌到檔案、列印日誌到網路等,timber 通過一行程式碼就可以同時呼叫多種方式。

timber 的思想很簡單,就是維護一個森林物件,它由不同型別的日誌樹組合而成,例如,Logcat 記錄樹、檔案記錄樹、網路記錄樹等,森林物件提供對外的介面進行日誌列印。每種型別的樹都可以通過種植操作把自己新增到森林物件中,或者通過移除操作從森林物件中刪除,從而實現該型別日誌記錄的開啟和關閉。

最終我們的日誌記錄模組將由 timber+Logger+LogUtils 組成,當然輪子找到了,輪子的相容合併就得靠我們自己實現了,同時我們還得增加列印到檔案的日誌樹和列印到網路的日誌樹實現。

4. JSON 解析能力

移動網際網路產品與伺服器端通訊的資料格式,如果沒有特殊需求的話,一般都使用 JSON 格式。Android 系統也原生的提供了 JSON 解析的 API,但是它的速度非常慢,而且沒有提供簡潔方便的介面來提高開發者的效率和降低出錯的可能。所以我們就開始找第三方開源庫來實現 JSON 解析,比較優秀的包括如下幾種。

4.1 gson

gosn 是 Google 出品的 JSON 解析函式庫,可以將 JSON 字串反序列化對應的 Java 物件,或者反過來將 Java 物件序列化為對應的 JSON 字串,免去了開發者手動通過 JSONObject 和 JSONArray 將 JSON 欄位逐個進行解析的煩惱,也減少了出錯的可能性,增強了程式碼的質量。使用 gson 解析時,對應的 Java 實體類無需使用註解進行標記,支援任意複雜 Java 物件包括沒有原始碼的物件。

4.2 jackson

jcakson 是 Java 語言的一個流行的 JSON 函式庫,在 Android 開發中使用時,主要包含三部分。

  • jackson-core:JSON 流處理核心庫
  • jackson-databind:資料繫結函式庫,實現 Java 物件和 JSON 字串流的相互轉換。
  • jackson-annotations:databind 使用的註解函式庫

由於 jackson 是針對 Java 語言通用的 JSON 函式庫,並沒有為 Android 優化定製過,因此函式保重包含很多非必要的 API,相比其他的 JSON 函式庫,用於 Android 平臺會更顯著的增大最終生成的 APK 的體積。

4.3 Fastjson

Fastjson 是阿里巴巴出品的一個 Java 語言編寫的高效能且功能完善的 JSON 函式庫。它採用一種 “假定有序快速匹配” 的演算法,把 JSON Parse 的效能提升到極致,號稱是目前 Java 語言中最快的 JSON 庫。Fastjson 介面簡單易用,已經被廣泛使用在快取序列化、協議互動、Web 輸出、Android 客戶端等多種應用場景。

由於是 Java 語言通用的,因此,以前在 Android 上使用時,Fastjson 不可避免的引入了很多對於 Android 而言冗餘的功能,從而增加了包大小,很多人使用的就是標準版的 fastjson,但事實上,fastjson 還存在一個專門為 Android 定製的版本 ---fastjson.android。和標準版本相比,Android 版本去掉了一些 Android 虛擬機器 dalvik 不支援的功能,使得 jar 更小。

4.4 LoganSquare

LoganSquare 是近兩年崛起的快速解析和序列化 JSON 的 Android 函式庫,其底層基於 jackson 的 streaming API,使用 APT(Android Annotation Tool) 實現編譯時註解,從而提高 JSON 解析和序列化的效能。官網上可以看到 LoganSquare 和 gson、jackson databind 的效能對比。

從效能方面看,LoganSquare 是完勝 gson 和 jackson 的。如果和 fastjson 相比較,兩者應該是不相上下的。

再來看下 jar 包的大小

  • gson:232KB
  • jackson:259+47+1229 = 1.5M
  • Fastjson:417KB
  • Fastjson.android:256KB
  • LoganSquare:48+259 = 307KB

從效能和包大小綜合考慮,最終我們會選擇 Fastjson.android 作為基礎技術堆疊中的 JSON 解析和序列化庫。

5. 資料庫操作能力

無論是 iOS 還是 Android,底層資料庫都是基於開源的 SQLite 實現,然後在系統層封裝成用於應用層的 API。雖然直接使用系統的資料庫 API 效能很高,但是這些 API 介面並不是很方便開發者使用,一不小心就會引入 Bug,而且程式碼的視覺效果也不佳。為了解決這個問題,物件關係對映(ORM)框架出現了,比較好的有 ActiveAndroid,ormlite 和 greenDAO。

5.1 ActiveAndroid

ActiveAndroid 是一種 Active Record 風格的 ORM 框架,Active Record(活動目錄)是 Yii,Rails 等框架中對 ORM 實現的典型命名方式。它極大的簡化資料庫的使用,使用面向物件的方式管理資料庫,告別手寫 SQL 的歷史。每一個數據庫表都可以被對映為一個類,開發者只需使用類似 save() 或者 delete() 這樣的函式即可。

不過 ActiveAndroid 已經基本上處於維護階段了,最新的一個 Release 版本是在 2012 年釋出的。

5.2 ormlite

ormlite 是 Java 平臺的一個 ORM 框架,支援 JDBC 連線、Spring 和 Android 平臺。在 Android 中使用時,它包含兩部分。

  • ormlite-core:核心模組,無論在哪個平臺使用,都必須基於這個核心庫,是實現 ORM 對映的關鍵模組。
  • ormlite-android:基於 ormlite-core 封裝的針對 Android 平臺的介面卡模組,Android 開發中主要跟這個模組打交道。

與 ActiveAndroid 類似,ormlite 也已經不是一個活躍的開源庫,最近一次 Release 版本是在 2013 年釋出的。

5.3 greenDAO

greenDAO 是一個輕量級且快速的 ORM 框架,專門為 Android 高度優化和定製,它能夠支援每秒數千條記錄的 CRUD 操作。官網上給出一張效能對比圖

縱軸表示每秒執行的運算元。而且 greenDAO 處在高度活躍中,最新 Release 版本是在 2017 年 3 月份釋出的

5.4 Realm

Realm 是一個全新的移動資料庫引擎,它既不是基於 iOS 平臺的 Core Data,也不是基於 SQLite,它擁有自己的資料庫儲存引擎,並實現了高效快速的資料庫構建操作,相比 Core Data 和 SQLite,Realm 操作要快很多,跟 ORM 框架相比就更不用說了。

Realm 的好處如下:

  • 跨平臺:Android 和 iOS 已經是事實上的兩大移動網際網路作業系統,絕大多數應用都會支援這兩個平臺。使用 Realm,Android 和 iOS 開發者無需考慮內部資料的架構,呼叫 Realm 提供的 API 即可輕鬆完成資料的交換。
  • 用法簡單:相比 Core Data 和 SQLite 所需的入門知識,Realm 可以極大降低開發者的學習成本,快速實現資料庫儲存功能。
  • 視覺化操作:Realm 為開發者提供了一個輕量級的資料庫視覺化操作工具,開發者可以輕鬆檢視資料庫中的內容,並實現簡單地插入和刪除等操作。

我們看下上述四種資料庫包大小。

  • activeandroid:40KB
  • greendao:100KB
  • ormlite-android:57KB
  • realm-android:4.2M

可以看出,前三個還是正常範圍,但 Realm 的大小一般專案可能無法接受。這是因為不同 CPU 架構平臺的 .so 檔案增加了整個包的大小,由於 arm 平臺的 so 在其他平臺上面能夠以相容模式執行的,雖然會損失效能,但是可以極大地減少函式庫佔用的空間。因此,可以選擇只保留 armeabi-v7a 和 x86 兩個平臺的 .so 檔案,直接刪除無用的 .so 檔案,或者通過工程的 build.gradle 檔案中增加 ndk abi 過濾,語句如下:

android {
    ...
    defaultConfig {
        ...
        ndk {
            abiFilters "armeabi-v7a", "x86"
        } 
    }
}

因此,綜合性能考慮,包大小以及開源庫的可持續發展等因素,我們最終選擇 greenDAO。

6. 網路通訊能力

現在的 APP 幾乎都需要從伺服器獲取資料,不可避免的需要具備網路通訊的能力,否則就是一個死介面。

6.1 android-async-http

Android 最經典的網路非同步通訊函式庫,它對 Apache 的 HttpClient API 的封裝使得開發者可以簡潔優雅地實現網路請求和響應,並且同時支援同步和非同步請求。主要特性如下:

  • 支援非同步 HTTP 請求,並在匿名回撥函式中處理響應
  • 在子執行緒中發起 HTTP 請求
  • 內部採用執行緒池來處理併發請求
  • 通過 RequestParams 類實現 GET/POST 引數構造
  • 無需第三方庫支援即可實現 Multipart 檔案上傳
  • 庫的大小隻有 60KB
  • 支援多種行動網路環境下自動智慧的請求重試機制
  • HTTP 響應中實現自動的 gzip 解碼,實現快速請求響應
  • 內建多種形式的響應解析,有原生的位元組流、String、JSON 物件,甚至可以將 response 寫入到檔案中。
  • 可選的永久 cookie 儲存,內部實現使用的是 Android 的 SharedPreferences。

但是在 6.0 之後,系統對開發者隱藏了 HttpClient 函式庫,這顯著增大了使用 android-async-http 的代價。 如果鐵了心想繼續使用 HttpClient,官方推薦的做法是在編譯期引入org.apache.http.legacy 這個庫,庫目錄在 Android SDK 目錄下的 platformsandroid-23optional 中找到,它的作用是確保在編譯時不會出現找不到 HttpClient 相關 API 的錯誤,在應用執行時可以不依賴這個庫,因為 6.0 以上的 Android 系統還沒有真正移除 HttpClient 的程式碼,只不過 API 設定為對開發者不可見。我們檢視 android-async-http 原始碼發現,需要使用下面這個函式庫來替換之前的 Apache 的 HttpClient。

dependencies {
    compile 'cz.msebera.android:httpclient:4.3.6'
}

這樣顯著的增加了 APP 的包的大小,如果想繼續使用 android-async-http,那麼你的 APP 需要額外增加 1.1MB 左右的大小。

6.2 OkHttp

OkHttp 是一個高效的 HTTP 客戶端,具有如下特性。

  • 支援 HTTP/2 和 SPDY,對同一臺主機的所有請求共享同一個 socket。
  • 當 SPDY 不可用時,使用連線池減少請求的延遲。
  • 透明的 GZIP 壓縮減少下載資料大小
  • 快取響應避免重複的網路請求

OkHttp 在網路效能很差的情況下能夠很好地工作,它能夠避免常見的網路連線問題。如果你的 HTTP 服務有多個 IP 地址,OkHttp 在第一次連線失敗是,會嘗試其他可選的地址。這對於 IPv4+IPv6 以及託管在冗餘資料中心的服務來說是必要的。OkHttp 使用現代的 TLS 特性(SNI,ALPN)初始化 HTTP 連線,當握手失敗時,會降低使用 TSL1.0 初始化連線。

OkHttp 依賴於 okio,okio 作為 java.io 和 java.nio 的補充,是 square 公司開發的一個函式庫。okio 使得開發者可以更好地訪問、儲存和處理資料。一開始是作為 OkHttp 的一個元件存在的,當然我們也可以單獨使用它。

使用 Okhttp 需要引入 Jar 包,包的大小為:326+66 = 392KB

6.3 Volley

Volley 是 Google 在 2003 年釋出的用於 Android 平臺的網路通訊庫,能使網路通訊更快、更簡單、更健壯。官網配出一張弓箭發射圖來說明 Volley 特別使用於資料量小等通訊頻繁的場景。

具體的將,Volley 是為了簡化網路任務而設計的,用於幫助開發者處理請求、載入、快取、多執行緒、同步等任務。Volley 設計了一個靈活的網路棧介面卡,在 Android2.2 及之前的版本中,Volley 底層使用 Apache HttpClient,在 Android2.3 及以上版本中,它使用 HttpURLConnection 來發起網路請求,而且開發者也很容易將網路棧切換成使用 OkHttp。Volley 官方原始碼託管在 Google Source 上面,使用時只能直接以 Jar 包形式引入,如果想在 Gradle 中使用 compile 線上引入,可以考慮使用 mcxiaoke 在 Github 上面的 Volley Mirror,然後再 build.gradle 中使用如下語句即可。

compile 'com.mcxiaoke.volley:library:1.0.19'

6.4 Retrofit

確切的說,Retrofit 並不是一個完整的網路請求函式庫,而是將 REST API 轉換成 Java 介面的一個開源函式庫,它要求伺服器 API 介面遵循 REST 規範。基於註解使得程式碼變得很簡潔,Retrofit 預設情況下使用 GSON 作為 JSON 解析器,使用 OkHttp 實現網路請求,三者通常配合使用,當然我們也可以將這兩者換成其他的函式庫。

通過以上分析,HttpURLConnection、Apache HttpClient 和 OkHttp 封裝了底層的網路請求,而 android-async-http,Volley 和 Retrofit 是基於前面三者的基礎上二次開發而成。

最後看下函式庫的大小

  • android-async-http:106KB+1.1MB = 1.2MB
  • OkHttp:326KB+66KB = 392KB
  • Volley:94KB
  • Retrofit:122KB+211KB = 333KB

7. 圖片快取和顯示能力

圖片快取函式庫有很多非常優秀的,開發人員可以根據需求進行選擇。傳統的圖片快取方案中設定有兩級快取,分別是記憶體快取和磁碟快取。在 Facebook 推出的 Fresco 中,它增加了一級快取,也就是 Native 快取,這極大地降低了使用 Fresco 的 APP 出現 OOM 的概率。

7.1 BitmapFun

BitmapFun 函式庫是 Android 官方教程中的一個圖片載入和快取例項,對於簡單的圖片載入需求來說,使用 BitmapFun 就夠了,在早期用的多,現在漸漸退出了實際專案開發的舞臺。

7.2 Picasso

Picasso 是著名的 square 公司眾多開源專案中的一個,它除了實現圖片的下載和二級快取功能,還解決了常見的一些問題。

  • 在 adapter 中正常的處理 ImageView 回收和下載的取消
  • 使用盡量小的記憶體實現複雜的影象變換

在 Picasso 中,我們使用一行程式碼即可實現圖片下載並渲染到 ImageView 中。

Picasso.with(context).load(url).into(imageView);

7.3 Glide

Glide 是 Google 推薦的用於 Android 平臺上的圖片載入和快取函式庫。這個庫被廣泛應用在 Google 的開源專案中,Glide 和 Picasso 有 90% 的相似度,只是在細節上還是存在不少區別。Glide 為包含圖片的滾動列表做了儘可能流暢的優化。除了靜態圖片,Glide 也支援 GIF 格式圖片的顯示。Glide 提供了靈活的 API 可以讓開發者方便地替換下載圖片所用的網路函式庫,預設情況下,它使用 HttpUrlConnection 作為網路請求模組,開發者也可以根據自己專案的實際需求靈活使用 Google 的 Volley 或者 Square 的 OkHttp 等函式庫進行替換。

Glide 的使用也可以使用一行程式碼來完成,語句如下

Glide.with(context).load(url).into(imageView);

7.4 Fresco

Fresco 是 Facebook 開源的功能強大的圖片載入和快取函式庫,相比其他圖片快取庫,Fresco 最顯著的特點是具有三級快取:兩級記憶體快取和一級磁碟快取。主要特性如下:

  • 漸進式地載入 JPEG 圖片
  • 顯示 GIF 和 WebP 動畫
  • 可擴充套件,可自定義圖片載入和顯示
  • 在 Android 4.X 和一下的系統上,將圖片放在 Android 記憶體一個特殊的區域,從而使得應用執行更流暢,同時極大減低出現 OutOfMemoryError 的錯誤。

7.5 Android-Universal-Image-Loader

Android-Universal-Image-Loader 簡稱 UIL,是 Android 平臺老牌的圖片下載和快取函式庫,功能強大靈活且高度可自定義,它提供一系列配置選項,並能很好地控制圖片載入和快取的過程。使用者甚多,現在專案仍在使用。UIL 也支援二級快取,特性如下:

  • 同步或非同步的多執行緒圖片載入
  • 高度可自定義:執行緒池、下載器、解碼器、記憶體和磁碟快取、圖片顯示選項等。
  • 每張圖片的顯示支援多種自定義選項:預設存根圖片、解碼選項、Bitmap 處理和顯示等。
  • 圖片可快取在記憶體或者磁碟(裝置的檔案系統或者 SD 卡)上。
  • 可實時監聽圖片載入流程,包括下載進度。

最後看下幾個庫的包大小

  • BitmapFun:71KB
  • Picasso:120KB
  • Glide:475KB
  • Fresco:47KB+93KB+93KB+10KB+3MB+62KB+8KB+111KB = 3.4MB
  • Android-Universal-Image-Loader:162KB

圖片函式庫的選擇需要根據 APP 的具體情況而定,對於嚴重依賴圖片快取的 APP, 例如桌布類,圖片社交類 APP 來說,可以選擇最專業的 Fresco。對於一般的 APP,選擇 Fresco 會顯得比較重,畢竟 Fresco 3.4MB 的體量擺在這。

根據 APP 對圖片顯示和快取的需求從低到高我們可以對以上函式庫做一個排序

BitmapFun < Picasso < Android-Universal-Image-Loader 
< Glide < Fresco

值得一提的是,如果你的 APP 計劃使用 React Native 進行部分模組功能的開發的話,那麼在基礎函式庫選擇方面需要考慮和 React Native 的依賴庫的複用,這樣可以減少引入 React Native 所增加的 APP 的大小,可以複用的函式庫有:OkHttp,Fresco,jackson-core.