基於Retrofit+RxJava的Android分層網絡請求框架

目前已經有很多Android客戶端在使用Retrofit+RxJava實現網絡請求了,相比於xUtils,Volley等網絡訪問框架,其具備網絡訪問效率高(基於OkHttp)、內存佔用少、代碼量小以及數據傳輸安全性高等特色。html

Retrofit源碼更是經典的設計模式教程,筆者已在以前的文章中分享過本身的一些體會,有興趣的話可點擊如下連接瞭解:Retrofit源碼設計模式解析(上)》、Retrofit源碼設計模式解析(下)android

但在具體業務場景下,好比涉及到多種網絡請求(GET/PUT/POST/DELETE等),多種請求方式(異步/同步)時,按照Retrofit官方文檔實現網絡請求仍然會顯得比較繁瑣,本文主要介紹筆者基於Retrofit+RxJava封裝的Android分層網絡請求框架,適用於下圖所示的業務場景:Android移動端經過移動網關調用接口平臺發佈的業務服務git

繪圖2

上述業務架構多是目前移動應用中使用的比較廣的,其具備如下優勢:github

  • 因爲移動網關係統和統一服務發佈平臺的存在,移動端不須要直接調用業務系統的服務,避免了移動端同時對接多個業務系統,下降移動端系統的複雜性;
  • 移動網關會對移動端的請求進行鑑權,屏蔽外部惡意訪問,有效提升內部業務系統的安全性;
  • 統一服務發佈平臺集成全部的業務接口,對外提供格式統一的接口服務,這對於內部系統的可維護性和可擴展性是相當重要的。
  • 業務系統只須要按照格式將其服務在接口平臺上發佈便可,無需關心具體的調用者。

所以,本文分享的分層網絡請求框架的前提是:Android移動端直接對接移動網關。主要有如下內容:json

  1. 網關請求封裝。移動網關的請求格式(參數、字段、通訊方式等)應該是固定的,而且對業務是透明的,不觸碰具體業務數據。負責直接對接客戶端的請求,包括請求的鑑權,客戶端與後臺的數據格式的轉換等。
  2. 基礎業務請求。基礎業務請求涉及到正式/測試環境的切換,網關返回業務數據的統一解析,以及添加業務相關的網關默認字段等;
  3. 業務Module統一網絡請求管理。業務Module負責統一管理一個業務模塊中全部的網絡請求,接收鑑別請求對應的字段,包含服務名、服務分組名、請求方法以及請求參數等;
  4. Model層網絡請求。Model層的網絡請求是按服務劃分的,一個應用Module一般會對應多個服務,而且接收Activity的參數,組裝請求bean;
  5. Activity層的網絡訪問。Activity直接調用Model層的方法,傳入界面相關的參數,回調響應結果。
  6. 文件上下傳及其它網絡訪問。經過Retrofit+RxJava還能夠實現文件上下傳以及軟件更新等其它網絡訪問,本文也會一併簡要介紹。

1、網關請求封裝

經過Retrofit註解定義移動網關接口,好比請求方式,參數格式,字段等。以POST請求爲例,參數格式爲表單數據,字段包含服務名、服務分組名、方法名、參數、請求頭Map以及其餘參數等。設計模式

@FormUrlEncoded
@POST("./")
Observable<WGResponseBean> postRequest (
        @FieldMap("param") String param,
        @HeaderMap Map<String, String> headMap);

Retrofit的FieldMap不支持字段值爲null,如參數中有null值,須要使用Field。安全

如上所述,@POST表示該請求是一個POST方法,經常使用的POST提交數據的方式有:網絡

  • application/x-www-form-urlencoded
  • multipart/form-data
  • application/json
  • text/xml

application/x-www-form-urlencoded對應表單數據,在Retrofit中,經過@FormUrlEncoded標註的參數將以表單形式進行提交。multipart/form-data通常用於文件上傳的時候,這個在後面會提到。application/json經過JSON方式與服務端進行數據交換,text/xml使用XML數據格式。多線程

定義了網關請求以後,須要建立對應的Service,而Service的使用方式並不肯定,這裏經過一個抽象類對其進行封裝。架構

public abstract class WgReqService<T> {

    // 網關網絡請求
    protected WGApi wgApi;

    // 省略代碼
    public WgReqService(String baseUrl) {
        wgApi = new NetWork.Builder(baseUrl).build().getApi(WGApi.class);
    }

    public abstract T wgReq(WGRequestBean wgRequest, Map<String, String> headMap);
}

以同步/異步網絡請求爲例,分別繼承自WgReqService,實現對應的wgReq方法便可。

public class WgReqAsync<T> extends WgReqService<Observable<T>> {
    // 省略代碼 
    @Override
    public Observable<T> wgReq(WGRequestBean wgRequest, Map<String, String> headMap) {
        // 省略代碼
    }
}
public class WgReqSync extends WgReqService<WGResponseBean> {
    // 省略代碼
    @Override
    public WGResponseBean wgReq(WGRequestBean wgRequest, Map<String, String> headMap) {
        // 省略代碼
    }
}

因爲採用了RxJava,所以在異步實現中,泛型參數爲Observable<T>,而同步請求時直接返回網關的出參Bean。另外,須要說明的是WgReqAsync包含域Func1<WGResponseBean, T>,Func1爲RxJava支持的接口,這裏表示將網關返回的業務數據進行統一解析的方法。

2、基礎業務請求

經過上述的分析可知,業務請求能夠有同步/異步等多種實現方式,同時涉及到正式/測試環境的切換,網關返回業務數據的統一解析,以及添加業務相關的網關默認字段等,這裏以異步請求爲例:

public class BaseWgRequest implements Func1<WGResponseBean, BusinessBean> {

    // 網關請求Helper類
    private WgReqAsync<BusinessBean> wgReqAsync;

    // 服務名
    protected String service;
    // 服務組名
    protected String alias;
    // 解析類
    protected Class<? extends BusinessBean> rClazz;

    // 省略代碼
}

BaseWgRequest持有WgReqAsync<BusinessBean>引用,並經過其完成網關訪問,service、alias等域指定相應的服務,Class<? extends BusinessBean>表示對業務返回值進行解析的類。

return JSON.parseObject(wgResponse.getData(), rClazz != null ? rClazz : BusinessBean.class);

異步請求中,經過上述域及業務相關的網關默認字段封裝請求體,同時獲取請求head。

// 請求
return wgReqAsync.wgReq(ParamUtil.getWGRequestBean(service, alias, method, param),
            BaseConstants.getHeaderMap());

3、業務Module

首先申明,對整個項目進行多工程劃分(業務工程和庫工程獨立,便於庫工程獨立維護),同時業務工程中分爲多個功能Module(便於功能模塊插件化、熱加載),這種方式在比較大型的項目中應用效果可能比較好,在小型項目中並不推薦。這裏的業務Module是以功能模塊進行劃分的,對一個功能模塊中的全部網絡請求進行統一管理,能有效的單元測試,提升總體開發效率。

如上所述,業務Module的主要職責是接收鑑別請求對應的字段,包含服務名、服務分組名、請求方法以及請求參數等,並繼承自上述 BaseWgRequest實現。

public class WelNetwork extends BaseWgRequest {}

業務Module包含了一個功能模塊中的全部網絡請求方法,以登陸爲例:

public Observable<BusinessBean> userLoginWork(SysUsersReqDto sysUsersReqDto) {
    return wgRequest(service, alias, BusinessConstants.userLoginWork, ParamUtil.getJsonParam(sysUsersReqDto));
}

這裏重點說明下登陸方法的入參,BaseWgRequest關注的是與網關接口相關的參數,因爲業務Module繼承自BaseWgRequest,這一層的方法再也不關注網關相關內容,重點是業務相關的請求入參。換句話說,業務Module的入參直接對應業務接口的入參,具備訪問形式的無關性。考慮清楚每個層次的關注重點,是搭建軟件架構的基礎。

4、Model層網絡請求

在本系統中,按照服務名對Model進行了劃分,須要申明的是,因爲每一個公司的具體狀況不同,這種劃分方式不必定適用於你的系統。不過這種分層方式仍有借鑑之處。

因爲Model中方法的訪問可能不止一處,所以對外(Activity)提供單實例對象。這裏提供一種最簡單餓漢模式的單實例:

private static LoginModel loginModel = new LoginModel();

public static LoginModel getInstance() {
    return loginModel;
}

同時,在其構造器中初始化業務Module訪問類。

private LoginModel() {
        super();
        welNetwork = new WelNetwork.Builder().service(BusinessConstants.SysLogin).alias(BaseConstants.getALIAS())
                .rClazz(SysUsersResDto.class).build();
}

上面提到,業務Module關注的是業務接口的入參,那麼這個入參就是有Model提供的。一個功能模塊可能對應多個服務,那麼這些服務須要持有業務Module的引用,並經過業務Module的方法實現自身的方法。仍是以登陸爲例:

public Observable<BusinessBean> userLoginWork(String username, String password) {
        return welNetwork.userLoginWork(new SysUsersReqDto.Builder(username).userPwd(password)
                .devType("1").devIp(DeviceUtils.getClientIpAddress()).build());
}

Model負責鏈接Activity和業務Module,對上直接對接Activity,Activity關注的是用戶輸入的用戶名和密碼,並不知道業務接口須要的數據格式,而業務Module關注的是業務接口的入參格式。所以,Model層對這兩種數據進行適配,常見的就是對請求bean的組裝,好比上述登陸方法接收用戶名和密碼,組裝成業務Module所需的SysUsersReqDto。

5、Activity層的網絡訪問

經過上述分層封裝,在Activity中的網絡訪問就很是簡單了。直接上示例代碼:

LoginModel.getInstance().userLoginWork(usernameStr, passwordStr)
    .subscribe(new RxObserver<BusinessBean>(this) {
    @Override
    public void onSuccess(BusinessBean businessBean) {
          handleLoginResult(businessBean);
    }
});

須要說明的是RxObserver,RxObserver<T>繼承自Subscriber<T>,Subscriber是RxJava的回調類,RxObserver包含抽象方法onSuccess,並在onNext實現中進行調用。

public abstract class RxObserver<T> extends Subscriber<T> {
    // 省略代碼
    @Override
    public void onNext(T t) {
        onSuccess(t);
    }

    public abstract void onSuccess(T t);
}

從Activity的角度來說,其負責用戶交互,所以只關注用戶輸入和接口返回具體數據,並對數據進行處理。而至於網關的實現,業務接口的入參格式,網絡請求的方式等底層實現,則對Activity徹底閉合。

上述簡要介紹了題目所講到的基於Retrofit+RxJava的Android分層網絡請求框架,因爲涉及具體業務,只能開放部分代碼樣例。至於對架構的觀點,可參考《什麼是架構?》

  1. 根據要解決的問題,對目標系統的邊界進行界定。
  2. 並對目標系統按某個原則的進行切分。切分的原則,要便於不一樣的角色,對切分出來的部分,並行或串行開展工做,通常並行才能減小時間。
  3. 並對這些切分出來的部分,設立溝通機制。
  4. 根據3,使得這些部分之間可以進行有機的聯繫,合併組裝成爲一個總體,完成目標系統的全部工做。

界定-切分-溝通-系統,是架構設計的基本步驟。

本系統界定爲基於Retrofit+RxJava實現Android分層網絡請求,而後將整個系統進行切分五個層次,每一個層次的關注點相異,但又相互聯繫,這五個層次經過抽象(抽象類或接口)、繼承、複合等方法進行溝通,造成一個統一系統,完成Android中的網絡請求。

6、文件上下傳及其它網絡訪問

除上述網關請求外,Android中還常常涉及文件上下傳、軟件更新等與網絡相關的操做,這裏也對其進行簡要的介紹。

如上所述,文件上傳須要採用multipart/form-data數據提交方式,所以在Retrofit中定義方法時,須要採用@Multipart註解。

@Multipart
@POST("./")
Observable<UploadFileResponseBean> uploadFile(@Part MultipartBody.Part file,
                                                   @PartMap Map<String, RequestBody> params,
                                                   @HeaderMap Map<String, String> headMap);

同時,其入參類型爲@Part,或@PartMap。須要注意的是,傳入該方法的參數爲MultipartBody.Part。

// 根據文件路徑生成文件
File file = new File(requestBean.getFilePath());
// 根據文件建立請求體
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
// 建立實際請求用的MultipartBody
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);

其餘封裝形式與上述網關請求相似,這裏再也不贅述。

對於文件的下載,筆者嘗試了《Retrofit 2 — How to Download Files from Server》的方法,但因爲其涉及下載進度的監聽以及下載完成的操做等,對後續系統的封裝並很差,這裏就不詳細介紹了。

針對文件下載這種場景,若是自定義實現,須要處理OOM、多線程等問題。DownloadManager是Android2.3之後引入的系統自帶類庫,經過getSystemService(Context.DOWNLOAD_SERVICE)就能獲取並使用,系統服務已經完成網絡訪問控制、文件讀寫控制、通知欄進度顯示、大文件續傳等一系列文件下載可能遇到的問題。所以,推薦系統自帶實現,這個列出簡要參考代碼,詳細狀況請參考《DownloadManager官方文檔》

DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
// 設置目標文件路徑
request.setDestinationInExternalPublicDir(dir, fileName);
// 僅在WIFI網絡下載
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 設置標題及描述
request.setTitle(getString(R.string.app_name));
// 發送請求
downloadManager.enqueue(request);

最後,舉個GET請求的栗子,查詢軟件是否有更新通常會採用GET請求,好比請求參數包括系統、包名、版本號等入參的請求格式爲:

@GET("./")
Observable<ApkUpdateResponseBean> apkUpdate(
        @Query("os") String os,
        @Query("packageName") String packageName,
        @Query("version") String version);
@Query表示請求字段。
相關文章
相關標籤/搜索