Android:Okhttp+Retrofit+Rx+RxLificycler+Fragmenation框架搭建自己的技術堆疊(一)
自己獨立開發一個新專案。從技術選型到框架構建,再到具體的程式碼編輯,單元測試,全部由一個人負責。(說白了,就是把你扔那裡,看你能弄出什麼么蛾子)。特此,在這裡記錄自己的開發過程。
在沒看到產品需求和設計之前,自己先確定大概的專案框架和技術選型。
- MVP的設計模式,最大程度上解耦Activity和業務邏輯的關係;
- OkHttp+Retrofit 的網路訪問方案;
- RxJava+RxAndroid+RxLifeCycler實現執行緒控制的方案
- Fragmenation 實現Fragment的管理;
- EventBus實現元件間通訊
因為是小型專案,所以以上的技術選型基本上可以滿足需求。現將各個部分的實現簡單記錄一下。今天主要記錄網路訪問這一塊。
網路訪問這一塊基本上是對OkHttp+Retrofit+Rx的一些封裝以及對網路訪問錯誤的統一化處理。先看看我的專案分包和設計到的網路訪問的分包吧。
專案分包分的比較隨便,基本上從命名上可以理解
- base: 專案中的基類,,如BaseActivity,BaseFragment等
- bean: bean類,各種專案中bean物件的封裝;
- configs: 專案中的常量和引數(全域性性);
- http: 專案中的網路訪問部分;
- ui: 介面部分。裡面是按照模組分的,每個模組又按照mvp模式繼續分包;
- utils: 專案中涉及到的工具類;
- widget 專案中涉及到的自定義View;
其中http下的分包為:
- api:Retrofitde 需要的網路介面和api的幫助類(用於實現多個api的管理);
- exception:涉及到異常的統一化處理。
- function:自定義的實現Rx 中Function介面的類;
- intercepter:Okhttp的攔截器;
- listener:網路狀態的回撥;
- retrofit:Retrofit的封裝和幫助類;
- rx:實現對Rx網路訪問的封裝;
1.OkHttp+Retrofit的簡易封裝:
封裝OkHttp
該方法返回一個okhttp的例項:該例項實現了日誌列印,快取處理,以及自定義訊息頭。這三者的實現都跟okhttp的攔截器有關; // 獲取http例項;
private OkHttpClient okHttpClient(){
// 快取目錄;
File cacheFile = new File(BaseApplication.getContext().getCacheDir(), HTTP_CASH_FILE_DIR);
// logging 網路攔截
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// 例項化client
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(NET_CONNECTED_TIME_OUT, TimeUnit.SECONDS)
.readTimeout(NET_READ_TIME_OUT,TimeUnit.SECONDS)
.writeTimeout(NET_WRITE_TIME_OUT,TimeUnit.SECONDS)
.cache(new Cache(cacheFile,HTTP_CASH_SIZE))
.addInterceptor(httpLoggingInterceptor) // 日誌
.addInterceptor(new HeadsInterceptor()) // 自定義的訊息頭
.addInterceptor(new CacheInterceptor()) // 快取
.build();
return client;
}
日誌列印:HttpLogingInterceptor已經幫我們封裝的很好了,我們我們只要在我們的gradle檔案中宣告一下就可以很方便的使用了。
cache快取的話,我們需要自定義一個快取目錄,(一般在應用快取目錄或者是系統內建儲存的快取目錄,當然你也可以自己定義),定義完後,在Okhttp中設定就可以了。當然做完這些是不完美的。因為這些僅僅是自己一廂情願,你還要在你的收到的響應裡面去改幾個引數。於是CacheInterceptor就應運而生了。
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//get the origin response;
Response originResponse = chain.proceed(chain.request());
String cacheSetting = originResponse.cacheControl().toString();
// if server can not support cache ,then cacheSetting is null or empty;
if(TextUtils.isEmpty(cacheSetting)){
cacheSetting = "public,max-age=60";
}
// return the rebuild response;
return originResponse.newBuilder()
.addHeader("Cache-Control",cacheSetting)
.removeHeader("Pragma")
.build();
}
}
最後,自定義訊息頭也是經常性用到的。在使用者註冊或者登陸後,為了辨別使用者身份,後端基本上都會要求我們在網路請求中帶上token。如果涉及到加密和解密的話,也會叫我們帶上一些公鑰。這些都是通過網路攔截器實現的。
public class HeadsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
SPUtils sp = SPUtils.getInstance();
String deviceId = DeviceUtils.getDeviceId();
String token = sp.getString(CommonConfigs.KEY_TOKEN, null);
String secretKey = sp.getString(CommonConfigs.KEY_SECRET_KEY, null);
Request.Builder builder = chain.request().newBuilder();
// add head;
if(BaseApplication.getLoginState()){
builder.addHeader(CommonConfigs.KEY_DEVICE_ID,deviceId)
.addHeader(CommonConfigs.KEY_TOKEN,token)
.addHeader(CommonConfigs.KEY_SECRET_KEY,secretKey);
}else {
builder.addHeader(CommonConfigs.KEY_DEVICE_ID,deviceId);
}
return chain.proceed(builder.build());
}
}
這裡,使用者登入後,後端希望我的每個訪問都能帶上使用者token secretKey和deviceId;封裝Retrofit
Retrofit是個非常優秀的網路框架。這裡我加入了Json解析和Rx2的功能。public Retrofit retrofit(){
return new Retrofit.Builder()
.baseUrl(HTTP_BASE_URL)
.client(okHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
封裝RetrofitHelper
RetrofitHelper全域性的單利,通過它,可以獲得Retrofit的客戶端。 private static RetrofitHelper mHelper;
// 私有化構建函式;
private RetrofitHelper(){
}
// 對外提供獲取該例項的方法;
public static RetrofitHelper getHelper(){
if(mHelper == null){
synchronized (RetrofitHelper.class){
if(mHelper == null){
mHelper = new RetrofitHelper();
}
}
}
return mHelper;
}
ApiHelper管理Api分類
ApiHelper主要是使用者管理Retrofit的api介面分類。因為很多情況,網路訪問介面我都會以模組來分類,一個模組對應一個api介面類。這樣做不僅可以便於我們做網路介面的單元測試,同時也方便我們管理和查詢我們的介面。public class ApiHelper {
private static LotteryApi mLotteryApi;
public static LotteryApi getLotteryApi(){
if(mLotteryApi == null){
RetrofitHelper.getHelper().retrofit().create(LotteryApi.class);
}
return mLotteryApi;
}
}
當然,因為保密問題,只羅列出了一個介面。2.異常的統一化處理
對於異常的話,這裡可以分為兩個大類: 客戶端異常: 客戶端異常就是指在非正常的網路訪問,或者在網路訪問過程中對資料的錯誤處理觸發的,可以在客戶端捕獲的異常或錯誤。具體的例子如,連線超時,讀寫超時,Json解析錯誤等。這些異常的發生都可以使得客戶端程式崩潰,或者異常中斷,以至於無法獲得我們需要的資料。但是這類資料通常我們可以在程式碼中捕獲。服務端異常: 服務端的異常,說白了就是後端與我們的約定的異常資訊。這類資訊往往不會觸發程式異常奔潰,網路互動功能可以正常進行。但是往往我們無法獲取我們期望的資料。資料往往為空。最常見例子就是:我們往往與後端約定返回碼200為網路訪問成功,非200則為某種異常,這種異常資訊往往由後端自己定義。如下圖 當這類異常發生的時候,我們的程式中是正常執行的,不丟擲異常則無法使我們的程式中斷,也就無法對此做相應的UI處理。當然,也許會有人說,跟以前一樣,在拿資料時候先判斷返回碼。這是一種方式。但是,這種方式有兩個弊端。一因為涉及的網路訪問太多,每次網路訪問都這樣寫,程式碼無法複用(如果自己封裝一套當然也是可以的),二則是無法實現服務端異常與客戶端異常的統一化處理,用這種方式處理服務端異常,往往是在我們從後端拿到資料後,而我們拿到資料後的操作往往是應該配合UI的賦值操作,如果這時候還要去做一系列的異常處理,至少在程式碼風格上顯得不太優雅。所以,將所有的異常進行統一的處理是顯得很有必要的。最後,我希望的結果是,在一次網路訪問中,只有兩種結果,成功與失敗。成功可以獲取資料,失敗知道失敗的原因。
2.1定義網路回撥介面
public interface OnHttpCallback<T> {
// 成功的回撥;
void onSucceed();
// 失敗的回撥;
void onFailed(ApiException exception);
}
該介面只有兩個方法,成功和失敗。這裡使用了泛型。成功的方法中返回成功後的資料。失敗的方法中返回失敗的異常物件。ApiException這個類是我自定義的異常類,是我們統一的異常類,它只有兩個屬性,異常碼,和異常宣告,具體如下;public class ApiException extends Exception {
private String msg;
private int code;
public ApiException(Throwable throwable, int code){
super(throwable);
this.code = code;
}
public ApiException(String msg, int code){
this.msg = msg;
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
當然,也要為服務端異常封裝一個類 public class ServerException extends RuntimeException {
private String msg;
private int code;
public ServerException(int code ,String msg){
this.code = code;
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
2.2 異常的統一轉化。
這裡自定義了一個工具類,用於將所有的異常統一轉化為ApiException:public class ExceptionEngine {
public static final int UN_KNOW_ERROR = 10000;
public static final int ANALYTIC_SERVER_DATA_ERROR = 10001;
public static final int ANALYTIC_CLIENT_DATA_ERROR = 10002;
public static final int NET_CONNECT_TIME_OUT = 10003;
public static final int NET_CONNECT_ERROR = 10004;
// 異常統一化處理;
public static ApiException handleException(Throwable throwable){
ApiException apiException = null;
if(throwable instanceof HttpException){
HttpException httpException = (HttpException) throwable;
apiException = new ApiException(throwable,httpException.code());
apiException.setMsg("網路錯誤");
}else if (throwable instanceof ServerException){
ServerException serverException = (ServerException) throwable;
apiException = new ApiException(serverException.getMsg(),serverException.getCode());
}else if(throwable instanceof JSONException || throwable instanceof JsonParseException
|| throwable instanceof ParseException || throwable instanceof MalformedJsonException){
apiException = new ApiException(throwable,ANALYTIC_SERVER_DATA_ERROR);
apiException.setMsg("解析錯誤");
}else if(throwable instanceof ConnectException){
apiException = new ApiException(throwable,NET_CONNECT_ERROR);
apiException.setMsg("連線失敗");
}else if(throwable instanceof SocketTimeoutException){
apiException = new ApiException(throwable,NET_CONNECT_TIME_OUT);
apiException.setMsg("連線超時");
}else {
apiException = new ApiException(throwable,UN_KNOW_ERROR);
apiException.setMsg("未知錯誤");
}
return apiException;
}
2.3:配合Rx框架
因為使用的RxAndroid+RxJava+RxLifeCycler框架。所以處理時機需要的選擇很重要。在這裡我的處理邏輯是如下:我們從服務端獲取的資料格式一般是包含狀態碼,狀態資訊,和返回資料。{"code":0, "message":"登入成功",”data”:{“token”:3434…….,”secretKey”:ewadwadwad… ,”data”:{……}}}
資料層級上而言,一般情況下,我們需要的資料是data物件。而code和message屬性則是我們判斷是否發生了服務端異常的標準。一般我們在網路訪問成功後,都需要剔除出有用的資料,這個操作一般在Rx 的map方法中進行操作。我們就在map的過程中判斷是否存在服務端異常,如果存在則丟擲異常。先看看對於服務端資料的封裝物件;public class HttpResponse<T> {
private int code;
private String message;
private T data;
public HttpResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
public boolean isSuccessful(){
if(code == CommonConfigs.HTTP_RESPONSE_SUCCEED_CODE){
return true;
}
return false;
}
}
在Rx的map中,我們如果沒有異常,則返回data中資料,如果發生異常,則丟擲一個ServerException,於是,我們可以自定義一個Function;public class MapFunction<T> implements Function<HttpResponse<T>,T> {
@Override
public T apply(HttpResponse<T> tHttpResponse) throws Exception {
// 如果響應碼不是約定好的,則丟擲伺服器異常;
if(!tHttpResponse.isSuccessful()){
throw new ServerException(tHttpResponse.getCode(),tHttpResponse.getMessage());
}
// 過濾出有用資料;
return tHttpResponse.getData();
}
}
在丟擲服務端異常後,接下來是對異常的統一轉化,這裡我是在Rx的onErrorResumeNext()方法中進行的,該方法的作用是Rx在丟擲錯誤後,停止原先Observerable的資料分發,重新訂閱onErrorResumeNext()方法中的被觀察者,說白了,就是原先的被觀察者丟擲異常後,會停止對其訂閱,轉而重新訂閱該方法中的被觀察者。該方法的內部也是一個function介面;public class ErrorFunction<T> implements Function<Throwable,Observable<T>> {
@Override
public Observable<T> apply(Throwable throwable) throws Exception {
return Observable.error(ExceptionEngine.handleException(throwable));
}
}
直接異常統一轉化。簡單粗暴。轉化後的異常在哪裡去處理呢?當然是在Oberver物件中了,這裡,我自定義了一個Oberver物件。主要是實現將網路訪問的結果傳遞。這裡的介面是指第一步定義的介面。
public class RxObserver<T> implements Observer<T> {
OnHttpCallback<T> mCallback;
Disposable mDisposable;
public OnHttpCallback<T> getmCallback() {
return mCallback;
}
public RxObserver (OnHttpCallback callback){
this.mCallback = callback;
}
@Override
public void onSubscribe(Disposable d) {
mDisposable = d;
}
@Override
public void onNext(T t) {
if(mCallback!=null){
mCallback.onSucceed();
}
}
@Override
public void onError(Throwable e) {
ApiException exception = null;
if(!(e instanceof ApiException)){
exception = ExceptionEngine.handleException(e);
}
if(mCallback!=null){
mCallback.onFailed(exception);
}
}
@Override
public void onComplete() {
}
// dispose;
public void cancle(){
if(mDisposable != null){
mDisposable.dispose();
mDisposable = null;
}
}
}
程式碼太簡單,沒什麼好講的。 同時,在Observerable中,封裝了異常的統一化處理方案,如下public class RxObservable<T>{
Observable<HttpResponse<T>> observable;
public RxObservable(Observable<HttpResponse<T>> observable){
this.observable = observable;
}
public Observable<T> init(){
return observable.map(new MapFunction<T>())
.onErrorResumeNext(new ErrorFunction<T>());
}
}
這裡原來是計劃採用裝飾者模式的。但是怕實現過程中有些東西遺漏掉,就放棄了。最後,自己封裝了一個網路訪問的工具類,該工具類將Rx生命週期的回收也加入了進去;
public class RxHelper {
// 網路互動,並且繫結生命週期;
public static void subscribe(@NonNull Observable observable, @NonNull ObservableTransformer
transformer, @NonNull OnHttpCallback callback) {
new RxObservable<>(observable).init()
.compose(transformer)
.subscribeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RxObserver(callback));
}
// 網路互動,但是不繫結生命週期;
public static void subscribe(@NonNull Observable observable, @NonNull OnHttpCallback callback) {
new RxObservable<>(observable).init()
.subscribeOn(Schedulers.newThread())
.unsubscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RxObserver(callback));
}
// 獲取被觀察者;
public static Observable observable(@NonNull Observable observable){
return new RxObservable<>(observable).init();
}
// 獲取觀察者;
public static Observer observer(@NonNull OnHttpCallback callback){
return new RxObserver(callback);
}
最後,當我們需要進行網路訪問的時候,只要呼叫RxHelper就可以了。 當然,這裡的內容有一部分設計到了MVP,這裡後面會將。
總結,進行這樣封裝後,網路訪問一句搞定。
相關推薦
Android:Okhttp+Retrofit+Rx+RxLificycler+Fragmenation框架搭建自己的技術堆疊(一)
自己獨立開發一個新專案。從技術選型到框架構建,再到具體的程式碼編輯,單元測試,全部由一個人負責。(說白了,就是把你扔那裡,看你能弄出什麼么蛾子)。特此,在這裡記錄自己的開發過程。 在沒看到產品需求和設計之前,自己先確定大概的專案框架和技術
Flask框架的學習與實戰(一):開發環境搭建
進行 read 模型 clas tar pychar html itl .html Flask是一個使用 Python 編寫的輕量級 Web 應用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎則使用 Jinja2。很多功能的實現都參考了django框架。由於項
vue從入門到女裝??:從零開始搭建後臺管理系統(一)安裝框架
安裝及執行都是基於node的,不會node的可以自行百度,網上教程很多,也不難 專案效果預覽: demo1 demo2 原始碼下載 開始安裝框架: vue element-ui 注意如果報錯安裝失敗就重新安裝,不然雖然本地有element的依賴包但是可能會出一些奇怪的錯誤 另外element-ui
SSM框架開發web專案系列(一) 環境搭建篇
前言 開發環境:Eclipse Mars + Maven + JDK 1.7 + Tomcat 7 + MySQL 主要框架:Spring + Spring MVC + Mybatis 目的:快速上手搭建SSM開發環境,熟悉客戶端請求到資料庫操作的流程。
NET使用ABP框架搭建部落格專案(二) 使用者模組
首先我們先來設計下使用者模組,簡單使用者模組設計涵蓋兩個類,使用者表以及角色表。如下圖: 我們在Core專案中增加一個資料夾Domain,在Domain增加Customers資料夾,新增一個列舉PasswordFormat並且繼承Int。 /// <
詳細講解Android的圖片下載框架UniversialImageLoader之磁碟快取(一)
沉浸在Android的開發世界中有一些年頭的猴子們,估計都能夠深深的體會到Android中的圖片下載、展示、快取一直是心中抹不去的痛。鄙人亦是如此。Ok,閒話不說,為了督促自己的學習,下面就逐一的挖掘Android中還算是比較牛叉的圖片處理框架Universial
python框架之 Tornado 學習筆記(一)
tornado pythontornado 一個簡單的服務器的例子:首先,我們需要安裝 tornado ,安裝比較簡單: pip install tornado 測試安裝是否成功,可以打開python 終端,輸入: import tornado.https
IDEA搭建maven項目(一)
ext web項目 mage 技術 png images http 點擊 項目 在IntelliJ IDEA中配置maven 打開-File-Settings 新建maven WEB項目 打開-File-New-Project 點擊NEXT
如何搭建一個web網站(一)
團隊合作 是的 轉換 們的 web服務 ons lang 用戶 域名 前言: 由於新生軍訓結束,作為學生會的一個技術部的老油條,這時候得幫幫他們了。 大多數新生都是奔著能做一些小東西,能夠被大家,被其他人用,為目的進入了技術部,部門主要負責做院系微信運營,順帶做開發。前兩任
從零開始利用vue-cli搭建簡單音樂網站(一)
路徑 nod .com mman csdn desc blog -a where 最近在學習vue框架,練習了一些例子之後,想著搭建一個vue項目,了解到官方有提供一個vue-cli工具來搭建項目腳手架,嘗試了一下,寫下博客來記錄一下。 一、工具環境 1、node.js 6
【SSH框架】之Struts2系列(一)
核心 mapping 調度 fault code 組件 -i -c params 微信公眾號:compassblog 歡迎關註、轉發,互相學習,共同進步! 有任何問題,請後臺留言聯系 1、Struts2框架概述 (1)、什麽是Struts2 Struts2是一種基於M
【SSH框架】之Spring系列(一)
oca getc per 名稱 寫入 xmla java開發 無需 不能 微信公眾號:compassblog 歡迎關註、轉發,互相學習,共同進步! 有任何問題,請後臺留言聯系! 1、前言 前面更新過幾篇關於 Struts2 框架和 Hibernate 框架的文章,但鑒於
react搭建後臺管理系統(一)
管理系 for menu port 文件中 segment 後臺管理 ans create 先準備工具: yarn安裝: npm install -g yarn #yarn也是包管理工具,只不過它構建效率更高 官方使用教程:https://yarnpkg.
作業流 oozie調度框架的配置與使用(一)
大數據 hadoop oozie 調度框架 一: 常見的調度框架 一: oozie 概述與功能 二: oozie 安裝與配置 一: 常見的作用調度框架 1.1 linux 下面的計劃任務 在工作量比較下的情況下 使用linux 下的crond 使用定制計劃任務 * * *
集合框架相關接口概述(一)
ren array 方法 klist link map trees nth 框架 List VS Set List 是有序的可重復的, Set 是無序的不可重復的。 ArrayList VS LinkList ArrayList : 底層實現是數組,所以易查詢難存儲,
windows環境下搭建Java開發環境(一):jdk安裝和配置
變量 win jns jdk安裝 分享 tool 直接 www. 技術 一、資源下載 官網:http://www.oracle.com/technetwork/java/javase/downloads/index.html 本人安裝的是jdk1.8,百度雲資源:鏈
環境的搭建--PWN入門系列(一)
python2.7 com 是我 analysis strong nal 64bit amd ... 前言:作為一個偏前端開發的小白,入門信息安全也有兩年了。這兩年來一直被各種表哥血虐...... 最近暑假終於有時間專心水群了,這一個系列筆記是我這半個月來的pwn水題總結
函數計算搭建 Serverless Web 應用(一)- HTTP 觸發器
選擇 文件 process 程序 例如 函數計算 -o 同時 時代 摘要: Web 應用(Serverless web backend) 是函數計算很重要的一個使用場景。相比於傳統的在服務器上搭建 web 應用,函數計算無需您管理服務器等基礎設施,只需編寫代碼並上傳,函數計
2018 - Python 3.7 爬蟲之 Scrapy 框架的安裝及配置(一)
一,安裝 Python3.7 二,安裝 pip 三,安裝 pywin32 四,安裝 pyOpenSSL 五,安裝 lxml 六,安裝 zope.interface 七,安裝 twisted 八,安裝 Scrapy 九,一鍵升級所有庫,Python 3.7親測可用,建立
QT框架下的文字操作(一)
簡介 檔案操作作為軟體開發中不可或缺的一環,將其獨立出來並形成一個模組就顯得十分必要。這樣不僅易於維護管理,而且易於在專案中整合。 文字型別 常用的文字型別包括: csv檔案 dbf 檔案 excel 檔案 ini 檔案 json 檔案