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

四級五次郎發表於2017-09-01

在技術面試的時候肯定都會問到使用了哪些第三方框架,為什麼使用它而不用其他的。身邊朋友就有這樣的親身經歷:

面試官:你們專案中載入圖片都是用的什麼框架?
面試者: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目錄下的platforms\android-23\optional中找到,它的作用是確保在編譯時不會出現找不到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在2013年釋出的用於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.

Thanks

《Android高階進階》顧浩鑫

相關文章