基于Retrofit+RxJava的Android分層網(wǎng)絡(luò)請求框架

目前已經(jīng)有不少Android客戶端在使用Retrofit+RxJava實(shí)現(xiàn)網(wǎng)絡(luò)請求了,相比于xUtils,Volley等網(wǎng)絡(luò)訪問框架,其具有網(wǎng)絡(luò)訪問效率高(基于OkHttp)、內(nèi)存占用少、代碼量小以及數(shù)據(jù)傳輸安全性高等特點(diǎn)。

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

但在具體業(yè)務(wù)場景下,比如涉及到多種網(wǎng)絡(luò)請求(GET/PUT/POST/DELETE等),多種請求方式(異步/同步)時(shí),按照Retrofit官方文檔實(shí)現(xiàn)網(wǎng)絡(luò)請求仍然會顯得比較繁瑣,本文主要介紹筆者基于Retrofit+RxJava封裝的Android分層網(wǎng)絡(luò)請求框架,適用于下圖所示的業(yè)務(wù)場景:Android移動端通過移動網(wǎng)關(guān)調(diào)用接口平臺發(fā)布的業(yè)務(wù)服務(wù)。

Android網(wǎng)絡(luò)訪問架構(gòu)

上述業(yè)務(wù)架構(gòu)可能是目前移動應(yīng)用中使用的比較廣的,其具有以下優(yōu)點(diǎn):

  • 由于移動網(wǎng)關(guān)系統(tǒng)和統(tǒng)一服務(wù)發(fā)布平臺的存在,移動端不需要直接調(diào)用業(yè)務(wù)系統(tǒng)的服務(wù),避免了移動端同時(shí)對接多個(gè)業(yè)務(wù)系統(tǒng),降低移動端系統(tǒng)的復(fù)雜性;
  • 移動網(wǎng)關(guān)會對移動端的請求進(jìn)行鑒權(quán),屏蔽外部惡意訪問,有效提高內(nèi)部業(yè)務(wù)系統(tǒng)的安全性;
  • 統(tǒng)一服務(wù)發(fā)布平臺集成所有的業(yè)務(wù)接口,對外提供格式統(tǒng)一的接口服務(wù),這對于內(nèi)部系統(tǒng)的可維護(hù)性和可擴(kuò)展性是至關(guān)重要的。
  • 業(yè)務(wù)系統(tǒng)只需要按照格式將其服務(wù)在接口平臺上發(fā)布即可,無需關(guān)心具體的調(diào)用者。

因此,本文分享的分層網(wǎng)絡(luò)請求框架的前提是:Android移動端直接對接移動網(wǎng)關(guān)。主要有以下內(nèi)容:

  1. 網(wǎng)關(guān)請求封裝。移動網(wǎng)關(guān)的請求格式(參數(shù)、字段、通信方式等)應(yīng)該是固定的,并且對業(yè)務(wù)是透明的,不觸碰具體業(yè)務(wù)數(shù)據(jù)。負(fù)責(zé)直接對接客戶端的請求,包括請求的鑒權(quán),客戶端與后臺的數(shù)據(jù)格式的轉(zhuǎn)換等。
  2. 基礎(chǔ)業(yè)務(wù)請求?;A(chǔ)業(yè)務(wù)請求涉及到正式/測試環(huán)境的切換,網(wǎng)關(guān)返回業(yè)務(wù)數(shù)據(jù)的統(tǒng)一解析,以及添加業(yè)務(wù)相關(guān)的網(wǎng)關(guān)默認(rèn)字段等;
  3. 業(yè)務(wù)Module統(tǒng)一網(wǎng)絡(luò)請求管理。業(yè)務(wù)Module負(fù)責(zé)統(tǒng)一管理一個(gè)業(yè)務(wù)模塊中所有的網(wǎng)絡(luò)請求,接收鑒別請求對應(yīng)的字段,包含服務(wù)名、服務(wù)分組名、請求方法以及請求參數(shù)等;
  4. Model層網(wǎng)絡(luò)請求。Model層的網(wǎng)絡(luò)請求是按服務(wù)劃分的,一個(gè)應(yīng)用Module通常會對應(yīng)多個(gè)服務(wù),并且接收Activity的參數(shù),組裝請求bean;
  5. Activity層的網(wǎng)絡(luò)訪問。Activity直接調(diào)用Model層的方法,傳入界面相關(guān)的參數(shù),回調(diào)響應(yīng)結(jié)果。
  6. 文件上下傳及其它網(wǎng)絡(luò)訪問。通過Retrofit+RxJava還可以實(shí)現(xiàn)文件上下傳以及軟件更新等其它網(wǎng)絡(luò)訪問,本文也會一并簡要介紹。

一、網(wǎng)關(guān)請求封裝

通過Retrofit注解定義移動網(wǎng)關(guān)接口,比如請求方式,參數(shù)格式,字段等。以POST請求為例,參數(shù)格式為表單數(shù)據(jù),字段包含服務(wù)名、服務(wù)分組名、方法名、參數(shù)、請求頭Map以及其他參數(shù)等。

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

Retrofit的FieldMap不支持字段值為null,如參數(shù)中有null值,需要使用Field。

如上所述,@POST表示該請求是一個(gè)POST方法,常用的POST提交數(shù)據(jù)的方式有:

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

application/x-www-form-urlencoded對應(yīng)表單數(shù)據(jù),在Retrofit中,通過@FormUrlEncoded標(biāo)注的參數(shù)將以表單形式進(jìn)行提交。multipart/form-data一般用于文件上傳的時(shí)候,這個(gè)在后面會提到。application/json通過JSON方式與服務(wù)端進(jìn)行數(shù)據(jù)交換,text/xml使用XML數(shù)據(jù)格式。

定義了網(wǎng)關(guān)請求之后,需要創(chuàng)建對應(yīng)的Service,而Service的使用方式并不確定,這里通過一個(gè)抽象類對其進(jìn)行封裝。

public abstract class WgReqService<T> {

    // 網(wǎng)關(guān)網(wǎng)絡(luò)請求
    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);
}

以同步/異步網(wǎng)絡(luò)請求為例,分別繼承自WgReqService,實(shí)現(xiàn)對應(yīng)的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,因此在異步實(shí)現(xiàn)中,泛型參數(shù)為Observable<T>,而同步請求時(shí)直接返回網(wǎng)關(guān)的出參Bean。另外,需要說明的是WgReqAsync包含域Func1<WGResponseBean, T>,F(xiàn)unc1為RxJava支持的接口,這里表示將網(wǎng)關(guān)返回的業(yè)務(wù)數(shù)據(jù)進(jìn)行統(tǒng)一解析的方法。

二、基礎(chǔ)業(yè)務(wù)請求

通過上述的分析可知,業(yè)務(wù)請求可以有同步/異步等多種實(shí)現(xiàn)方式,同時(shí)涉及到正式/測試環(huán)境的切換,網(wǎng)關(guān)返回業(yè)務(wù)數(shù)據(jù)的統(tǒng)一解析,以及添加業(yè)務(wù)相關(guān)的網(wǎng)關(guān)默認(rèn)字段等,這里以異步請求為例:

public class BaseWgRequest implements Func1<WGResponseBean, BusinessBean> {

    // 網(wǎng)關(guān)請求Helper類
    private WgReqAsync<BusinessBean> wgReqAsync;

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

    // 省略代碼
}

BaseWgRequest持有WgReqAsync<BusinessBean>引用,并通過其完成網(wǎng)關(guān)訪問,service、alias等域指定相應(yīng)的服務(wù),Class<? extends BusinessBean>表示對業(yè)務(wù)返回值進(jìn)行解析的類。

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

異步請求中,通過上述域及業(yè)務(wù)相關(guān)的網(wǎng)關(guān)默認(rèn)字段封裝請求體,同時(shí)獲取請求head。

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

三、業(yè)務(wù)Module

首先申明,對整個(gè)項(xiàng)目進(jìn)行多工程劃分(業(yè)務(wù)工程和庫工程獨(dú)立,便于庫工程獨(dú)立維護(hù)),同時(shí)業(yè)務(wù)工程中分為多個(gè)功能Module(便于功能模塊插件化、熱加載),這種方式在比較大型的項(xiàng)目中應(yīng)用效果可能比較好,在小型項(xiàng)目中并不推薦。這里的業(yè)務(wù)Module是以功能模塊進(jìn)行劃分的,對一個(gè)功能模塊中的所有網(wǎng)絡(luò)請求進(jìn)行統(tǒng)一管理,能有效的單元測試,提高整體開發(fā)效率。

如上所述,業(yè)務(wù)Module的主要職責(zé)是接收鑒別請求對應(yīng)的字段,包含服務(wù)名、服務(wù)分組名、請求方法以及請求參數(shù)等,并繼承自上述 BaseWgRequest實(shí)現(xiàn)。

public class WelNetwork extends BaseWgRequest {}

業(yè)務(wù)Module包含了一個(gè)功能模塊中的所有網(wǎng)絡(luò)請求方法,以登錄為例:

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

這里重點(diǎn)說明下登錄方法的入?yún)?,BaseWgRequest關(guān)注的是與網(wǎng)關(guān)接口相關(guān)的參數(shù),由于業(yè)務(wù)Module繼承自BaseWgRequest,這一層的方法不再關(guān)注網(wǎng)關(guān)相關(guān)內(nèi)容,重點(diǎn)是業(yè)務(wù)相關(guān)的請求入?yún)ⅰQ句話說,業(yè)務(wù)Module的入?yún)⒅苯訉?yīng)業(yè)務(wù)接口的入?yún)?,具有訪問形式的無關(guān)性??紤]清楚每一個(gè)層次的關(guān)注重點(diǎn),是搭建軟件架構(gòu)的基礎(chǔ)。

四、Model層網(wǎng)絡(luò)請求

在本系統(tǒng)中,按照服務(wù)名對Model進(jìn)行了劃分,需要申明的是,由于每個(gè)公司的具體情況不一樣,這種劃分方式不一定適用于你的系統(tǒng)。不過這種分層方式仍有借鑒之處。

由于Model中方法的訪問可能不止一處,因此對外(Activity)提供單實(shí)例對象。這里提供一種最簡單餓漢模式的單實(shí)例:

private static LoginModel loginModel = new LoginModel();

public static LoginModel getInstance() {
    return loginModel;
}

同時(shí),在其構(gòu)造器中初始化業(yè)務(wù)Module訪問類。

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

上面提到,業(yè)務(wù)Module關(guān)注的是業(yè)務(wù)接口的入?yún)ⅲ敲催@個(gè)入?yún)⒕褪怯蠱odel提供的。一個(gè)功能模塊可能對應(yīng)多個(gè)服務(wù),那么這些服務(wù)需要持有業(yè)務(wù)Module的引用,并通過業(yè)務(wù)Module的方法實(shí)現(xiàn)自身的方法。還是以登錄為例:

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

Model負(fù)責(zé)連接Activity和業(yè)務(wù)Module,對上直接對接Activity,Activity關(guān)注的是用戶輸入的用戶名和密碼,并不知道業(yè)務(wù)接口需要的數(shù)據(jù)格式,而業(yè)務(wù)Module關(guān)注的是業(yè)務(wù)接口的入?yún)⒏袷健R虼?,Model層對這兩種數(shù)據(jù)進(jìn)行適配,常見的就是對請求bean的組裝,比如上述登錄方法接收用戶名和密碼,組裝成業(yè)務(wù)Module所需的SysUsersReqDto。

五、Activity層的網(wǎng)絡(luò)訪問

通過上述分層封裝,在Activity中的網(wǎng)絡(luò)訪問就非常簡單了。直接上示例代碼:

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的回調(diào)類,RxObserver包含抽象方法onSuccess,并在onNext實(shí)現(xiàn)中進(jìn)行調(diào)用。

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

    public abstract void onSuccess(T t);
}

從Activity的角度來講,其負(fù)責(zé)用戶交互,因此只關(guān)注用戶輸入和接口返回具體數(shù)據(jù),并對數(shù)據(jù)進(jìn)行處理。而至于網(wǎng)關(guān)的實(shí)現(xiàn),業(yè)務(wù)接口的入?yún)⒏袷?,網(wǎng)絡(luò)請求的方式等底層實(shí)現(xiàn),則對Activity完全閉合。
上述簡要介紹了題目所講到的基于Retrofit+RxJava的Android分層網(wǎng)絡(luò)請求框架,由于涉及具體業(yè)務(wù),只能開放部分代碼樣例。至于對架構(gòu)的觀點(diǎn),可參考《什么是架構(gòu)?》。

  1. 根據(jù)要解決的問題,對目標(biāo)系統(tǒng)的邊界進(jìn)行界定。
  1. 并對目標(biāo)系統(tǒng)按某個(gè)原則的進(jìn)行切分。切分的原則,要便于不同的角色,對切分出來的部分,并行或串行開展工作,一般并行才能減少時(shí)間。
  2. 并對這些切分出來的部分,設(shè)立溝通機(jī)制。
  3. 根據(jù)3,使得這些部分之間能夠進(jìn)行有機(jī)的聯(lián)系,合并組裝成為一個(gè)整體,完成目標(biāo)系統(tǒng)的所有工作。

界定-切分-溝通-系統(tǒng),是架構(gòu)設(shè)計(jì)的基本步驟。

本系統(tǒng)界定為基于Retrofit+RxJava實(shí)現(xiàn)Android分層網(wǎng)絡(luò)請求,然后將整個(gè)系統(tǒng)進(jìn)行切分五個(gè)層次,每個(gè)層次的關(guān)注點(diǎn)相異,但又相互聯(lián)系,這五個(gè)層次通過抽象(抽象類或接口)、繼承、復(fù)合等方法進(jìn)行溝通,形成一個(gè)統(tǒng)一系統(tǒng),完成Android中的網(wǎng)絡(luò)請求。

六、文件上下傳及其它網(wǎng)絡(luò)訪問

除上述網(wǎng)關(guān)請求外,Android中還經(jīng)常涉及文件上下傳、軟件更新等與網(wǎng)絡(luò)相關(guān)的操作,這里也對其進(jìn)行簡要的介紹。如上所述,文件上傳需要采用multipart/form-data數(shù)據(jù)提交方式,因此在Retrofit中定義方法時(shí),需要采用@Multipart注解。

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

同時(shí),其入?yún)㈩愋蜑锧Part,或@PartMap。需要注意的是,傳入該方法的參數(shù)為MultipartBody.Part。

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

其他封裝形式與上述網(wǎng)關(guān)請求類似,這里不再贅述。

對于文件的下載,筆者嘗試了《Retrofit 2 — How to Download Files from Server
》的方法,但由于其涉及下載進(jìn)度的監(jiān)聽以及下載完成的操作等,對后續(xù)系統(tǒng)的封裝并不好,這里就不詳細(xì)介紹了。

針對文件下載這種場景,如果自定義實(shí)現(xiàn),需要處理OOM、多線程等問題。DownloadManager是Android2.3以后引入的系統(tǒng)自帶類庫,通過getSystemService(Context.DOWNLOAD_SERVICE)就能獲取并使用,系統(tǒng)服務(wù)已經(jīng)完成網(wǎng)絡(luò)訪問控制、文件讀寫控制、通知欄進(jìn)度顯示、大文件續(xù)傳等一系列文件下載可能遇到的問題。因此,推薦系統(tǒng)自帶實(shí)現(xiàn),這個(gè)列出簡要參考代碼,詳細(xì)情況請參考《DownloadManager官方文檔》

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

最后,舉個(gè)GET請求的栗子,查詢軟件是否有更新一般會采用GET請求,比如請求參數(shù)包括系統(tǒng)、包名、版本號等入?yún)⒌恼埱蟾袷綖椋?/p>

@GET("./")
Observable<ApkUpdateResponseBean> apkUpdate(
        @Query("os") String os,
        @Query("packageName") String packageName,
        @Query("version") String version);

@Query表示請求字段。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容