打造Android輕量級框架XSnow的後繼之路

幻影宇寰發表於2017-07-20

前言

由於有使用的朋友提到 XSnow 框架資訊量有點大,希望能有篇文章詳細介紹框架中每個模組的細節,所以本文會圍繞該框架將每一個模組的相關構思和重要技術點做一個詳細的剖析,讓使用該框架的朋友對 XSnow 有一個清晰的認識。

準備

由於 XSnow 框架是基於 RxJava2Retrofit2 打造的,其中也依賴瞭如網路基礎庫 OkHttp 、圖片載入庫 Glide 、資料庫基礎庫 GreenDao ,所以使用該專案的朋友需要對這幾個基礎框架有基本的認識,下面將對這幾個框架做一個簡單的說明。

RxJava

熟悉的朋友可能知道該框架是屬於 ReactiveX 程式設計庫的一員,這是一種新的程式設計思想,一般都把它叫做響應式程式設計思想,對於使用該思想的優勢在這裡就不贅述了,感興趣的朋友可以看看這篇文章 ReactiveX 文件中文翻譯

RxJava 主要的模式就是觀察者模式,何為觀察者模式,可以簡單的理解為:兩個物件觀察者與被觀察者,其中觀察者與被觀察者建立了訂閱關係,如果被觀察者發生了變化那麼需要及時通知觀察者,觀察者收到通知後做出相應的處理。描述可能會有點繞,舉個通俗易懂的例子吧,比如說:顧客去買蛋糕這樣一個場景(把顧客和蛋糕店店員分別看做觀察者與被觀察者),由於蛋糕是現做需要一定時間才能完成,顧客在購買時一般會先付錢給店員拿到一個付款憑證後離開去忙其他事,這樣他們就建立了購買關係(建立訂閱關係),店員在做完蛋糕後(被觀察者發生變化),店員就會通知顧客來拿(訊息通知到觀察者),顧客在收到店員通知後前來拿蛋糕(觀察者收到通知後做出相應的處理)。到這裡,一個完整的觀察者模式場景就講解完畢,希望能幫助朋友們加深對觀察者模式的理解。

下面繼續講解對 RxJava 的理解,先想象一個這樣的場景,有一臺萬能機器,它可以生產任何東西,它有一個輸入端、一個輸出端,中間有很多核心元件,比如說轉換器、轉移器等,它們對外部都是隱藏的,但是外部有一個控制終端,可以輸入任何想要的規則,機器就會根據規則,將輸入端的東西通過終端制定的規則輸送到輸出端。RxJava 就是這樣一臺萬能機器,它擁有資料輸入端、資料輸出端,中間也有很多可以操控資料的規則,比如說各種操作符、執行緒轉換,它們都有一個目的,就是如何讓資料來源通過一定規則進行輸出。

以上的講解只是為了讓你對 RxJava 有一個清晰的認識,不牽扯具體的技術細節,如果想詳細瞭解的可以去看看給初學者的 RxJava2.0 教程,這是一個系列,看完這些文章,基本就可以使用 RxJava 做一些基礎的功能了,這個系列是針對 RxJava2 的,如果想了解 RxJava 第一版,想看看兩個版本到底發生了什麼改變,那麼可以看看給 Android 開發者的 RxJava 詳解以及 RxJava2 vs RxJava1.

Retrofit

Retrofit 簡單來說就是一個基於 OkHttpRESTful API 請求工具。RESTful 是一種架構風格,它的特點是資源、統一介面、URI 和無狀態,如果想更詳細的瞭解 RESTful 可以看看RESTful 架構風格概述

Retrofit 在使用時其實就充當了一個介面卡(Adapter)的角色,主要是將一個 Java 介面翻譯成一個 HTTP 請求物件,然後用 OkHttp 去傳送這個請求。其中核心思想就是動態代理機制,什麼是動態代理,就是當你要呼叫某個 Class 的方法前或後,插入你想要執行的程式碼。通俗來講,就是你要執行某個操作的前後需要增加一些操作,比如檢視使用者個人資訊前需要判斷使用者是否登入,使用者訪問資料庫後想清除使用者的訪問記錄等操作。

Retrofit 的設計非常外掛化且輕量級,高內聚且低耦合。
Retrofit 主要定義了 4 個介面:

  • Callback<T>:請求資料的返回;
  • Converter<F, T>:對返回資料進行解析,一般用 GSON
  • Call<T>:傳送請求,Retrofit 預設的實現是 OkHttpCall<T>,也可以依需自定義 Call<T>
  • CallAdapter<T>:將 Call 物件轉換成其他物件,如轉換成支援 RxJavaObservable 物件。

Retrofit 進行網路請求的過程:

  • 通過 Retrofit 物件和 Method 物件獲取 callAdapterresponseType 以及 responseConverter 三個物件;
  • 通過解析 Method 中的註解以及進行一系列的檢查得到中心管理物件 ServiceMethod
  • 通過 ServiceMethod 物件獲取實際執行的 Call 物件執行 Http 請求。

以上講解只是對 Retrofit 的核心功能做了相關的解釋,如果想更詳細的瞭解 Retrofit 可以看看拆輪子系列:拆 Retrofit

OkHttp

OkHttp 是一個高效的 HTTP 庫,它的總體設計圖如下(圖片來源:泡在網上的日子):

OkHttp總體設計圖
OkHttp總體設計圖

OkHttp 的請求由 OkHttpClient 統一管理,採用的是門面模式,OkHttpClient 擁有子模組的所有配置和引數,並將請求分發到相應的子系統。它由以下幾個核心子系統組成:路由、連線協議、攔截器、代理、安全性認證、連線池以及網路適配。主要是通過 Dispatcher 不斷從 RequestQueue 中取出請求(Call),根據是否已快取呼叫 CacheNetwork 這兩類資料來獲取某個介面,再從記憶體快取或是伺服器取得請求的資料。該引擎有同步和非同步請求,同步請求通過Call.execute()直接返回當前的 Response,而非同步請求會把當前的請求(AsyncCallCall.enqueue 新增到請求佇列中,並通過回撥(Callback)的方式來獲取最後結果。

OkHttp 中的 Interceptor(攔截器)方式對於整體的設計提供了很大的幫助,它採用的是責任鏈模式,它不只是負責攔截請求進行一些額外的處理(例如增加請求頭),實際上它把實際的網路請求、快取、透明壓縮等功能都統一了起來,每一個功能都只是一個 Interceptor,它們再連線成一個 Interceptor.Chain,環環相扣,最終圓滿完成一次網路請求。

以上講解只是對 OkHttp 的流程做了相關的解釋,如果想更詳細的瞭解 OkHttp 可以看看拆輪子系列:拆 OkHttp

Glide

Glide 是為圖片載入而生,一行程式碼Glide.with(this).load(url).into(imageView);就搞定圖片的載入。使用非常簡單,如果想詳細瞭解的,這裡推薦郭霖的 Glide 原始碼解析,Android 圖片載入框架最全解析,這是一個系列,看完後對於 Glide 基本就能知道怎麼用和為啥要這樣用了。

GreenDao

GreenDao 是一個將物件對映到 SQLite 資料庫中的輕量且快速的 ORM 解決方案。如果想更詳細的瞭解 GreenDao 可以看看Android 資料儲存之 GreenDao 3.0 詳解

注:

  • 以上對於基礎庫的講解只是做了簡單的介紹,讓你對 XSnow 框架利用的相關技術有個直觀的感受,如果想詳細瞭解的,上面在每個基礎庫後面都備註了個人認為比較好的文章,可以當做學習該框架的參考。
  • 以下介紹都以包名作為每個模組的標題,這樣也表明模組之間充分解耦,也方便讀者對照程式碼進行理解,分析起來邏輯更清晰。

Http(網路模組,包含網路請求,上傳下載)

該模組是 XSnow 框架的核心功能,其核心思想就是將請求分離和基於動態配置,採用門面模式,上層不用關係具體實現細節,只需要簡單配置相關的請求資訊就可以達到完整的網路請求功能。該模組相對於其他模組程式碼量比較大,下面將會對該模組下的每一個包進行詳細拆分講解,現在我們首先來看看 ViseHttp 類,該類相當於該模組的門面類,也是網路請求的唯一入口類,所有的請求都是由該類構建,如:

  • BASE:傳入自定義請求物件,方便外部根據自己的需求自定義請求;
  • GET:獲取 GET 方式請求物件;
  • POST:獲取 POST 方式請求物件;
  • HEAD:獲取 HEAD 方式請求物件;
  • PUT:獲取 PUT 方式請求物件;
  • PATCH:獲取 PATCH 方式請求物件;
  • OPTIONS:獲取 OPTIONS 方式請求物件;
  • DELETE:獲取 DELETE 方式請求物件;
  • UPLOAD:獲取上傳檔案請求物件,支援傳入上傳回撥;
  • DOWNLOAD:獲取下載檔案請求物件。

該類還提供了根據 tag 中斷單個網路請求以及中斷所有網路請求功能,也提供了根據 key 刪除快取和清除所有網路快取功能。必須注意的是,在應用初始化時必須呼叫該類的初始化方法

ViseHttp.init(this);複製程式碼

以及相關的網路配置

ViseHttp.CONFIG()
        //配置請求主機地址
        .baseUrl("http://10.8.4.39/")
        //配置全域性請求頭
        .globalHeaders(new HashMap<String, String>())
        //配置全域性請求引數
        .globalParams(new HashMap<String, String>())
        //配置讀取超時時間,單位秒
        .readTimeout(30)
        //配置寫入超時時間,單位秒
        .writeTimeout(30)
        //配置連線超時時間,單位秒
        .connectTimeout(30)
        //配置請求失敗重試次數
        .retryCount(3)
        //配置請求失敗重試間隔時間,單位毫秒
        .retryDelayMillis(1000)
        ......;複製程式碼

這樣才能在應用中呼叫相關的網路請求功能。如果沒有初始化,在呼叫網路請求時該模組會丟擲如下異常資訊:

Please call ViseHttp.init(this) in Application to initialize!複製程式碼

切記!

下面來分別講解該模組下每個包的功能:

  • api
    該包提供的是請求的 API,目前只有一個類 ApiService,主要提供的是 Retrofit 進行網路請求的請求方法。

  • body
    該包提供的是相關的請求和響應 body,目前只有一個上傳進度展示的請求實體類 UploadProgressRequestBody,通過傳入 UCallback 來處理上傳檔案的進度回撥。

  • callback
    該包提供的是相關的回撥類,目前包含上傳回撥 UCallback 和請求 API 回撥 ACallback

  • config
    該包提供的是配置相關類,目前只有請求全域性配置類 HttpGlobalConfig,該類提供了很豐富的配置方法,提供該類的目的是想將配置與請求分離。

  • core
    該包提供的是一些核心功能類,目前包含快取處理類 ApiCacheCookie 管理類 ApiCookie 以及網路請求訂閱管理類 ApiManager。快取採用磁碟快取方式,支援定製各種快取策略,策略將在 strategy 包下進行講解。Cookie 採用 SharedPreferences 儲存,儲存物件以十六進位制進行儲存。

  • exception
    該包提供網路請求相關異常類,目前提供了請求異常統一處理類 ApiException,可用來判定請求失敗的原因。

  • func
    該包提供一些資料轉換類,目前提供了由 ResponseBody 物件轉 TApiFunc<T> 類以及由 Observable<? extends Throwable>Observable<?>ApiRetryFunc 類。ApiRetryFunc 類主要用來處理網路超時重試機制,可傳入重試次數和重試間隔時間,這些都可以在配置類中進行配置。

  • interceptor
    該包提供的是一系列的攔截器類,這個包也算是該模組的核心包,基本大部分功能都可以採用攔截器的方式來提供,目前該包下面包含如下攔截器:
    1、GzipRequestInterceptor:包含Gzip壓縮的請求攔截;
    2、HeadersInterceptor:請求頭攔截;
    3、HttpLogInterceptor:Http日誌列印攔截;
    4、NoCacheInterceptor:無快取攔截;
    5、OfflineCacheInterceptor:離線快取攔截;
    6、OnlineCacheInterceptor:線上快取攔截;
    7、UploadProgressInterceptor:上傳檔案進度展示攔截。

  • mode
    該包提供的是相關的實體類。

  • request
    該包提供的是所有請求相關的類,所有的網路請求都需要建立一個請求物件,該包提供了一個基礎請求類 BaseRequest<R extends BaseRequest>,該類需將 R 寫成實際請求類,這樣才能獲取對應請求類的物件來進行相關的請求資訊配置,如果請求配置與全域性配置衝突,那麼優先請求配置,意思就是區域性請求配置會替換掉相同的全域性配置。
    請求類中提供了一系列請求頭配置、請求引數配置等資訊,如果是 POST 請求還提供了上傳 JSON 字串、上傳表單等方式,而如果是上傳檔案則提供了新增檔案、新增位元組陣列和新增流的方式。
    由於帶快取請求和不帶快取請求返回的結果不一樣,所以需要分開處理,故有 cacheExecuteexecute 的區分。

  • strategy
    該包提供的是快取相關的策略,包含如下幾種策略:
    1、CacheAndRemoteStrategy:先載入快取資料後載入網路資料;
    2、FirstCacheStrategy:優先載入快取資料;
    3、FirstRemoteStrategy:優先載入網路資料;
    4、OnlyCacheStrategy:只載入快取資料;
    5、OnlyRemoteStrategy:只載入網路資料。
    快取策略採用面向介面程式設計原則,定義了一個快取策略介面 ICacheStrategy<T>,並將具體的策略實現交由各個子類。

  • subscriber
    該包提供的是相關的訂閱者,目前包含 API 請求的統一訂閱者 ApiSubscriber<T>、包含回撥的訂閱者 ApiCallbackSubscriber<T> 以及包含下載回撥的訂閱者 DownCallbackSubscriber<T>

Cache(快取)

該模組提供了幾種快取方式,分別是記憶體快取、磁碟快取以及 SharedPreferences 儲存。該模組主要思想是面向介面程式設計,提供了 ICache 介面,主要包含新增快取、獲取快取、刪除快取、清除所有快取以及判斷是否包含該快取這幾個能力。下面將對每個快取方式做詳細的解釋說明:

  • 記憶體快取:記憶體快取採用單例模式管理快取物件,快取物件為 LruCache,快取演算法採用 Lru 演算法(Least Recently Used 近期最少使用演算法),快取大小為最大記憶體的八分之一。
  • 磁碟快取:磁碟快取 KEY 採用 MD5 加密,可定製快取時長,沒有定製則預設永久儲存,快取物件為 DiskLruCache,快取演算法也是 Lru 演算法,快取位置和快取大小都可以定製,如果沒有定製就會使用預設值,預設的快取位置為該應用快取目錄下的 disk_cache 目錄,優先儲存到 SD 卡中,預設的快取大小為 20M
  • SharedPreferences 儲存:SharedPreferences 儲存對快取物件進行 Base64 加密儲存,可定製快取檔名,如果沒有定製則使用預設檔名 sp_cache

Event(事件匯流排)

該模組使用 Rx 思想實現了 RxBus 功能,其 Bus 的設計思想與 EventBus 類似。其主要由以下幾個核心類組成:

  • EventBase :事件處理基類,包含粘性事件 Map 以及普通事件 Subject,也定義了獲取 Flowable 以及刪除粘性事件和刪除粘性事件 Map 的方法。
  • EventComposite :事件複合類,就是將需要接收事件的類中所有事件組合到一起進行處理,並提供了一個傳送粘性事件的方法。
  • EventSubscriber :事件訂閱者,提供了訂閱事件和分發事件的方法。
  • EventFind :根據註解查詢事件接收方法,由此得到 EventSubscriber,最後組合到一起得到 EventComposite
  • ThreadMode :執行緒模型,包含如主執行緒、IO執行緒等,通過 getScheduler(ThreadMode thread) 來獲取執行緒排程者。

事件接收採用註解方式類進行管理,事件訂閱後依據註解來查詢對應的事件接收地。

該模組為了能將事件匯流排統一,定義了 IBus 介面,提供瞭如下四個方法:

void register(Object object);

void unregister(Object object);

void post(IEvent event);

void postSticky(IEvent event);複製程式碼

也提供了 IEvent 介面,所有事件都實現該介面,這樣就可以將具體的事件實現類抽離,其實也是面向介面程式設計。

該模組也提供了外掛化思想,上層可以將如 EventBus 的實現類注入該模組,那麼事件的處理就會採用 EventBus 實現的策略,但這裡有一個問題,由於事件接收採用的是註解方式,而 EventBus 中的註解是不同的,所以還是需要把註解事件進行統一替換,耦合性太高,目前沒有發現更好的方式,如果哪位朋友有好的去耦合方式,歡迎留言交流!

Loader(圖片載入)

該模組針對圖片載入做了二次封裝,面向介面程式設計,每個實現就是一種圖片載入策略,預設採用 Glide 圖片載入框架,上層也可以依需自定義實現介面 ILoader,比如 Demo 中提供的 Fresco 圖片框架的實現類 FrescoLoader,其主要思想就是外掛化,外部可注入任何載入策略,這樣可達到高內聚低耦合。

介面中提供瞭如下四種載入圖片的方式:

  • 載入網路圖片
    void loadNet(ImageView target, String url, Options options);複製程式碼
  • 載入資源圖片
    void loadResource(ImageView target, int resId, Options options);複製程式碼
  • 載入Assets中的圖片
    void loadAssets(ImageView target, String assetName, Options options);複製程式碼
  • 載入本地圖片
    void loadFile(ImageView target, File file, Options options);複製程式碼

預設的 Glide 框架採用 provided 方式依賴,這樣就只是編譯時依賴,執行時不依賴,上層如果確定使用 GlideLoader載入策略,那麼還需要自己使用 compile 進行依賴,這樣執行時才不會報錯,GlideLoader 在初始化時也增加了如下驗證機制

try {
    Class.forName("com.bumptech.glide.Glide");
} catch (ClassNotFoundException e) {
    throw new IllegalStateException("Must be dependencies Glide!");
}複製程式碼

如果沒有依賴 Glide 庫則會丟擲異常。

Database(資料庫)

該模組將 GreenDao 作為底層資料庫,定義了資料庫的操作介面 IDatabase<M, K>,統一由 DBManager<M, K> 抽象類管理,由於每個實體類對應的 Dao 不一樣,所以定義了抽象方法 getAbstractDao()。由於 GreenDao 的特殊性,該方法的實現類不能在框架中搭建,所有資料庫操作都可以參考 DemoDbHeDlper 類實現自己的資料庫操作管理類,不同的 Dao 實現對應的 getAbstractDao() 方法就行。

Permission(許可權管理)

該模組利用 Rx 思想統一管理許可權的申請,一行程式碼搞定許可權的申請問題。

PermissionManager.instance().with(this).request(onPermissionCallback, Manifest.permission.CALL_PHONE);複製程式碼

該模組很簡潔,就幾個類,下面分別對它們進行介紹:

  • OnPermissionCallback:許可權申請回撥介面,在介面實現類中呼叫具體的業務邏輯。
  • Permission:許可權實體類,包含許可權名稱、是否授予許可權以及是否顯示許可權申請理由變數。
  • PermissionManager:許可權管理類,也是許可權申請的唯一入口,採用單例模式,需要通過 with 將當前的 Activity 物件傳進去,呼叫 request 方法就可以進行許可權申請,需要傳入回撥和許可權列表,許可權列表採用可變陣列方式傳入,使用示例如下:

    PermissionManager.instance().with(this).request(new OnPermissionCallback() {
      @Override
      public void onRequestAllow(String permissionName) {
          DialogUtil.showTips(mContext, getString(R.string.permission_control),
                  getString(R.string.permission_allow) + "\n" + permissionName);
      }
    
      @Override
      public void onRequestRefuse(String permissionName) {
          DialogUtil.showTips(mContext, getString(R.string.permission_control),
                  getString(R.string.permission_refuse) + "\n" + permissionName);
      }
    
      @Override
      public void onRequestNoAsk(String permissionName) {
          DialogUtil.showTips(mContext, getString(R.string.permission_control),
                  getString(R.string.permission_noAsk) + "\n" + permissionName);
      }
    }, Manifest.permission.CALL_PHONE);複製程式碼
  • RxPermissions:許可權管理核心類,該類通過傳入 Activity 獲取一個 Fragment 來進行許可權申請回撥處理,提供了一系列許可權申請的方法,有多個許可權單獨處理回撥和統一處理回撥的方式,分別是 requestEachrequest 方式。
  • RxPermissionsFragment:許可權申請回撥處理 Fragment

UI(UI模組,包含萬能介面卡、檢視切換)

該模組包含萬能介面卡和試圖切換功能。介面卡部分採用 ViewHolder 來管理資料的裝載和展示,將資料與展示分離,提供了 DataHelper 介面來裝載資料,ViewHelper 介面來處理 UI 的展示。其中的 HelperAdapter 提供了介面卡的常用方法,基本能滿足介面卡的常用需求。
檢視切換部分由 StatusLayoutManager 統一管理,通過傳入相關配置進行檢視展示處理。內部提供了 OnRetryListener 重試監聽和 OnStatusViewListener 試圖切換監聽,並定義了一個自定義檢視 StatusLayout 用來展示以下五種檢視:

  • CONTENT:內容檢視;
  • LOADING:載入檢視;
  • EMPTY:空檢視;
  • NETWORK_ERROR:網路錯誤檢視;
  • OTHER_ERROR:其他錯誤檢視。

最後

到此,XSnow 框架的所有模組就介紹完畢了,不知各位朋友是否對該框架有了更深入的瞭解。

以上描述有些部分可能講解比較簡單,這是因為有些模組本身不是很複雜,所以就沒有做過多的講解,如果想更清晰的瞭解該框架,最好的辦法是直接 down 下原始碼觀察,如果在看原始碼過程中有哪裡不理解或覺得實現有問題而你有更好的方案都可以留言交流!

原始碼地址:github.com/xiaoyaoyou1…,原始碼地址中提供了詳細的使用文件,裡面有版本介紹以及QQ群等資訊,如果喜歡該框架不妨點點 star 並分享給身邊的朋友,讓更多的朋友參與進來,謝謝大家!

相關文章