Retrofit_Rxjava

注:此文轉(zhuǎn)載自:作者:JYcoder?的文章:http://www.itdecent.cn/u/2ebe42698573。

如有侵犯,請通知我刪除,謝謝。

安卓基礎(chǔ)開發(fā)庫,讓開發(fā)簡單點(diǎn)。

DevRing & Demo地址https://github.com/LJYcoder/DevRing

學(xué)習(xí)/參考地址:

Retrofit:

整體教程?http://blog.csdn.net/jdsjlzx/article/details/52015347

文件上傳?http://blog.csdn.net/jdsjlzx/article/details/52246114

文件下載?http://www.itdecent.cn/p/060d55fc1c82

Https請求?http://blog.csdn.net/dd864140130/article/details/52625666

異常處理?http://blog.csdn.net/mq2553299/article/details/70244529

失敗重試?http://blog.csdn.net/johnny901114/article/details/51539708

生命周期?http://android.jobbole.com/83847?|?http://mp.weixin.qq.com/s/eedFDMIQe30rQmryLeif_Q

RxJava:

整體教程(RxJava1)?https://gank.io/post/560e15be2dca930e00da1083

整體教程(RxJava2)?https://mp.weixin.qq.com/s/UAEgdC2EtqSpEqvog0aoZQ

操作符?https://zhuanlan.zhihu.com/p/21926591

使用場景?http://blog.csdn.net/theone10211024/article/details/50435325

1.x與2.x區(qū)別?http://blog.csdn.net/qq_35064774/article/details/53045298

前言

Retrofit是目前主流的網(wǎng)絡(luò)請求框架,功能強(qiáng)大,操作便捷。

RxJava是實(shí)現(xiàn)異步操作的庫??稍诰€程間快速切換,同時提供許多操作符,使一些復(fù)雜的操作代碼變得清晰有條理。

兩者結(jié)合使用后,使得網(wǎng)絡(luò)請求更加簡潔,尤其在嵌套請求等特殊場景大有作為。

本文側(cè)重于介紹Retrofit網(wǎng)絡(luò)請求,以及它是如何結(jié)合RxJava使用的。還沒了解過RxJava的建議先到上面貼出的參考地址學(xué)習(xí),以便更好明白兩者結(jié)合的過程。

文章篇幅較長,因?yàn)橄MM可能涵蓋常用、實(shí)用的模塊。

demo以及文章中的RxJava部分,已從1.x更新到2.x。

介紹

下面通過配置,請求,異常處理,生命周期管理,失敗重試,監(jiān)聽進(jìn)度,封裝,混淆這幾個部分來介紹。

1. 配置

1.1 添加依賴

1.2 開啟Log日志

開啟后,則可以在Log日志中看到網(wǎng)絡(luò)請求相關(guān)的信息了,如請求地址,請求狀態(tài)碼,返回的結(jié)果等。

Log日志截圖

1.3 開啟Gson轉(zhuǎn)換

開啟后,會自動把請求返回的結(jié)果(json字符串)自動轉(zhuǎn)化成與其結(jié)構(gòu)相符的實(shí)體。

1.4 采用Rxjava

1.5 設(shè)置基礎(chǔ)請求路徑BaseUrl

Retrofit.BuilderretrofitBuilder=newRetrofit.Builder();//服務(wù)器地址,基礎(chǔ)請求路徑,最好以"/"結(jié)尾retrofitBuilder.baseUrl("https://api.douban.com/");

1.6 設(shè)置請求超時

OkHttpClient.BuilderokHttpClientBuilder=newOkHttpClient.Builder();//設(shè)置請求超時時長為15秒okHttpClientBuilder.connectTimeout(15,TimeUnit.SECONDS);

1.7 設(shè)置緩存

1.8 設(shè)置header

可統(tǒng)一設(shè)置

InterceptorheaderInterceptor=newInterceptor(){@OverridepublicResponseintercept(Chainchain)throwsIOException{RequestoriginalRequest=chain.request();Request.Builderbuilder=originalRequest.newBuilder();//設(shè)置具體的header內(nèi)容builder.header("timestamp",System.currentTimeMillis()+"");Request.BuilderrequestBuilder=builder.method(originalRequest.method(),originalRequest.body());Requestrequest=requestBuilder.build();returnchain.proceed(request);}};//設(shè)置統(tǒng)一的headerOkHttpClient.BuilderokHttpClientBuilder=newOkHttpClient.Builder();okHttpClientBuilder.addInterceptor(getHeaderInterceptor());

也可在請求方法中單獨(dú)設(shè)置

@Headers("Cache-Control: max-age=120")@GET("請求地址")Observable<HttpResult>getInfo();或者@GET("請求地址")Observable<HttpResult>getInfo(@Header("token")Stringtoken);

1.9 設(shè)置https訪問

現(xiàn)在不少服務(wù)器接口采用了https的形式,所以有時就需要設(shè)置https訪問。

下面列舉“客戶端內(nèi)置證書”時的配置方法,其他方式請參考?http://blog.csdn.net/dd864140130/article/details/52625666

//設(shè)置https訪問(驗(yàn)證證書,請把服務(wù)器給的證書文件放在R.raw文件夾下)okHttpClientBuilder.sslSocketFactory(getSSLSocketFactory(mContext,newint[]{R.raw.tomcat}));okHttpClientBuilder.hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

getSSLSocketFactory()方法如下:

//設(shè)置https證書protectedstaticSSLSocketFactorygetSSLSocketFactory(Contextcontext,int[]certificates){if(context==null){thrownewNullPointerException("context == null");}//CertificateFactory用來證書生成CertificateFactorycertificateFactory;try{certificateFactory=CertificateFactory.getInstance("X.509");//Create a KeyStore containing our trusted CAsKeyStorekeyStore=KeyStore.getInstance(KeyStore.getDefaultType());keyStore.load(null,null);for(inti=0;i<certificates.length;i++){//讀取本地證書InputStreamis=context.getResources().openRawResource(certificates[i]);keyStore.setCertificateEntry(String.valueOf(i),certificateFactory.generateCertificate(is));if(is!=null){is.close();}}//Create a TrustManager that trusts the CAs in our keyStoreTrustManagerFactorytrustManagerFactory=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());trustManagerFactory.init(keyStore);//Create an SSLContext that uses our TrustManagerSSLContextsslContext=SSLContext.getInstance("TLS");sslContext.init(null,trustManagerFactory.getTrustManagers(),newSecureRandom());returnsslContext.getSocketFactory();}catch(Exceptione){}returnnull;}

1.10 綜合前面的配置

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();//設(shè)置請求超時時長okHttpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);//啟用Log日志okHttpClientBuilder.addInterceptor(getHttpLoggingInterceptor());//設(shè)置緩存方式、時長、地址okHttpClientBuilder.addNetworkInterceptor(getCacheInterceptor());okHttpClientBuilder.addInterceptor(getCacheInterceptor());okHttpClientBuilder.cache(getCache());//設(shè)置https訪問(驗(yàn)證證書)okHttpClientBuilder.sslSocketFactory(getSSLSocketFactory(mContext, new int[]{R.raw.tomcat}));okHttpClientBuilder.hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);//設(shè)置統(tǒng)一的headerokHttpClientBuilder.addInterceptor(getHeaderInterceptor());Retrofit retrofit = new Retrofit.Builder()? ? ? ? ? //服務(wù)器地址? ? ? ? ? .baseUrl(UrlConstants.HOST_SITE_HTTPS)? ? ? ? ? //配置轉(zhuǎn)化庫,采用Gson? ? ? ? ? .addConverterFactory(GsonConverterFactory.create())? ? ? ? ? //配置回調(diào)庫,采用RxJava? ? ? ? ? .addCallAdapterFactory(RxJava2CallAdapterFactory.create())? ? ? ? ? //設(shè)置OKHttpClient為網(wǎng)絡(luò)客戶端? ? ? ? ? .client(okHttpClientBuilder.build()).build();

配置后得到的retrofit變量用于后面發(fā)起請求。

2. 請求

2.1 創(chuàng)建API接口

定義一個接口,在其中添加具體的網(wǎng)絡(luò)請求方法。

請求方法的格式大致如下:

@其他聲明

@請求方式("請求地址")

Observable<請求返回的實(shí)體> 請求方法名(請求參數(shù));

或者

@其他聲明

@請求方式

Observable<請求返回的實(shí)體> 請求方法名(@Url String 請求地址,請求參數(shù));

第一種格式中的請求地址,填寫基礎(chǔ)請求路徑baseUrl后續(xù)的部分即可,當(dāng)然填寫完整地址也是可以的。

第二種格式中的請求地址,需填寫完整的地址。

下面列舉Get請求、Post請求文件上傳、文件下載的接口定義。

其中HttpResult是自定義的、與后臺返回的json數(shù)據(jù)結(jié)構(gòu)相符的實(shí)體。

Get請求

請求參數(shù)逐個傳入

@GET("v2/movie/in_theaters")Observable<HttpResult>getPlayingMovie(@Query("start")intstart,@Query("count")intcount);

請求參數(shù)一次性傳入(通過Map來存放key-value)

@GET("v2/movie/in_theaters")Observable<HttpResult>getPlayingMovie(@QueryMapMap<String,String>map);

以上兩種方式,請求參數(shù)是以“?key=vale%key=value...”方式拼接到地址后面的,假如你需要的是以"/value"的方式拼接到地址后面(restful模式?),那么可以通過@Path注解來實(shí)現(xiàn):

@GET("v2/movie/in_theaters/{start}/{count}")Observable<HttpResult>getPlayingMovie(@Path("start")intstart,@Path("count")intcount);

Post請求

請求參數(shù)逐個傳入

@FormUrlEncoded@POST("請求地址")Observable<HttpResult>getInfo(@Field("token")Stringtoken,@Field("id")intid);

請求參數(shù)一次性傳入(通過Map來存放參數(shù)名和參數(shù)值)

@FormUrlEncoded@POST("請求地址")Observable<HttpResult>getInfo(@FieldMapMap<String,String>map);

上傳文本+文件

1)上傳單個文本和單個文件

@Multipart@POST("請求地址")Observable<HttpResult>upLoadTextAndFile(@Part("textKey")RequestBodytextBody,@Part("fileKey\"; filename=\"test.png")RequestBodyfileBody);

第一個參數(shù)用于傳文本,

--- @Part("textKey")中的"textKey"為文本參數(shù)的參數(shù)名。

--- RequestBody textBody為文本參數(shù)的參數(shù)值,生成方式如下:

RequestBody textBody = RequestBody.create(MediaType.parse("text/plain"), text);

第二個參數(shù)用于傳文件,

--- @Part("fileKey"; filename="test.png")

其中的"fileKey"為文件參數(shù)的參數(shù)名(由服務(wù)器后臺提供)

其中的"test.png"一般是指你希望保存在服務(wù)器的文件名字,傳入File.getName()即可

--- RequestBody fileBody為文件參數(shù)的參數(shù)值,生成方法如下:

RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);

(這里文件類型以png圖片為例,所以MediaType為"image/png",

不同文件類型對應(yīng)不同的type,具體請參考http://tool.oschina.net/commons

2)上傳多個文本和多個文件(通過Map來傳入)

@Multipart@POST("")Observable<HttpResult>upLoadTextAndFiles(@PartMapMap<String,RequestBody>textBodyMap,@PartMapMap<String,RequestBody>fileBodyMap);

第一個參數(shù)用于傳文本,

Map的key為String,內(nèi)容請參考上方“上傳文本和單個文件”中@Part()里的值。

Map的value值為RequestBody,內(nèi)容請參考上方“上傳文本和單個文件”中RequestBody的生成。

第二個參數(shù)用于傳文件,

Map的key為String,內(nèi)容請參考上方“上傳文本和單個文件”中@Part()里的值。

Map的value值為RequestBody,內(nèi)容請參考上方“上傳文本和單個文件”中RequestBody的生成。

3)另外補(bǔ)充多一種上傳方式(2018/07/16),以上傳多個文本和多個文件為例

@POST("")Observable<HttpResult>upLoadTextAndFiles(@BodyMultipartBodymultipartBody);

MultipartBody 的生成方式如下:

MultipartBody.Builderbuilder=newMultipartBody.Builder();//文本部分builder.addFormDataPart("fromType","1");builder.addFormDataPart("content","意見反饋內(nèi)容");builder.addFormDataPart("phone","17700000066");//文件部分RequestBodyrequestBody=RequestBody.create(MediaType.parse("image/jpg"),file);builder.addFormDataPart("image",file.getName(),requestBody);// “image”為文件參數(shù)的參數(shù)名(由服務(wù)器后臺提供)builder.setType(MultipartBody.FORM);MultipartBodymultipartBody=builder.build();

下載文件

//下載大文件時,請加上@Streaming,否則容易出現(xiàn)IO異常@Streaming@GET("請求地址")Observable<ResponseBody>downloadFile();//ResponseBody是Retrofit提供的返回實(shí)體,要下載的文件數(shù)據(jù)將包含在其中

(目前使用@Streaming進(jìn)行下載的話,需添加Log攔截器(且LEVEL為BODY)才不會報(bào)錯,但是網(wǎng)上又說添加Log攔截器后進(jìn)行下載容易OOM,

所以這一塊還很懵,具體原因也不清楚,有知道的朋友可以告訴下我)

2.2 發(fā)起請求

完成前面說的的配置和請求接口的定義后,就可以發(fā)起請求了。

//構(gòu)建Retrofit類Retrofitretrofit=newRetrofit.Builder()//服務(wù)器地址.baseUrl("https://api.douban.com/")//配置轉(zhuǎn)化庫,采用Gson.addConverterFactory(GsonConverterFactory.create())//配置回調(diào)庫,采用RxJava.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//設(shè)置OKHttpClient為網(wǎng)絡(luò)客戶端.client(okHttpClientBuilder.build()).build();//獲取API接口mApiService=retrofit.create(ApiService.class);//調(diào)用之前定義好的請求方法,得到ObservableObservableobservable=mApiService.xxx();

普通請求、上傳請求:

//通過Observable發(fā)起請求observable.subscribeOn(Schedulers.io())//指定網(wǎng)絡(luò)請求在io后臺線程中進(jìn)行.observeOn(AndroidSchedulers.mainThread())//指定observer回調(diào)在UI主線程中進(jìn)行.subscribe(observer);//發(fā)起請求,請求的結(jié)果會回調(diào)到訂閱者observer中

下載請求:

//通過Observable發(fā)起請求observable.subscribeOn(Schedulers.io())//指定網(wǎng)絡(luò)請求在io后臺線程中進(jìn)行.observeOn(Schedulers.io())//指定doOnNext的操作在io后臺線程進(jìn)行.doOnNext(newConsumer<ResponseBody>(){//doOnNext里的方法執(zhí)行完畢,observer里的onNext、onError等方法才會執(zhí)行。@Overridepublicvoidaccept(ResponseBodybody)throwsException{//下載文件,保存到本地//通過body.byteStream()可以得到輸入流,然后就是常規(guī)的IO讀寫保存了。...}}).observeOn(AndroidSchedulers.mainThread())//指定observer回調(diào)在UI主線程中進(jìn)行.subscribe(observer);//發(fā)起請求,請求的結(jié)果先回調(diào)到doOnNext進(jìn)行處理,再回調(diào)到observer中

3. 異常處理

使用Retrofit+RxJava發(fā)起請求后,如果請求失敗,會回調(diào)observer中的onError方法,該方法的參數(shù)為Throwable,并沒能反饋更直接清楚的異常信息給我們,所以有必要對Throwable異常進(jìn)行處理轉(zhuǎn)換。

//observer封裝類中的代碼@OverridepublicvoidonError(Throwablethrowable){if(throwableinstanceofException){onError(ThrowableHandler.handleThrowable(throwable));}else{onError(newHttpThrowable(HttpThrowable.UNKNOWN,"未知錯誤",throwable));}}//應(yīng)用中具體實(shí)現(xiàn)的是下面這個onError方法publicabstractvoidonError(HttpThrowablehttpThrowable);

publicclassThrowableHandler{....publicstaticHttpThrowablehandleThrowable(Throwablethrowable){if(throwableinstanceofHttpException){returnnewHttpThrowable(HttpThrowable.HTTP_ERROR,"網(wǎng)絡(luò)(協(xié)議)異常",throwable);}elseif(throwableinstanceofJsonParseException||throwableinstanceofJSONException||throwableinstanceofParseException){returnnewHttpThrowable(HttpThrowable.PARSE_ERROR,"數(shù)據(jù)解析異常",throwable);}elseif(throwableinstanceofUnknownHostException){returnnewHttpThrowable(HttpThrowable.NO_NET_ERROR,"網(wǎng)絡(luò)連接失敗,請稍后重試",throwable);}elseif(throwableinstanceofSocketTimeoutException){returnnewHttpThrowable(HttpThrowable.TIME_OUT_ERROR,"連接超時",throwable);}elseif(throwableinstanceofConnectException){returnnewHttpThrowable(HttpThrowable.CONNECT_ERROR,"連接異常",throwable);}elseif(throwableinstanceofjavax.net.ssl.SSLHandshakeException){returnnewHttpThrowable(HttpThrowable.SSL_ERROR,"證書驗(yàn)證失敗",throwable);}else{returnnewHttpThrowable(HttpThrowable.UNKNOWN,throwable.getMessage(),throwable);}}}

publicclassHttpThrowableextendsException{publicinterrorType;publicStringmessage;publicThrowablethrowable;/**

? ? * 未知錯誤

? ? */publicstaticfinalintUNKNOWN=1000;/**

? ? * 解析錯誤

? ? */publicstaticfinalintPARSE_ERROR=1001;/**

? ? * 連接錯誤

? ? */publicstaticfinalintCONNECT_ERROR=1002;/**

? ? * DNS解析失?。o網(wǎng)絡(luò))

? ? */publicstaticfinalintNO_NET_ERROR=1003;/**

? ? * 連接超時錯誤

? ? */publicstaticfinalintTIME_OUT_ERROR=1004;/**

? ? * 網(wǎng)絡(luò)(協(xié)議)錯誤

? ? */publicstaticfinalintHTTP_ERROR=1005;/**

? ? * 證書錯誤

? ? */publicstaticfinalintSSL_ERROR=1006;publicHttpThrowable(interrorType,Stringmessage,Throwablethrowable){super(throwable);this.errorType=errorType;this.message=message;this.throwable=throwable;}}

處理后得到ResponeThrowable,里面包含了異常碼code?和?異常描述信息message,這樣就可以方便地知道請求失敗的原因了。

4. 生命周期管理

4.1 意義

如果頁面發(fā)起了網(wǎng)絡(luò)請求并且在請求結(jié)果返回前就已經(jīng)銷毀了,那么我們應(yīng)該在它銷毀時把相關(guān)的請求終止。一方面是為了停止無意義的請求,另一方面是為了避免可能帶來的內(nèi)存泄漏。

強(qiáng)大的RxJava可以幫助我們實(shí)現(xiàn)這一需求。下面通過?takeUntil、PublishSubject、綜合兩者進(jìn)行控制?三個部分來講解如何實(shí)現(xiàn)。

4.2 takeUntil

RxJava中提供了許多操作符,這里我們需要使用takeUntil操作符。

ObservableA.takeUntil(ObservableB) 的作用是:

監(jiān)視ObservableB,當(dāng)它發(fā)射內(nèi)容時,則停止ObservableA的發(fā)射并將其終止。

下面通過示意圖和示例代碼來加深了解,參考自https://zhuanlan.zhihu.com/p/21966621

示意圖:

takeUntil

示例代碼:

//下面的Observable.interval( x, TimeUnit.MILLISECONDS) 表示每隔x毫秒發(fā)射一個long類型數(shù)字,數(shù)字從0開始,每次遞增1Observable<Long>observableA=Observable.interval(300,TimeUnit.MILLISECONDS);Observable<Long>observableB=Observable.interval(800,TimeUnit.MILLISECONDS);observableA.takeUntil(observableB).subscribe(newObserver<Long>(){//...onComplete...//...onError...@OverridepublicvoidonNext(LongaLong){System.out.println(aLong);}});

輸出結(jié)果為01

示例代碼大意:

ObservableA每隔300ms發(fā)射一個數(shù)字(并打印出發(fā)射的數(shù)字),ObservableB每隔800ms發(fā)射一個數(shù)字。

由于ObservableB在800ms時發(fā)射了內(nèi)容,終止了ObservableA的發(fā)射,所以O(shè)bservableA最后只能發(fā)射0,1兩個數(shù)字。

因此,我們可以利用takeUntil這一特性,讓ObservableA負(fù)責(zé)網(wǎng)絡(luò)請求,讓ObservableB負(fù)責(zé)在頁面銷毀時發(fā)射事件,從而終止ObservableA(網(wǎng)絡(luò)請求)。

4.3 PublishSubject

上面提到了需要一個ObservableB來負(fù)責(zé)在頁面銷毀時發(fā)射事件,PublishSubject就能充當(dāng)這一角色。

閱讀PublishSubject的源碼可以發(fā)現(xiàn),它既可充當(dāng)Observable,擁有subscribe()等方法;也可充當(dāng)Observer(Subscriber),擁有onNext(),onError等方法。

它的特點(diǎn)是進(jìn)行subscribe()訂閱后,并不立即發(fā)射事件,而是允許我們在認(rèn)為合適的時機(jī)通過調(diào)用onNext(),onError(),onCompleted()來發(fā)射事件。

所以,我們需在Activity或Fragment的生命周期onDestroy()中通過PublishSubject來發(fā)射事件

//一般以下代碼寫在Activity或Fragment的基類中。PublishSubject<LifeCycleEvent>lifecycleSubject=PublishSubject.create();//用于提供lifecycleSubject到RetrofitUtil中。publicPublishSubject<LifeCycleEvent>getLifeSubject(){returnlifecycleSubject;}//一般是在onDestroy()時發(fā)射事件終止請求,當(dāng)然你也可以根據(jù)需求在生命周期的其他狀態(tài)中發(fā)射。@OverrideprotectedvoidonDestroy(){//publishSubject發(fā)射事件lifecycleSubject.onNext(LifeCycleEvent.DESTROY);super.onDestroy();}

4.4 進(jìn)行控制

了解 takeUntil 和 PublishSubject 后,就可以綜合兩者來實(shí)現(xiàn)生命周期的控制了。

//省略Retrofit和ApiService的構(gòu)造過程......//得到負(fù)責(zé)網(wǎng)絡(luò)請求的ObservableObservableobservableNet=mApiService.getCommingMovie(count);//得到負(fù)責(zé)在頁面銷毀時發(fā)射事件的ObservableObservable<LifeCycleEvent>observableLife=lifecycleSubject.filter(newPredicate<LifeCycleEvent>(){@Overridepublicbooleantest(LifeCycleEventlifeCycleEvent)throwsException{//當(dāng)生命周期為DESTROY狀態(tài)時,發(fā)射事件returnlifeCycleEvent.equals(LifeCycleEvent.DESTROY);}}).take(1);//通過takeUntil將兩個Observable聯(lián)系在一起,實(shí)現(xiàn)生命周期的控制observableNet.takeUntil(observableLife).subscribeOn(Schedulers.io())//設(shè)置網(wǎng)絡(luò)請求在io后臺線程中進(jìn)行.observeOn(AndroidSchedulers.mainThread())//設(shè)置請求后的回調(diào)在UI主線程中進(jìn)行.subscribe(observer);//發(fā)起請求,請求的回調(diào)結(jié)果會傳到訂閱者observer中

還有其他方式可以實(shí)現(xiàn)生命周期的控制,具體實(shí)現(xiàn)可到以下地址查看:

http://www.itdecent.cn/p/d62962243c33

http://mp.weixin.qq.com/s/eedFDMIQe30rQmryLeif_Q

5.失敗重試機(jī)制

有時候用戶的網(wǎng)絡(luò)比較不穩(wěn)定,出現(xiàn)了請求失敗的情況。這時我們不一定就要直接反饋用戶請求失敗,而可以在失敗后嘗試重新請求,說不定這時網(wǎng)絡(luò)恢復(fù)穩(wěn)定請求成功了呢?! 這樣或許可以提高用戶體驗(yàn)。

下面介紹如何設(shè)置某個請求在失敗后自動進(jìn)行重試,以及設(shè)置重試的次數(shù)、延遲重試的時間。

先上代碼:

ObservableobservableNet=mApiService.getCommingMovie(count);observableNet.retryWhen(newRetryFunction(3,3))//加入失敗重試機(jī)制(失敗后延遲3秒開始重試,重試3次).takeUntil(observableLife)//生命周期控制.subscribeOn(Schedulers.io())//設(shè)置網(wǎng)絡(luò)請求在io后臺線程中進(jìn)行.observeOn(AndroidSchedulers.mainThread())//設(shè)置請求后的回調(diào)在UI主線程中進(jìn)行.subscribe(observer);//發(fā)起請求

//請求失敗重試機(jī)制publicstaticclassRetryFunctionimplementsFunction<Observable<Throwable>,ObservableSource<?>>{privateintretryDelaySeconds;//延遲重試的時間privateintretryCount;//記錄當(dāng)前重試次數(shù)privateintretryCountMax;//最大重試次數(shù)publicRetryFunction(intretryDelaySeconds,intretryCountMax){this.retryDelaySeconds=retryDelaySeconds;this.retryCountMax=retryCountMax;}@OverridepublicObservableSource<?>apply(Observable<Throwable>throwableObservable)throwsException{//方案一:使用全局變量來控制重試次數(shù),重試3次后不再重試,通過代碼顯式回調(diào)onError結(jié)束請求returnthrowableObservable.flatMap(newFunction<Throwable,ObservableSource<?>>(){@OverridepublicObservableSource<?>apply(Throwablethrowable)throwsException{//如果失敗的原因是UnknownHostException(DNS解析失敗,當(dāng)前無網(wǎng)絡(luò)),則沒必要重試,直接回調(diào)error結(jié)束請求即可if(throwableinstanceofUnknownHostException){returnObservable.error(throwable);}//沒超過最大重試次數(shù)的話則進(jìn)行重試if(++retryCount<=retryCountMax){//延遲retryDelaySeconds后開始重試returnObservable.timer(retryDelaySeconds,TimeUnit.SECONDS);}returnObservable.error(throwable);}});//方案二:使用zip控制重試次數(shù),重試3次后不再重試(會隱式回調(diào)onComplete結(jié)束請求,但我需要的是回調(diào)onError,所以沒采用方案一)//? ? ? ? ? ? return Observable.zip(throwableObservable,Observable.range(1, retryCountMax),new BiFunction<Throwable, Integer, Throwable>() {//? ? ? ? ? ? ? ? @Override//? ? ? ? ? ? ? ? public Throwable apply(Throwable throwable, Integer integer) throws Exception {//? ? ? ? ? ? ? ? ? ? LogUtil.e("ljy",""+integer);//? ? ? ? ? ? ? ? ? ? return throwable;//? ? ? ? ? ? ? ? }//? ? ? ? ? ? }).flatMap(new Function<Throwable, ObservableSource<?>>() {//? ? ? ? ? ? ? ? @Override//? ? ? ? ? ? ? ? public ObservableSource<?> apply(Throwable throwable) throws Exception {//? ? ? ? ? ? ? ? ? ? if (throwable instanceof UnknownHostException) {//? ? ? ? ? ? ? ? ? ? ? ? return Observable.error(throwable);//? ? ? ? ? ? ? ? ? ? }//? ? ? ? ? ? ? ? ? ? return Observable.timer(retryDelaySeconds, TimeUnit.SECONDS);//? ? ? ? ? ? ? ? }//? ? ? ? ? ? });}}

分析:

通過observableNet.retryWhen(new RetryFunction(3,3))加入失敗重試機(jī)制,其參數(shù)RetryFunction中的apply方法會返回一個Observable,后面就稱它為ObservableRetry吧。

加入后,當(dāng)網(wǎng)絡(luò)請求失敗時,并不會直接回調(diào)observer中的onError,而是會先將失敗異常throwable作為ObservableRetry的事件源。如果ObservableRetry通過onNext發(fā)射了事件,則觸發(fā)重新請求,而如果ObservableRetry發(fā)射了onError/onComplete通知,則該請求正式結(jié)束。因此可以我們對apply方法中的throwableObservable進(jìn)行改造,然后返回一個合適的ObservableRetry來實(shí)現(xiàn)自己想要的重試效果。

代碼中對throwableObservable進(jìn)行了flatMap操作,目的是對其事件throwable的類型進(jìn)行判斷。如果為UnknownHostException類型,則表示無網(wǎng)絡(luò)DNS解析失敗,這時就沒必要進(jìn)行重試(都沒網(wǎng)絡(luò)還重試啥呀),直接通過Observable.error(throwable)結(jié)束該次請求。

然后通過全局變量 retryCount 和 retryCountMax 來控制重試的次數(shù)。重試retryCountMax次之后如果還是失敗,那就通過Observable.error(throwable)放棄重試并結(jié)束請求。

代碼中還有個方案二,與方案一的區(qū)別在于使用zip操作符來控制重試的次數(shù)。

了解過zip的應(yīng)該知道其產(chǎn)生的ObservableZip發(fā)射的事件總量,與組合成員中事件量少的一致。所以我們通過Observable.range(start, count)發(fā)射有限的事件,如range(1, 3)只發(fā)射"1","2","3"三個事件,從而限制了ObservableZip最終發(fā)射的事件總量不大于3,即重試的次數(shù)不超過3次。當(dāng)超過3次的時候,它會隱式地調(diào)用onComplete來結(jié)束該次請求(方案一是通過顯式地調(diào)用onError來結(jié)束請求,而我需要在observer的onError中反饋給用戶請求失敗,所以選擇了方案一)

6.監(jiān)聽進(jìn)度

這里只講下實(shí)現(xiàn)步驟思路,代碼太多就不放上來了,大家可以直接看DevRing/Demo里的代碼,基本參考自JessYan的ProgressManager庫

6.1 上傳進(jìn)度

自定義請求實(shí)體,繼承RequestBody重寫其幾個必要的方法。

其中監(jiān)聽上傳進(jìn)度主要是重寫其writeTo(BufferedSink sink)方法,從該方法中獲取數(shù)據(jù)總量以及已寫入請求實(shí)體的數(shù)據(jù)量,在這里通過回調(diào)傳遞相關(guān)進(jìn)度。

自定義攔截器,實(shí)現(xiàn)Interceptor的intercept(Chain chain)方法。

通過該方法將第1步定義的請求實(shí)體應(yīng)用到請求中。

添加攔截器到OkHttpClient中。

builder.addNetworkInterceptor(progressInterceptor);

6.2 下載進(jìn)度

思路和上傳進(jìn)度差不多

自定義響應(yīng)實(shí)體,繼承ResponseBody重寫其幾個必要的方法。

其中監(jiān)聽下載進(jìn)度主要是重寫其source(Source source)方法,從該方法中獲取數(shù)據(jù)總量以及已寫入響應(yīng)實(shí)體的數(shù)據(jù)量,在這里通過回調(diào)傳遞相關(guān)進(jìn)度。

自定義攔截器,實(shí)現(xiàn)Interceptor的intercept(Chain chain)方法。

通過該方法將第1步定義的響應(yīng)實(shí)體應(yīng)用到請求中。

添加攔截器到OkHttpClient中。

builder.addNetworkInterceptor(progressInterceptor);

7. 封裝

(2018.3.27:Demo已對封裝這一塊做了新的調(diào)整,詳情請看demo,但封裝的思路還是和下文差不多的)

封裝分為?初始化配置、統(tǒng)一轉(zhuǎn)換、請求結(jié)果封裝、請求回調(diào)(Observer)封裝?四個部分進(jìn)行。

7.1 初始化配置

publicRetrofitinitRetrofit(){OkHttpClient.BuilderokHttpClientBuilder=newOkHttpClient.Builder();//設(shè)置請求超時時長okHttpClientBuilder.connectTimeout(DEFAULT_TIMEOUT,TimeUnit.SECONDS);//啟用Log日志okHttpClientBuilder.addInterceptor(getHttpLoggingInterceptor());//設(shè)置緩存方式、時長、地址okHttpClientBuilder.addNetworkInterceptor(getCacheInterceptor());okHttpClientBuilder.addInterceptor(getCacheInterceptor());okHttpClientBuilder.cache(getCache());//設(shè)置https訪問(驗(yàn)證證書)okHttpClientBuilder.hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);//設(shè)置統(tǒng)一的headerokHttpClientBuilder.addInterceptor(getHeaderInterceptor());Retrofitretrofit=newRetrofit.Builder()//服務(wù)器地址.baseUrl(UrlConstants.HOST_SITE_HTTPS)//配置轉(zhuǎn)化庫,采用Gson.addConverterFactory(GsonConverterFactory.create())//配置回調(diào)庫,采用RxJava.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//設(shè)置OKHttpClient為網(wǎng)絡(luò)客戶端.client(okHttpClientBuilder.build()).build();returnretrofit;}

7.2 統(tǒng)一轉(zhuǎn)換

由于每次請求都要進(jìn)行線程切換以及生命周期的控制,頻繁地調(diào)用以下代碼

observable.takeUntil(lifecycleObservable).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());

因此可以使用compose方法對Observable進(jìn)行統(tǒng)一的轉(zhuǎn)換

//RetrofitUtil中的方法/**

* 對observable進(jìn)行統(tǒng)一轉(zhuǎn)換,并發(fā)起請求

*

* @param observable? ? ? ? 被訂閱者

* @param observer? ? ? ? ? 訂閱者

* @param event? ? ? ? ? ? ? 生命周期中的某一個狀態(tài),比如傳入DESTROY,則表示在進(jìn)入destroy狀態(tài)時?

*? ? ? ? ? ? ? ? ? ? ? ? ? lifecycleSubject會發(fā)射一個事件從而終止請求

* @param lifecycleSubject? 生命周期事件發(fā)射者

*/publicstaticvoidcomposeToSubscribe(Observableobservable,Observerobserver,LifeCycleEventevent,PublishSubject<LifeCycleEvent>lifecycleSubject){observable.compose(getTransformer(event,lifecycleSubject)).subscribe(observer);}/**

* 獲取統(tǒng)一轉(zhuǎn)換用的Transformer

*

* @param event? ? ? ? ? ? ? 生命周期中的某一個狀態(tài),比如傳入DESTROY,則表示在進(jìn)入destroy狀態(tài)時

*? ? ? ? ? ? ? ? ? ? ? ? ? ? lifecycleSubject會發(fā)射一個事件從而終止請求

* @param lifecycleSubject? ? 生命周期事件發(fā)射者

*/publicstatic<T>ObservableTransformer<T,T>getTransformer(finalLifeCycleEventevent,finalPublishSubject<LifeCycleEvent>lifecycleSubject){returnnewObservableTransformer(){@OverridepublicObservableSourceapply(Observableupstream){//當(dāng)lifecycleObservable發(fā)射事件時,終止操作。//統(tǒng)一在請求時切入io線程,回調(diào)后進(jìn)入ui線程//加入失敗重試機(jī)制(延遲3秒開始重試,重試3次)returnupstream.takeUntil(getLifeCycleObservable(event,lifecycleSubject)).retryWhen(newRetryFunction(3,3)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());}};}

7.3 封裝請求結(jié)果

服務(wù)器返回的請求結(jié)果,一般分為三個部分:請求結(jié)果的狀態(tài)值,請求結(jié)果的描述,返回的數(shù)據(jù)內(nèi)容。

{"status":1,"message":"success","data":{"name":"小明","sex":0,"age":10}}

其中status和message的類型是固定的,而data的類型不確定,所以data可以采用泛型表示

豆瓣接口返回的結(jié)構(gòu)比較特殊,并不是上面所說的那三部分。實(shí)際結(jié)構(gòu)根據(jù)服務(wù)器后臺給的來定

//與請求結(jié)果結(jié)構(gòu)相符的實(shí)體類publicclassHttpResult<T>{privateintcount;//請求的數(shù)量privateintstart;//請求的起始頁碼privateinttotal;//得到的數(shù)據(jù)總數(shù)privateStringtitle;//請求結(jié)果的描述privateTsubjects;//返回的數(shù)據(jù)內(nèi)容,類型不確定,使用泛型T表示//getter&setter...}

7.4 封裝請求回調(diào)(Observer)

(DevRing中提供了三種封裝好的Observer,分別用于普通請求,上傳請求(可監(jiān)聽進(jìn)度),下載請求(可監(jiān)聽進(jìn)度))

可對Observer封裝一層,作用:

在onError中進(jìn)行統(tǒng)一的異常處理,得到更直接詳細(xì)的異常信息

在onNext中進(jìn)行統(tǒng)一操作,如請求回來后,先判斷token是否失效,如果失效則直接跳轉(zhuǎn)登錄頁面

在onNext中對返回的結(jié)果進(jìn)行處理,得到更直接的數(shù)據(jù)信息

在onSubscribe中進(jìn)行請求前的操作,注意,onSubscribe是執(zhí)行在 subscribe() 被調(diào)用時的線程,所以如果在onSubscribe里進(jìn)行UI操作,就要保證subscribe()也是調(diào)用在UI線程里。

publicabstractclassHttpObserver<T>implementsObserver<HttpResult<T>>{@OverridepublicvoidonSubscribe(Disposabled){}@OverridepublicvoidonComplete(){}@OverridepublicvoidonError(Throwablee){if(einstanceofException){//訪問獲得對應(yīng)的ExceptionExceptionHandler.ResponeThrowableresponeThrowable=ExceptionHandler.handleException(e);onError(responeThrowable.code,responeThrowable.message);}else{//將Throwable 和 未知錯誤的status code返回ExceptionHandler.ResponeThrowableresponeThrowable=newExceptionHandler.ResponeThrowable(e,ExceptionHandler.ERROR.UNKNOWN);onError(responeThrowable.code,responeThrowable.message);}}@OverridepublicvoidonNext(HttpResult<T>httpResult){//做一些回調(diào)后需統(tǒng)一處理的事情//如請求回來后,先判斷token是否失效//如果失效則直接跳轉(zhuǎn)登錄頁面//...//如果沒失效,則正?;卣{(diào)onNext(httpResult.getTitle(),httpResult.getSubjects());}//具體實(shí)現(xiàn)下面兩個方法,便可從中得到更直接詳細(xì)的信息publicabstractvoidonNext(Stringtitle,Tt);publicabstractvoidonError(interrType,StringerrMessage);}

到此,封裝算是結(jié)束了,這樣使用起來就會便捷很多,整個的使用流程會在下面的“使用”中介紹,也可以查看demo。

8. 混淆

在proguard-rules.pro文件中添加以下內(nèi)容進(jìn)行混淆配置

#Retrofit開始-dontwarn retrofit2.**-keepclassretrofit2.**{*;}-keepattributes Signature-keepattributes Exceptions-dontwarn okio.**#Retrofit結(jié)束#Rxjava&RxAndroid開始-dontwarn sun.misc.**-keepclassmembersclassrx.internal.util.unsafe.*ArrayQueue*Field*{long producerIndex;long consumerIndex;}-keepclassmembersclassrx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef{rx.internal.util.atomic.LinkedQueueNode producerNode;}-keepclassmembersclassrx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef{rx.internal.util.atomic.LinkedQueueNode consumerNode;}#Rxjava&RxAndroid結(jié)束

使用

經(jīng)過前面的配置和封裝后,下面演示一下在實(shí)際場景的使用。

1. 一般場景

請求正在上映的電影,然后在View層展示

@GET("v2/movie/in_theaters")Observable<HttpResult<List<MovieRes>>>getPlayingMovie(@Query("count")intcount);

//被訂閱者(用于發(fā)起網(wǎng)絡(luò)請求)Observableobservable=RetrofitUtil.getApiService().getPlayingMovie(count);//訂閱者(網(wǎng)絡(luò)請求回調(diào))HttpObserver<List<MovieRes>>observer=newHttpObserver<List<MovieRes>>(){//請求成功回調(diào)@OverridepublicvoidonNext(Stringtitle,List<MovieRes>list){LogUtil.d(TAG,"獲取"+title+"成功");//通過IView接口將數(shù)據(jù)回調(diào)給View層展示if(mIView!=null){mIView.getMovieSuccess(list);}}//請求失敗回調(diào)@OverridepublicvoidonError(interrType,StringerrMessage){//通過IView接口將數(shù)據(jù)回調(diào)給View層展示if(mIView!=null){mIView.getMovieFail(errType,errMessage);}}};//通過IView接口獲取View層的PublishSubject來進(jìn)行生命周期的控制 PublishSubject<LifeCycleEvent>lifecycleSubject=mIView.getLifeSubject();//發(fā)起請求RetrofitUtil.composeToSubscribe(observable,observer,lifecycleSubject);

2. 特殊場景

由于沒找到相符的接口,所以demo中沒有提供以下代碼。就當(dāng)作提供個思路,請諒解。

2.1 嵌套請求(使用flatMap實(shí)現(xiàn))

場景:先請求token,再根據(jù)得到的token請求用戶信息,最后在View層展示

@GET("...")Observable<HttpResult<String>>getToken();@GET("...")Observable<HttpResult<UserInfo>>getUserInfo(@Query("token")Stringtoken);

//被訂閱者(用于發(fā)起網(wǎng)絡(luò)請求)Observableobservable=RetrofitUtil.getApiService().getToken().flatMap(newFunction<HttpResult<String>,ObservableSource<HttpResult<UserInfo>>{@OverridepublicObservableSource<HttpResult<UserInfo>>apply(HttpResult<String>httpResult)throwsException{//從httpResult中得到請求來的token,然后再發(fā)起用戶信息的請求returnRetrofitUtil.getApiService().getUserInfo(httpResult.getData());}});//訂閱者(網(wǎng)絡(luò)請求回調(diào))HttpObserver<UserInfo>observer=newHttpObserver<UserInfo>(){//請求成功回調(diào)@OverridepublicvoidonNext(UserInfouserInfo){//通過IView接口將數(shù)據(jù)回調(diào)給View層展示if(mIView!=null){mIView.getUserInfoSuccess(userInfo);}}//請求失敗回調(diào)@OverridepublicvoidonError(interrType,StringerrMessage){//通過IView接口將數(shù)據(jù)回調(diào)給View層展示if(mIView!=null){mIView.getUserInfoFail(errType,errMessage);}}};//通過IView接口獲取View層的PublishSubject來進(jìn)行生命周期的控制 PublishSubject<LifeCycleEvent>lifecycleSubject=mIView.getLifeSubject();//發(fā)起請求RetrofitUtil.composeToSubscribe(observable,observer,lifecycleSubject);

2.2 組合請求返回的結(jié)果(使用zip實(shí)現(xiàn))

場景:請求今日最佳男歌手,請求今日最佳女歌手,將男歌手和女歌手進(jìn)行組合,得到“最佳歌手組合”,最后在View層展示

@GET("...")Observable<HttpResult<Singer>>getBestSingerMale();@GET("...")Observable<HttpResult<Singer>>getBestSingerFemale();

//被訂閱者(用于發(fā)起網(wǎng)絡(luò)請求)ObservableobservableMale=RetrofitUtil.getApiService().getBestSingerMale();ObservableobservableFemale=RetrofitUtil.getApiService().getBestSingerFemale();ObservableobservableGroup=Observable.zip(observableMale,observableFemale,newBiFunction<HttpResult<Singer>,HttpResult<Singer>,HttpResult<SingerGroup>(){@OverridepublicHttpResult<SingerGroup>apply(HttpResult<Singer>resultMale,HttpResult<Singer>resultFemale){//組合男女歌手SingersingerMale=resultMale.getData();SingersingerFemale=resultFemale.getData();SingerGroupsingerGroup=newSingerGroup(singerMale,singerFemale);HttpResult<SingerGroup>resultGroup=newHttpResult<SingerGroup>();resultGroup.setData(singerGroup);returnresultGroup;}});//訂閱者(網(wǎng)絡(luò)請求回調(diào))HttpObserver<SingerGroup>observer=newHttpObserver<SingerGroup>(){//請求成功回調(diào)@OverridepublicvoidonNext(SingerGroupsingerGroup){//通過IView接口將數(shù)據(jù)回調(diào)給View層展示if(mIView!=null){mIView.getSingerGroupSuccess(singerGroup);}}//請求失敗回調(diào)@OverridepublicvoidonError(interrType,StringerrMessage){//通過IView接口將數(shù)據(jù)回調(diào)給View層展示if(mIView!=null){mIView.getSingerGroupFail(errType,errMessage);}}};//通過IView接口獲取View層的PublishSubject來進(jìn)行生命周期的控制? ? PublishSubject<LifeCycleEvent>lifecycleSubject=mIView.getLifeSubject();//發(fā)起請求RetrofitUtil.composeToSubscribe(observableGroup,observer,lifecycleSubject);

實(shí)際開發(fā)中肯定還有其他的特殊場景,關(guān)鍵是運(yùn)用好RxJava的操作符。操作符的學(xué)習(xí)地址已貼在文章頂部。

更新:

已將demo和文章中關(guān)于Rxjava的部分從1.x改為2.x

這里貼一下RxJava2與RxJava1的區(qū)別總結(jié)(隨筆記錄,僅供參考):

RxJava2 按是否可以背壓處理,分成Observable和Flowable,Observable的訂閱者為Observer,F(xiàn)lowable的訂閱者為Subscriber。

不了解背壓的可以看這個系列的5-9篇。

背壓處理

1)上游(Flowable)通過emitter.requested()查看事件容器的剩余空間。下游(Subscriber)通過subscription.request(n)從事件容器中請求并消耗事件(消耗一個事件并不代表事件容器立刻多出一個位置)

2)四種策略 BUFFER,ERROR,DROP,LATEST

Buffer:事件容器的空間不限制,非Buffer策略時事件容器大小為128

ERROR:當(dāng)事件容器溢出時會報(bào)MissingBackpressureException。該策略下,當(dāng)下游累計(jì)消耗完96個事件后,才會給事件容器騰出96個位置。

DROP: 事件容器裝入128個事件后,剩下的將不會裝入,當(dāng)下游累計(jì)消耗完128個事件后,才會給事件容器騰出128個位置,這時再取當(dāng)前時刻發(fā)送的事件裝入。

LATEST: 與DROP類似,但它會保證取到最后發(fā)射的事件

Observable多了幾個小伙伴:Single、Completable、Maybe。他們都繼承了ObservableSource。

Single/SingleObserver:只發(fā)送/接收onNext和onError,且只發(fā)送一次

Completable/Completable:只發(fā)送/接收onComplete和onError

Maybe:Single與Completable的結(jié)合

Func1改為Function,F(xiàn)unc2..n改為BiFunction。其中的方法call改成了apply。另外對于filter()過濾,其參數(shù)為不為Function而是Predicate

Action1改為Consumer,Action2改為BiConsumer。其中的方法call改成了accept。

Observer/Subscriber的抽象方法中多了一個onSubscribe(Disposable/Subscription),類似1.x的onStart方法,它在subscribe()時調(diào)用。其中的參數(shù)Disposable/Subscription可以用來取消訂閱/查詢訂閱狀態(tài),Subscription還可用于背壓中請求消耗事件。

不再能發(fā)送null事件,Observable<Void> 不再發(fā)射任何值,而是正常結(jié)束或者拋出空指針。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容