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


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

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

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

繪圖2

上述業務架構可能是目前移動應用中使用的比較廣的,其具有以下優點:

  • 由於移動網關系統和統一服務發布平台的存在,移動端不需要直接調用業務系統的服務,避免了移動端同時對接多個業務系統,降低移動端系統的復雜性;
  • 移動網關會對移動端的請求進行鑒權,屏蔽外部惡意訪問,有效提高內部業務系統的安全性;
  • 統一服務發布平台集成所有的業務接口,對外提供格式統一的接口服務,這對於內部系統的可維護性和可擴展性是至關重要的。
  • 業務系統只需要按照格式將其服務在接口平台上發布即可,無需關心具體的調用者。

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

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

一、網關請求封裝

通過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支持的接口,這里表示將網關返回的業務數據進行統一解析的方法。

二、基礎業務請求

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

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());

三、業務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的入參直接對應業務接口的入參,具有訪問形式的無關性。考慮清楚每一個層次的關注重點,是搭建軟件架構的基礎。

四、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。

五、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中的網絡請求。

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

除上述網關請求外,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表示請求字段。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM