目前已經(jīng)有不少Android客戶端在使用Retrofit+RxJava實現(xiàn)網(wǎng)絡(luò)請求了陪腌,相比于xUtils烟瞧,Volley等網(wǎng)絡(luò)訪問框架参滴,其具有網(wǎng)絡(luò)訪問效率高(基于OkHttp)砾赔、內(nèi)存占用少弥咪、代碼量小以及數(shù)據(jù)傳輸安全性高等特點聚至。
Retrofit源碼更是經(jīng)典的設(shè)計模式教程扳躬,筆者已在之前的文章中分享過自己的一些體會甚亭,有興趣的話可點擊以下鏈接了解:《Retrofit源碼設(shè)計模式解析(上)》亏狰、《Retrofit源碼設(shè)計模式解析(下)》
但在具體業(yè)務(wù)場景下,比如涉及到多種網(wǎng)絡(luò)請求(GET/PUT/POST/DELETE等)促脉,多種請求方式(異步/同步)時瘸味,按照Retrofit官方文檔實現(xiàn)網(wǎng)絡(luò)請求仍然會顯得比較繁瑣够挂,本文主要介紹筆者基于Retrofit+RxJava封裝的Android分層網(wǎng)絡(luò)請求框架孽糖,適用于下圖所示的業(yè)務(wù)場景:Android移動端通過移動網(wǎng)關(guān)調(diào)用接口平臺發(fā)布的業(yè)務(wù)服務(wù)。
上述業(yè)務(wù)架構(gòu)可能是目前移動應(yīng)用中使用的比較廣的尘奏,其具有以下優(yōu)點:
- 由于移動網(wǎng)關(guān)系統(tǒng)和統(tǒng)一服務(wù)發(fā)布平臺的存在罪既,移動端不需要直接調(diào)用業(yè)務(wù)系統(tǒng)的服務(wù)琢感,避免了移動端同時對接多個業(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)的可維護性和可擴展性是至關(guān)重要的漏健。
- 業(yè)務(wù)系統(tǒng)只需要按照格式將其服務(wù)在接口平臺上發(fā)布即可,無需關(guān)心具體的調(diào)用者殖属。
因此洗显,本文分享的分層網(wǎng)絡(luò)請求框架的前提是:Android移動端直接對接移動網(wǎng)關(guān)谭溉。主要有以下內(nèi)容:
- 網(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)換等枝缔。
- 基礎(chǔ)業(yè)務(wù)請求愿卸。基礎(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)字段等;
- 業(yè)務(wù)Module統(tǒng)一網(wǎng)絡(luò)請求管理。業(yè)務(wù)Module負(fù)責(zé)統(tǒng)一管理一個業(yè)務(wù)模塊中所有的網(wǎng)絡(luò)請求寓调,接收鑒別請求對應(yīng)的字段锄码,包含服務(wù)名、服務(wù)分組名痛悯、請求方法以及請求參數(shù)等重窟;
- Model層網(wǎng)絡(luò)請求。Model層的網(wǎng)絡(luò)請求是按服務(wù)劃分的扭仁,一個應(yīng)用Module通常會對應(yīng)多個服務(wù)厅翔,并且接收Activity的參數(shù)刀闷,組裝請求bean仰迁;
- Activity層的網(wǎng)絡(luò)訪問徐许。Activity直接調(diào)用Model層的方法怯邪,傳入界面相關(guān)的參數(shù),回調(diào)響應(yīng)結(jié)果澄步。
- 文件上下傳及其它網(wǎng)絡(luò)訪問村缸。通過Retrofit+RxJava還可以實現(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表示該請求是一個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一般用于文件上傳的時候老翘,這個在后面會提到锻离。application/json通過JSON方式與服務(wù)端進(jìn)行數(shù)據(jù)交換墓怀,text/xml使用XML數(shù)據(jù)格式傀履。
定義了網(wǎng)關(guān)請求之后,需要創(chuàng)建對應(yīng)的Service碴犬,而Service的使用方式并不確定服协,這里通過一個抽象類對其進(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忍饰,實現(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赢织,因此在異步實現(xiàn)中逛拱,泛型參數(shù)為Observable<T>朽合,而同步請求時直接返回網(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ù)請求可以有同步/異步等多種實現(xiàn)方式奖地,同時涉及到正式/測試環(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)字段封裝請求體郎汪,同時獲取請求head。
// 請求
return wgReqAsync.wgReq(ParamUtil.getWGRequestBean(service, alias, method, param),
BaseConstants.getHeaderMap());
三闯狱、業(yè)務(wù)Module
首先申明煞赢,對整個項目進(jìn)行多工程劃分(業(yè)務(wù)工程和庫工程獨立,便于庫工程獨立維護)哄孤,同時業(yè)務(wù)工程中分為多個功能Module(便于功能模塊插件化照筑、熱加載),這種方式在比較大型的項目中應(yīng)用效果可能比較好蛾默,在小型項目中并不推薦。這里的業(yè)務(wù)Module是以功能模塊進(jìn)行劃分的牧挣,對一個功能模塊中的所有網(wǎng)絡(luò)請求進(jìn)行統(tǒng)一管理浸踩,能有效的單元測試据块,提高整體開發(fā)效率像屋。
如上所述己莺,業(yè)務(wù)Module的主要職責(zé)是接收鑒別請求對應(yīng)的字段阵子,包含服務(wù)名、服務(wù)分組名领突、請求方法以及請求參數(shù)等君旦,并繼承自上述 BaseWgRequest實現(xiàn)。
public class WelNetwork extends BaseWgRequest {}
業(yè)務(wù)Module包含了一個功能模塊中的所有網(wǎng)絡(luò)請求方法捞魁,以登錄為例:
public Observable<BusinessBean> userLoginWork(SysUsersReqDto sysUsersReqDto) {
return wgRequest(service, alias, BusinessConstants.userLoginWork, ParamUtil.getJsonParam(sysUsersReqDto));
}
這里重點說明下登錄方法的入?yún)⑾珺aseWgRequest關(guān)注的是與網(wǎng)關(guān)接口相關(guān)的參數(shù)凑懂,由于業(yè)務(wù)Module繼承自BaseWgRequest接谨,這一層的方法不再關(guān)注網(wǎng)關(guān)相關(guān)內(nèi)容,重點是業(yè)務(wù)相關(guān)的請求入?yún)⑸ㄒ埂Q句話說堕阔,業(yè)務(wù)Module的入?yún)⒅苯訉?yīng)業(yè)務(wù)接口的入?yún)⒊剑哂性L問形式的無關(guān)性⊥诉耄考慮清楚每一個層次的關(guān)注重點,是搭建軟件架構(gòu)的基礎(chǔ)垢油。
四、Model層網(wǎng)絡(luò)請求
在本系統(tǒng)中硝枉,按照服務(wù)名對Model進(jìn)行了劃分欣福,需要申明的是雏逾,由于每個公司的具體情況不一樣,這種劃分方式不一定適用于你的系統(tǒng)。不過這種分層方式仍有借鑒之處妹孙。
由于Model中方法的訪問可能不止一處,因此對外(Activity)提供單實例對象嚣崭。這里提供一種最簡單餓漢模式的單實例:
private static LoginModel loginModel = new LoginModel();
public static LoginModel getInstance() {
return loginModel;
}
同時懦傍,在其構(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)ⅲ敲催@個入?yún)⒕褪怯蠱odel提供的神汹。一個功能模塊可能對應(yīng)多個服務(wù)沧卢,那么這些服務(wù)需要持有業(yè)務(wù)Module的引用,并通過業(yè)務(wù)Module的方法實現(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虼朔嗡兀琈odel層對這兩種數(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實現(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)的實現(xiàn),業(yè)務(wù)接口的入?yún)⒏袷窖炊担W(wǎng)絡(luò)請求的方式等底層實現(xiàn)漏策,則對Activity完全閉合。
上述簡要介紹了題目所講到的基于Retrofit+RxJava的Android分層網(wǎng)絡(luò)請求框架,由于涉及具體業(yè)務(wù),只能開放部分代碼樣例谦絮。至于對架構(gòu)的觀點叫胖,可參考《什么是架構(gòu)怎棱?》砸捏。
- 根據(jù)要解決的問題,對目標(biāo)系統(tǒng)的邊界進(jìn)行界定咐扭。
- 并對目標(biāo)系統(tǒng)按某個原則的進(jìn)行切分。切分的原則,要便于不同的角色昙篙,對切分出來的部分焚辅,并行或串行開展工作,一般并行才能減少時間。
- 并對這些切分出來的部分颁股,設(shè)立溝通機制葡缰。
- 根據(jù)3,使得這些部分之間能夠進(jìn)行有機的聯(lián)系间影,合并組裝成為一個整體付燥,完成目標(biāo)系統(tǒng)的所有工作漩怎。
界定-切分-溝通-系統(tǒng),是架構(gòu)設(shè)計的基本步驟。
本系統(tǒng)界定為基于Retrofit+RxJava實現(xiàn)Android分層網(wǎng)絡(luò)請求蚕断,然后將整個系統(tǒng)進(jìn)行切分五個層次葛假,每個層次的關(guān)注點相異,但又相互聯(lián)系,這五個層次通過抽象(抽象類或接口)、繼承苦银、復(fù)合等方法進(jìn)行溝通,形成一個統(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中定義方法時,需要采用@Multipart注解。
@Multipart
@POST("./")
Observable<UploadFileResponseBean> uploadFile(@Part MultipartBody.Part file,
@PartMap Map<String, RequestBody> params,
@HeaderMap Map<String, String> headMap);
同時,其入?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)建實際請求用的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ì)介紹了。
針對文件下載這種場景,如果自定義實現(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)自帶實現(xiàn)晤揣,這個列出簡要參考代碼,詳細(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);
最后池户,舉個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表示請求字段烤宙。