文章標題

本文轉載自:http://www.jianshu.com/p/42d45998117f

這篇博文主要就是針對平常使用到的框架做一個整理和分析其優劣。 爲了從整體上進行把握,先來看看一個完整的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在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.

Thanks

《Android高級進階》顧浩鑫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章