目前已經有不少Android客戶端在使用Retrofit+RxJava實現網絡請求了,相比於xUtils,Volley等網絡訪問框架,其具有網絡訪問效率高(基於OkHttp)、內存占用少、代碼量小以及數據傳輸安全性高等特點。
Retrofit源碼更是經典的設計模式教程,筆者已在之前的文章中分享過自己的一些體會,有興趣的話可點擊以下鏈接了解:《Retrofit源碼設計模式解析(上)》、《Retrofit源碼設計模式解析(下)》
但在具體業務場景下,比如涉及到多種網絡請求(GET/PUT/POST/DELETE等),多種請求方式(異步/同步)時,按照Retrofit官方文檔實現網絡請求仍然會顯得比較繁瑣,本文主要介紹筆者基於Retrofit+RxJava封裝的Android分層網絡請求框架,適用於下圖所示的業務場景:Android移動端通過移動網關調用接口平台發布的業務服務。
上述業務架構可能是目前移動應用中使用的比較廣的,其具有以下優點:
- 由於移動網關系統和統一服務發布平台的存在,移動端不需要直接調用業務系統的服務,避免了移動端同時對接多個業務系統,降低移動端系統的復雜性;
- 移動網關會對移動端的請求進行鑒權,屏蔽外部惡意訪問,有效提高內部業務系統的安全性;
- 統一服務發布平台集成所有的業務接口,對外提供格式統一的接口服務,這對於內部系統的可維護性和可擴展性是至關重要的。
- 業務系統只需要按照格式將其服務在接口平台上發布即可,無需關心具體的調用者。
因此,本文分享的分層網絡請求框架的前提是:Android移動端直接對接移動網關。主要有以下內容:
- 網關請求封裝。移動網關的請求格式(參數、字段、通信方式等)應該是固定的,並且對業務是透明的,不觸碰具體業務數據。負責直接對接客戶端的請求,包括請求的鑒權,客戶端與后台的數據格式的轉換等。
- 基礎業務請求。基礎業務請求涉及到正式/測試環境的切換,網關返回業務數據的統一解析,以及添加業務相關的網關默認字段等;
- 業務Module統一網絡請求管理。業務Module負責統一管理一個業務模塊中所有的網絡請求,接收鑒別請求對應的字段,包含服務名、服務分組名、請求方法以及請求參數等;
- Model層網絡請求。Model層的網絡請求是按服務划分的,一個應用Module通常會對應多個服務,並且接收Activity的參數,組裝請求bean;
- Activity層的網絡訪問。Activity直接調用Model層的方法,傳入界面相關的參數,回調響應結果。
- 文件上下傳及其它網絡訪問。通過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分層網絡請求框架,由於涉及具體業務,只能開放部分代碼樣例。至於對架構的觀點,可參考《什么是架構?》。
- 根據要解決的問題,對目標系統的邊界進行界定。
- 並對目標系統按某個原則的進行切分。切分的原則,要便於不同的角色,對切分出來的部分,並行或串行開展工作,一般並行才能減少時間。
- 並對這些切分出來的部分,設立溝通機制。
- 根據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);