Rxjava、Retrofit返回json數據解析異常處理

每個App都避免不了要進行網絡請求,從最開始的用谷歌封裝的volley到再到android-async-http再到OKHttpUtils再到現在的RetrofitRxJava,從我自己用后的體驗來看,用了retrofit和RxJava真的回不去了,回不去了,不去了,去了,了...(哈哈,本來還想分析下這四個的區別,網上這樣的文章很多,我就沒必要多添亂了-.-)。不多逼逼,下面開始正文。

RxJava的學習資料

RxJava javadoc

ReactiveX文檔中文翻譯

1、Rxjava和Retrofit依賴導入:

compile 'io.reactivex:rxandroid:1.2.0'                       //Rxjava專門針對anroid封裝的RxAndroid
compile 'io.reactivex:rxjava:1.1.5'
compile 'com.squareup.retrofit2:retrofit:2.0.2' //retrofit
compile 'com.squareup.retrofit2:converter-gson:2.0.2' //gson converter
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' //Retrofit專門為Rxjava封裝的適配器
compile 'com.google.code.gson:gson:2.6.2' //Gson
compile 'com.squareup.okhttp3:logging-interceptor:3.1.2' //打印網絡請求的log日志

2、網絡請求基類的配置

建立一個工廠類

public class ServiceFactory {

private final Gson mGsonDateFormat;


public ServiceFactory(){
mGsonDateFormat = new GsonBuilder()
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create();
}


private static class SingletonHolder{
private static final ServiceFactory INSTANCE = new ServiceFactory();
}


public static ServiceFactory getInstance(){
return SingletonHolder.INSTANCE;
}


public <S> S createService(Class<S> serviceClass){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.BASE_URL) //Retrofit2 base url 必須是這種格式的:http://xxx.xxx/
.client(getOkHttpClient())

--------------------------添加Gson工廠變換器也就是不用管數據解析-----------------------------------
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(serviceClass);

}

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
//打印retrofit日志
Log.i("RetrofitLog","retrofitBack ======================= "+message);
}
});


private static final long DEFAULT_TIMEOUT = 10;
private OkHttpClient getOkHttpClient(){
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//定制OkHttp
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
builder.writeTimeout(DEFAULT_TIMEOUT,TimeUnit.SECONDS);
builder.addInterceptor(loggingInterceptor);
//設置緩存
File httpCacheDirectory = new File(SDCardUtils.getRootDirectoryPath(),"這里是你的網絡緩存存放的地址");
builder.cache(new Cache(httpCacheDirectory,10*1024*1024));

return builder.build();
}

}

好了 下一步 我們要建一個網絡請求的基類,一般網絡請求返回的數據最外層的根式就是 code msg result,可變的就是result,所以我們把result的類型定義為一個泛型的

public class HttpResult<T> extends BaseEntity {
public int code;
private boolean isSuccess;
private T result;
private String msg;

public void setMsg(String msg) {
this.msg = msg;
}


public T getResult() {
return result;
}


public void setResult(T result) {
this.result = result;

}

public boolean isSuccess() {
return code == 200;
}

public int getCode() {
return code;
}
}

既然我們的網絡請求是rxjava配合retrofit 下面就定義我們的網絡請求訂閱subscriber

public abstract class HttpResultSubscriber<T> extends Subscriber<HttpResult<T>> {
@Override
public void onNext(HttpResult<T> t) {
if (t.isSuccess()) {
onSuccess(t.getResult());
} else {
_onError(t.getMsg().getCode());
}
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {
e.printStackTrace();
//在這里做全局的錯誤處理
if (e instanceof ConnectException ||
e instanceof SocketTimeoutException ||
e instanceof TimeoutException) {
//網絡錯誤
_onError(-9999);
}
}


public abstract void onSuccess(T t);

public abstract void _onError(int status);
}

OK我們的網絡請求基類已經完成啦!下面開始我們的網絡請求

首先我們定義一個接口(以登錄為例):

public interface LoginService {

//這個例子是post為例,如果想要了解其他的網絡請求,請點擊文章開始出的retrofit鏈接
@FormUrlEncoded
@POST(Constant.LOGIN_URL) 這里是你的登錄url

//可以看到我們的登錄返回的是一個Observable,它里面包含的使我們的網絡請求返回的實體基類,

//而我們實體基類的result現在就是UserInfoEntity
Observable<HttpResult<UserInfoEntity>> login(@Field("mobile") String phone,
@Field("password") String pwd);
}

現在開始我們的網絡請求啦

public void login(String phone, String pwd) {
ServiceFactory.getInstance()
.createService(LoginService.class)
.login(phone,pwd)
.compose(TransformUtils.<HttpResult<UserInfoEntity>>defaultSchedulers())
.subscribe(new HttpResultSubscriber<UserInfoEntity>() {
@Override
public void onSuccess(UserInfoEntity userInfoEntity) {
//這是網絡請求陳宮的回調
}

@Override
public void _onError(int status) {
//這是失敗的回調 根據status做具體的操作
}
});
}

你以為這樣就行了?,? 這樣子確實沒毛病,確實已經妥妥的了。可是,可是,事與愿違啊!!!

3、具體解決辦法

一般情況這是我們的返回json格式:

{
"code":200,
"msg":"成功",
"result":{}
}

我們剛才定義登錄接口的時候 返回的實體基類例傳入的是UserInfoEntity? 這確實是沒問題的 可是你們加入登錄失敗的時候返回的json數據格式是這樣的怎么辦?

{
"code":300,
"msg":"成功",
"result":[]
}

失敗的時候返回的實體又是一個數組,這樣子就會抱一個json解析異常拿不多失敗的狀態碼和提示信息

OK其實我們的網絡請求已經完成90%了,剩下的就是不重要的失敗的時候回調了。

方法一:(這是在后臺兄弟好說話,而且不打人的情況下...當然這種好人,還是有的,不過這不是我們今天要講的重點)

我們可以讓后臺返回的json數據中的result永遠是個數組。

{
"code":300,
"msg":"成功",
"result":[]
}

方法二:

??????? 首先給大家看一個圖片

這就是我們要下手的地方

上面我們添加的工廠變換器是導入的依賴 compile 'com.squareup.retrofit2:converter-gson:2.0.2' 這個提供的,

那可能有人要問了,那我們不用這個用哪個啊,不著急,不著急。還好retrofit是支持自定義的ConverterFactory的

下面我們就開始我們的自定義征程吧。

---------------------------------------------------------華麗麗的分割線-----------------------------------------------

1、自定義Gson響應體變換器


public class GsonResponseBodyConverter<T> implements Converter<ResponseBody,T>{
private final Gson gson;
private final Type type;


public GsonResponseBodyConverter(Gson gson,Type type){
this.gson = gson;
this.type = type;
}
@Override
public T convert(ResponseBody value) throws IOException {

String response = value.string();
//先將返回的json數據解析到Response中,如果code==200,則解析到我們的實體基類中,否則拋異常
Response httpResult = gson.fromJson(response, Response.class);
if (httpResult.getCode()==200){
//200的時候就直接解析,不可能出現解析異常。因為我們實體基類中傳入的泛型,就是數據成功時候的格式
return gson.fromJson(response,type);
}else {
ErrorResponse errorResponse = gson.fromJson(response,ErrorResponse.class);
//拋一個自定義ResultException 傳入失敗時候的狀態碼,和信息
throw new ResultException(errorResponse.getCode(),errorResponse.getMsg());
}
}
}

2、自定義一個響應變換工廠 繼承自 retrofit的 converter.Factory

public class ResponseConverterFactory extends Converter.Factory {

public static ResponseConverterFactory create() {
return create(new Gson());
}


public static ResponseConverterFactory create(Gson gson) {
return new ResponseConverterFactory(gson);
}

private final Gson gson;

private ResponseConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
//返回我們自定義的Gson響應體變換器
return new GsonResponseBodyConverter<>(gson, type);
}

@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
//返回我們自定義的Gson響應體變換器
return new GsonResponseBodyConverter<>(gson,type);
}
}


//然后將上面的GsonConverterFactory.create()替換成我們自定義的ResponseConverterFactory.create()

然后將上面的GsonConverterFactory.create() 替換成我們自定義的ResponseConverterFactory.create()。

public <S> S createService(Class<S> serviceClass){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.client(getOkHttpClient())
//.addConverterFactory(GsonConverterFactory.create())
//然后將上面的GsonConverterFactory.create()替換成我們自定義的ResponseConverterFactory.create()
.addConverterFactory(ResponseConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(serviceClass);

}

再然后,最后一個然后啦(-.-)

在我們的自定義的Rxjava訂閱者 subscriber中的onError()中加入我們剛才定義的ResultException。

@Override
public void onError(Throwable e) {
e.printStackTrace();
//在這里做全局的錯誤處理
if (e instanceof ConnectException ||
e instanceof SocketTimeoutException ||
e instanceof TimeoutException) {
//網絡錯誤
_onError(-9999);
} else if (e instanceof ResultException) {
//自定義的ResultException
//由于返回200,300返回格式不統一的問題,自定義GsonResponseBodyConverter凡是300的直接拋異常
_onError(((ResultException) e).getErrCode());
System.out.println("---------errorCode------->"+((ResultException) e).getErrCode());
}
}

這次是真的完成了我們的json數據解析異常的處理,其實我們的解決辦法是解析了兩次,第一次解析的時候我們的Response中只有只是解析了最外層的 code 和 msg? ,result中的是沒有解析的。response中的code==200,直接將數據解析到我們的實體基類中。如果code!=200時,直接拋自定義的異常,直接會回調到subscriber中的onError()中。雖然進行了兩次解析,但是第一次只是解析了code,和msg 對于效率的影響其實并不大,在功能實現的基礎上一點點效率的影響(而且這個影響是微乎其微的-.-)其實無傷大雅的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容