目前已經有很多Android客戶端在使用Retrofit+RxJava實現網絡請求了,相比於xUtils,Volley等網絡訪問框架,其具備網絡訪問效率高(基於OkHttp)、內存佔用少、代碼量小以及數據傳輸安全性高等特色。html
Retrofit源碼更是經典的設計模式教程,筆者已在以前的文章中分享過本身的一些體會,有興趣的話可點擊如下連接瞭解:《Retrofit源碼設計模式解析(上)》、《Retrofit源碼設計模式解析(下)》android
但在具體業務場景下,好比涉及到多種網絡請求(GET/PUT/POST/DELETE等),多種請求方式(異步/同步)時,按照Retrofit官方文檔實現網絡請求仍然會顯得比較繁瑣,本文主要介紹筆者基於Retrofit+RxJava封裝的Android分層網絡請求框架,適用於下圖所示的業務場景:Android移動端經過移動網關調用接口平臺發佈的業務服務。git
上述業務架構多是目前移動應用中使用的比較廣的,其具備如下優勢:github
所以,本文分享的分層網絡請求框架的前提是:Android移動端直接對接移動網關。主要有如下內容:json
經過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對應表單數據,在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的主要職責是接收鑑別請求對應的字段,包含服務名、服務分組名、請求方法以及請求參數等,並繼承自上述 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中方法的訪問可能不止一處,所以對外(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中的網絡訪問就很是簡單了。直接上示例代碼:
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分層網絡請求框架,因爲涉及具體業務,只能開放部分代碼樣例。至於對架構的觀點,可參考《什麼是架構?》。
- 根據要解決的問題,對目標系統的邊界進行界定。
- 並對目標系統按某個原則的進行切分。切分的原則,要便於不一樣的角色,對切分出來的部分,並行或串行開展工做,通常並行才能減小時間。
- 並對這些切分出來的部分,設立溝通機制。
- 根據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);