從零開始的 Android 新專案(5):Repository 層(上)

markzhai發表於2016-05-17

本系列:


如期而至的Repository篇,內部實現則由Realm、Retrofit,以及記憶體級LruCache組成。

Repository,顧名思義,即倉庫,向上層遮蔽了資料來源和內部實現細節,不需要了解貨物來源,只需要拿走就行了。

由於篇幅問題,將分為上下兩篇,本篇主要介紹Retrofit的應用和Repository層組裝,下篇會講解本地快取(包括Realm和記憶體快取)以及基於異常的設計。

Why Repository

首先,為什麼我們需要Repository層呢?一言以蔽之,遮蔽細節。

上層(activity/fragment/presenter)不需要知道資料的細節(或者說 – 資料來源),來自於網路、資料庫,亦或是記憶體等等。如此,一來上層可以不用關心細節,二來底層可以根據需求修改,不會影響上層,兩者的分離用可以幫助協同開發。

舉些例子:

  • 當現在是無網狀態,我希望列表能直接顯示上一次的資料,而不會是空頁面。
  • 除非好友的使用者資料過期(比如超過一天),否則希望直接使用本地快取中的,但如果快取沒有,或者過期,則需要拉取並更新。
  • 點贊後,即便請求還沒傳送或者沒有收到response,仍然希望顯示點贊後的狀態。
    等等。

如果這些需求,我們都要實現在View或者Presenter中,就會導致充斥大量資料邏輯,目的不單一,難以維護。而Repository層就是來封裝這些邏輯的。

Overview

如圖,業務層只能看到repository介面。

Repository Overview

Retrofit

Retrofit是Android界網紅公司Square所開發維護的一個HTTP網路庫,目前最新版本是2.0.2(截止2016年4月30日)。其內部使用了自家的OkHttp

關於Retrofit的實現機制啊簡介的,網上已經很多了,這裡我就不囉嗦了,官方文件見專案主頁。這裡主要講講實際專案中的應用實踐。

import

root build.gradle:

repository module的build.gradle:

OkHttpClient

自底向上地,我們需要一個OkHttpClient來設定給Retrofit,這裡作為例項,放出一段包含大部分你可能會用到的功能的Client建立程式碼,可以根據需要進行調整。

如上包含了大部分你可能需要的特性,可以自由進行組合。

RxJava非同步請求

對應API請求類如

同步請求

有時候我們需要做同步請求,比如提供結果給一些第三方庫,它們可能需要直接返回對應資料(像我最近碰到的融雲….),而我們只需要拉資料同步返回,對其所線上程和呼叫事件均一臉懵逼。

這時候就需要建立一個同步的retrofit客戶端,其實就是不要去使用RxJava的adapter啦。

對應地,我們需要定義請求類,這裡我們需要使用Call<>去包一下最終解析物件的類。

資料格式解析

資料的解析當然是必不可少的一環了,常用格式對應的序列化庫以retrofit官網為例:

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

部分高大上公司可能自己使用內部的二進位制格式,自己實現ConverterFactory去解析就行了。

這裡以最常用的json為例,使用GsonConverterFactory,良好的資料結構通常都會帶有狀態碼和對應資訊:

根據statusCode可以快速判斷是否出現錯誤,通常0或者某個正數為正確,負數則根據和伺服器的協定做不同處理。
這裡對Gson的bean,推薦使用外掛GsonFormat,生成起來很方便。

至於具體的資料,則有兩種方案,一是使用data作為key把具體資料套起來,內部則使用K/V進行儲存,保證不存在不規範的直接丟一個array在data裡面的情形。

二次的組合解析

二次組合的解析通過將建立一個通用的Response Bean來做泛解析,如果statusCode表明介面請求成功,則繼續解析data:

呼叫則如:

所有介面都可以通過RepositoryUtils.extractData()進行泛型呼叫。

如此一來,如果response為空,我們僅在statusCode正確時才會去解析具體的資料,否則丟擲對應的異常(基於異常的資料層設計在下面會具體講)。

單次的繼承處理

上一種處理方式儘管看起來很優雅,但是存在一個問題,就是會重複解析,當statusCode正確時,會對data的object再次進行json處理。如果確實是error,比如statusCode為-1、-2這種,確實節省了開銷,因為gson會去反射構造對應類的adapter,解析所有欄位,建立對應的BoundField。

但考慮到大部分情況下還是正確的response居多,所以也可以使用繼承的結構,我們建立BaseResponse存放通用欄位,其他所有Gson Bean則繼承該BaseResponse

對應的判斷和error丟擲可以參照上小節的,這裡就不贅述了。

Repository層組裝實現

組裝即根據組合各個資料來源,如此又分為直接在實現方法中組合結果,亦或是通過DataStoreFactory進行封裝。根據複雜度和個人喜好而定,畢竟使用後者需要新增好多類,相對來說有一點重。

基於介面的設計實現

拿一個最簡單的repository,七牛Repository來作例子:

DataStoreFactory

使用DataStoreFactory封裝資料來源:

老實說這樣的話,一來要寫很多方法和介面,二來通過Factory判斷建立哪種DataStore還是挺麻煩的,比如使用者主頁資料我們可以判斷,但登陸登出這些,就需要直接指定createCloudDataStore()了,所以個人認為意義不大。

在實現方法中組合

如下是使用DBFlow和網路Api進行組合的一個list獲取介面。

我們使用RxJava的concat組合2個Observable,前者從cache(資料庫)獲取資料,後者從網路Api獲取資料,通常資料庫當然會更快。我們還保留了一個引數isForceRefresh來保證在某些情況下可以強制從網路獲取資料。

總結

本篇為Repository層的上篇,主要介紹了組合及Retrofit的應用。下篇將會講述資料庫,記憶體Cache,以及統一的異常處理設計。

另外,打個小廣告,本司的新產品Crew已經在各大Android應用市場上線,專注於職場垂直社交。一搜和興趣相投的人聊天。iOS版本正在稽核中。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

從零開始的 Android 新專案(5):Repository 層(上) 從零開始的 Android 新專案(5):Repository 層(上)

相關文章