原來公司用的是OKGO來加載網(wǎng)絡(luò),現(xiàn)在全部替換為Retrofit了,用起來挺不適應(yīng)的,現(xiàn)在我負(fù)責(zé)的模塊代碼中網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求都是照葫蘆畫瓢搬過其他人的接口代碼改成自己的。至于為什么按照這種格式寫?這么寫有什么好處?有沒有其他的寫法?懵逼了!因?yàn)橹皼]接觸過Retrofit這東西,現(xiàn)在想著抽點(diǎn)時(shí)間好好研究一下。
Retrofit是什么?
Retrofit其實(shí)我們可以理解為OkHttp的加強(qiáng)版,它也是一個(gè)網(wǎng)絡(luò)加載框架。底層是使用OKHttp封裝的。準(zhǔn)確來說,網(wǎng)絡(luò)請(qǐng)求的工作本質(zhì)上是OkHttp完成,而 Retrofit 僅負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求接口的封裝。它的一個(gè)特點(diǎn)是包含了特別多注解,方便簡(jiǎn)化你的代碼量。并且還支持很多的開源庫(kù)(著名例子:Retrofit + RxJava)。
還想說一點(diǎn)題外話,Retrofit和OkHttp(我們公司用到的OKGO框架也是封裝人家的OkHttp)都是square公司(前一篇我寫的簡(jiǎn)書文章Dragger也是他們的,我擦,真是大佬?。?/p>
Retrofit的好處?
- 超級(jí)解耦
解耦?解什么耦?
我們?cè)谡?qǐng)求接口數(shù)據(jù)的時(shí)候,API接口定義和API接口使用總是相互影響,什么傳參、回調(diào)等,耦合在一塊。有時(shí)候我們會(huì)考慮一下怎么封裝我們的代碼讓這兩個(gè)東西不那么耦合,這個(gè)就是Retrofit的解耦目標(biāo),也是它的最大的特點(diǎn)。
Retrofit為了實(shí)現(xiàn)解耦,使用了特別多的設(shè)計(jì)模式,這里附上一片很好的文章,里面講的就是實(shí)現(xiàn)原理:
Retrofit分析-漂亮的解耦套路 - 可以配置不同HttpClient來實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求,如OkHttp、HttpClient...
- 支持同步、異步和RxJava
- 可以配置不同的反序列化工具來解析數(shù)據(jù),如json、xml...
- 請(qǐng)求速度快,使用非常方便靈活
Retrofit注解
- 請(qǐng)求方法
| 注解代碼 | 請(qǐng)求格式 |
|---|---|
| @GET | GET請(qǐng)求 |
| @POST | POST請(qǐng)求 |
| @DELETE | DELETE請(qǐng)求 |
| @HEAD | HEAD請(qǐng)求 |
| @OPTIONS | OPTIONS請(qǐng)求 |
| @PATCH | PATCH請(qǐng)求 |
- 請(qǐng)求參數(shù)
| 注解代碼 | 說明 |
|---|---|
| @Headers | 添加請(qǐng)求頭 |
| @Path | 替換路徑 |
| @Query | 替代參數(shù)值,通常是結(jié)合get請(qǐng)求的 |
| @FormUrlEncoded | 用表單數(shù)據(jù)提交 |
| @Field | 替換參數(shù)值,是結(jié)合post請(qǐng)求的 |
下面我們?cè)敿?xì)說說這些注解
我們先來看一段Retrofit請(qǐng)求的簡(jiǎn)單用法
- 添加依賴
由于Retrofit是基于OkHttp,所以還需要添加OkHttp庫(kù)依賴
在build.grale添加如下依賴:
dependencies {
// Okhttp庫(kù)
compile 'com.squareup.okhttp3:okhttp:3.1.2'
// Retrofit庫(kù)
compile 'com.squareup.retrofit2:retrofit:2.0.2'
}
- 添加網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
- 創(chuàng)建接收服務(wù)器返回?cái)?shù)據(jù)的類
public class News {
// 根據(jù)返回?cái)?shù)據(jù)的格式和數(shù)據(jù)解析方式(Json、XML等)定義
...
}
- 創(chuàng)建用于描述網(wǎng)絡(luò)請(qǐng)求的接口
public interface APi {
// @GET注解的作用:采用Get方法發(fā)送網(wǎng)絡(luò)請(qǐng)求
// getNews(...) = 接收網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)的方法
// 其中返回類型為Call<News>,News是接收數(shù)據(jù)的類(即上面定義的News類)
// 如果想直接獲得Responsebody中的內(nèi)容,可以定義網(wǎng)絡(luò)請(qǐng)求返回值為Call<ResponseBody>
@Headers("apikey:81bf9da930c7f9825a3c3383f1d8d766")
@GET("word/word")
Call<News> getNews(@Query("num") String num,@Query("page")String page);
}
這一塊知識(shí)點(diǎn)很多,做好筆記了!
①Retrofit將Http請(qǐng)求抽象成Java接口,并在接口里面采用注解來配置網(wǎng)絡(luò)請(qǐng)求參數(shù)。用動(dòng)態(tài)代理將該接口的注解“翻譯”成一個(gè)Http請(qǐng)求,最后再執(zhí)行 Http請(qǐng)求
注意: 接口中的每個(gè)方法的參數(shù)都需要使用注解標(biāo)注,否則會(huì)報(bào)錯(cuò)
②APi接口中的最后一個(gè)注釋,Responsebody是Retrofit網(wǎng)絡(luò)請(qǐng)求回來的原始數(shù)據(jù)類,沒經(jīng)過Gson轉(zhuǎn)換什么的,如果你不想轉(zhuǎn)換,比如我就想看看接口返回的json字符串,那就像注釋中說的,把Call的泛型定義為ResponseBody:Call<ResponseBody>
③GET注解
說白了就是我們的GET請(qǐng)求方式。
這里涉及到Retrofit創(chuàng)建的一些東西,Retrofit在創(chuàng)建的時(shí)候,有一行代碼:
baseUrl("http://apis.baidu.com/txapi/")
這個(gè)http://apis.baidu.com/txapi/是我們要訪問的接口的BaseUrl,而我們現(xiàn)在用GET注解的字符串 "word/word"會(huì)追加到BaseUrl中變?yōu)椋?a target="_blank" rel="nofollow">http://apis.baidu.com/txapi/world/world
在我們?nèi)粘i_發(fā)中,BaseUrl具體是啥由后端接口童鞋給出,之后接口童鞋們會(huì)出各種各種的后綴(比如上面的 "word/word")組成各種各行的接口用來供移動(dòng)端數(shù)據(jù)調(diào)用,實(shí)現(xiàn)各種各樣的功能
④@Query
簡(jiǎn)單點(diǎn)來說呢
@Query("num")String num, @Query("page")String page;
就是鍵值對(duì),Retrofit會(huì)把這兩個(gè)字段一塊拼接到接口中,追加到http://apis.baidu.com/txapi/world/world后面,變?yōu)?a target="_blank" rel="nofollow">http://apis.baidu.com/txapi/world/world?num=10&page=1,這樣,這個(gè)帶著響應(yīng)頭的接口就是我們最終請(qǐng)求網(wǎng)絡(luò)的完整接口。
這里補(bǔ)充一點(diǎn)哈,GET請(qǐng)求方式,如果攜帶的參數(shù)不是以
?num=10&page=1
拼接到接口中(就是不帶?分隔符),那就不用Query注解了,而是使用Path注解,像我們項(xiàng)目中的Get請(qǐng)求:
@GET(URL.CLAIM_APPLICATION_BOOKINFO + "{claimId}")
Observable<PublicResponseEntity<ClaimApplicationBookInfo>> getClaimApplicationBookInfo(@Header("Authorization") String authorization, @Path("claimId") String claimId);
上面的GET注解的接口通過{}占位符來標(biāo)記的claimId,就用@Path注解在傳入claimId的值。
@Query與@Path功能相同,但區(qū)別明顯不一樣。像@Query的例子,我如果使用@Path來注解,那么程序就會(huì)報(bào)錯(cuò)。這塊要搞清楚!
還有一點(diǎn)哈,有的url既有“{}”占位符,又有“?”后面的鍵值對(duì)(key-value),那Retrofit既得使用@Query注解又得使用@Path注解,也就是說,兩者可以同時(shí)使用。
⑤@Headers
@Headers("apikey:81bf9da930c7f9825a3c3383f1d8d766")
這個(gè)很好理解,這個(gè)接口需要添加的header:
apikey:81bf9da930c7f9825a3c3383f1d8d766
@Headers就是把接口的header注解進(jìn)去。還有很多添加header的方式,比如:
public interface APi {
@GET("word/word")
Call<News> getNews(@Header("apikey")String apikey, @Query("num")String num, @Query("page")String page);
}
這個(gè)就是在代碼中動(dòng)態(tài)的添加header,用法如下:
Call<News> news = mApi.getNews("81bf9da930c7f9825a3c3383f1d8d766", "1", "10");
關(guān)于header的其他添加方式,大家可以看看下面的文章:
Retrofit之請(qǐng)求頭
這里再補(bǔ)充一點(diǎn):@Header與@Headers的區(qū)別
舉個(gè)例子:
//@Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
//@Headers
@Headers("Authorization:authorization")
@GET("user")
Call<User> getUser()
以上兩個(gè)方法的效果是一致的。
區(qū)別就在于使用場(chǎng)景和使用方式
使用場(chǎng)景:@Header用于添加不固定的請(qǐng)求頭,@Headers用于添加固定的請(qǐng)求頭
使用范圍:@Header作用于方法的參數(shù);@Headers作用于方法
- 創(chuàng)建Retrofit對(duì)象
Retrofit retrofit = new Retrofit.Builder()
//設(shè)置數(shù)據(jù)解析器
.addConverterFactory(GsonConverterFactory.create())
//設(shè)置網(wǎng)絡(luò)請(qǐng)求的Url地址
.baseUrl("http://apis.baidu.com/txapi/")
.build();
// 創(chuàng)建網(wǎng)絡(luò)請(qǐng)求接口的實(shí)例
mApi = retrofit.create(APi.class);
這一塊知識(shí)點(diǎn)有三個(gè):
①此處特意說明一下這個(gè)網(wǎng)絡(luò)請(qǐng)求的URL的組成:Retrofit把網(wǎng)絡(luò)請(qǐng)求的URL 分成了兩部分設(shè)置:
第一部分:在創(chuàng)建Retrofit實(shí)例時(shí)通過.baseUrl()設(shè)置,就是上面的
.baseUrl("http://apis.baidu.com/txapi/")
第二部分:在網(wǎng)絡(luò)請(qǐng)求接口的注解設(shè)置,就是在上面的APi接口中用GET注解的字符串:
@GET("word/word")
Retrofit的網(wǎng)絡(luò)請(qǐng)求的完整Url = 創(chuàng)建Retrofit實(shí)例時(shí)通過.baseUrl()設(shè)置的url
+網(wǎng)絡(luò)請(qǐng)求接口的注解設(shè)置(下面稱 “path“ )
(第四種類型存疑)

建議采用第三種方式來配置,并盡量使用同一種路徑形式。
②關(guān)于數(shù)據(jù)解析器(Converter)
//設(shè)置數(shù)據(jù)解析器
.addConverterFactory(GsonConverterFactory.create())
這個(gè)有啥用?這句話的作用就是使得來自接口的json結(jié)果會(huì)自動(dòng)解析成定義好了的字段和類型都相符的json對(duì)象接受類。在Retrofit 2.0中,Package 中已經(jīng)沒有Converter了,所以,你需要自己創(chuàng)建一個(gè)Converter, 不然的話Retrofit只能接收字符串結(jié)果,你也只能拿到一串字符,剩下的json轉(zhuǎn)換的活還得你自己來干。所以,如果你想接收json結(jié)果并自動(dòng)轉(zhuǎn)換成解析好的接收類,必須自己創(chuàng)建Converter對(duì)象,然后使用addConverterFactory把它添加進(jìn)來!
Retrofit支持多種數(shù)據(jù)解析方式,在使用時(shí)注意需要在Gradle添加依賴:
| 數(shù)據(jù)解析器 | Gradle依賴 |
|---|---|
| Gson | com.squareup.retrofit2:converter-gson:2.0.2 |
| Jackson | com.squareup.retrofit2:converter-jackson:2.0.2 |
| Simple XML | com.squareup.retrofit2:converter-simplexml:2.0.2 |
| Protobuf | com.squareup.retrofit2:converter-protobuf:2.0.2 |
| Moshi | com.squareup.retrofit2:converter-moshi:2.0.2 |
| Wire | com.squareup.retrofit2:converter-wire:2.0.2 |
| Scalars | com.squareup.retrofit2:converter-scalars:2.0.2 |
像上面代碼中,就是使用了第一種Gson數(shù)據(jù)解析器
③再來引入另一個(gè)方法:addCallAdapterFactory()
上文代碼中沒有這個(gè)方法,但是得知道它的作用,有必要作為補(bǔ)充知識(shí)點(diǎn)
看一下我們的接口返回:
Call<News> news = mApi.getNews("1", "10");
返回的Call<News>可以理解成源生的了,默認(rèn)就這么寫。但像很多很多項(xiàng)目都是結(jié)合著RXJava來使用這個(gè)Retrofit的,那么這個(gè)接口返回就會(huì)被定義為(偽代碼):
Observable<News> news = mApi.getNews("1", "10").subscribeOn(...).observeOn(...);
它返回的是一個(gè)Observable類型(觀察者模式)。從上面可以看到,Retrofit接口的返回值可以分為兩部分,第一部分是返回值類型:Call或者Observable,另一部分是泛型:News
addCallAdapterFactory()影響的就是第一部分:Call或者Observable。Call類型是Retrofit默認(rèn)支持的(Retrofit內(nèi)部有一個(gè)DefaultCallAdapterFactory),所以你如果不用RXJava + Retrofit結(jié)合使用,那就自動(dòng)忽略掉這個(gè)方法,而如果你想要支持RXJava(就是想把返回值定義為Observable對(duì)象),就需要我們自己用addCallAdapterFactory()添加:
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
像我們項(xiàng)目中Retrofit創(chuàng)建的代碼就是:
retrofit = new Retrofit.Builder()
.baseUrl(URL.SERVICE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
同理,Retrofit不光支持多種數(shù)據(jù)解析器,也支持多種網(wǎng)絡(luò)請(qǐng)求適配器:Guava、Java8、RXJava ,使用時(shí)也需要在Gradle添加依賴:
| 網(wǎng)絡(luò)請(qǐng)求適配器 | Gradle依賴 |
|---|---|
| Guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
| Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
| RXJava | com.squareup.retrofit2:adapter-rxjava:2.0.2 |
像上面的代碼,就是用的RXJava
- 發(fā)起網(wǎng)絡(luò)請(qǐng)求
//對(duì)發(fā)送請(qǐng)求進(jìn)行封裝
Call<News> news = mApi.getNews("1", "10");
//發(fā)送網(wǎng)絡(luò)請(qǐng)求(異步)
news.enqueue(new Callback<News>() {
//請(qǐng)求成功時(shí)回調(diào)
@Override
public void onResponse(Call<News> call, Response<News> response) {
//請(qǐng)求處理,輸出結(jié)果-response.body().show();
}
@Override
public void onFailure(Call<News> call, Throwable t) {
//請(qǐng)求失敗時(shí)候的回調(diào)
}
});
上面是一個(gè)簡(jiǎn)單的GET請(qǐng)求的全過程。
補(bǔ)充一點(diǎn),Retrofit還有個(gè)發(fā)起同步網(wǎng)絡(luò)請(qǐng)求的方式:
//對(duì)發(fā)送請(qǐng)求進(jìn)行封裝
Call<News> news = mApi.getNews("1", "10");
//發(fā)送網(wǎng)絡(luò)請(qǐng)求(同步)
Response<Reception> response = news.execute();
接下來我們看下POST請(qǐng)求
Retrofit的POST請(qǐng)求
POST請(qǐng)求與GET請(qǐng)求算是我們?nèi)粘i_發(fā)中最最常用的兩種網(wǎng)絡(luò)訪問方式,Retrofit的POST請(qǐng)求在用法上與GET區(qū)別不算大。
拿我早期寫過的一個(gè)比較不合格的代碼舉個(gè)例子就能看出來(先聲明,這種寫法是不合格的,但是接口能跑通,看下去你就知道了):
①首先都是定義一個(gè)API接口:
public interface IServiceApi {
@POST("/claims/preclaims")
Observable<PublicResponseEntity<PreclaimsResponseEntity>> postClaimPreclaims(@Header("Authorization") String authorization, @QueryMap HashMap<String, String> deviceInfo, @Body RequestBody body);
}
拿出筆記啦(因?yàn)榇蟛糠侄己虶ET請(qǐng)求一樣,所以這里講的就簡(jiǎn)單點(diǎn))
①和GET請(qǐng)求相比,流程的開頭都是創(chuàng)建了一個(gè)API的接口,然后用@POST注釋,指定了對(duì)應(yīng)的接口地址,我的返回值需要把獲取到的Json字符串轉(zhuǎn)成PublicResponseEntity<PreclaimsResponseEntity>,所以方法返回值要寫成Call<PublicResponseEntity<PreclaimsResponseEntity>>
但是我項(xiàng)目中用到的是RxJava + Retrofit,所以把返回值定義為了Observable<PublicResponseEntity<PreclaimsResponseEntity>>
②方法中的第一個(gè)參數(shù):我是在代碼中動(dòng)態(tài)的添加了一個(gè)header,這沒啥可說的,上面的GET請(qǐng)求中說完了已經(jīng),看第二個(gè)。
③方法中的第二個(gè)參數(shù):通過@QueryMap往接口中注解很多個(gè)參數(shù),看到這里很容易聯(lián)想到@Query,在上面的GET請(qǐng)求中@Query是一個(gè)一個(gè)往接口中注入?yún)?shù)的,而@QueryMap從名字也能看出來,如果Query參數(shù)比較多,那么可以通過@QueryMap方式將所有的參數(shù)集成在一個(gè)Map統(tǒng)一傳遞。
③第三個(gè)參數(shù):通過@Body注解了一個(gè)RequestBody,
好!又出來一個(gè)新的注解@Body,它的源碼中對(duì)他的注釋大體意思是:使用這個(gè)注解可以把參數(shù)放到請(qǐng)求體中,適用于 POST/PUT請(qǐng)求,一臉懵逼呀,只知道它適用于對(duì)于POST/PUT。
其實(shí),@Body可以注解很多東西的,HashMap、實(shí)體類等,例如:
public interface IServiceApi {
@POST("/claims/preclaims")
Observable<Item> postClaimUser(@Body User user);
}
那這么一看,@Body和@QueryMap差別不是很大哈,都可以對(duì)很多參數(shù)進(jìn)行封裝傳遞。話是這么說,但是它倆還是有差別的:
@QueryMap注解會(huì)把參數(shù)拼接到url后面,所以它適用于GET請(qǐng)求;
@Body會(huì)把參數(shù)放到請(qǐng)求體中,所以適用于POST請(qǐng)求。
如果你的項(xiàng)目是采用POST請(qǐng)求方式,不管是使用實(shí)體類還是使用HashMap最好采用@Body注解。雖然你使用QueryMap 可能也不會(huì)有什么問題(PS:這種共用的情況只適用于POST請(qǐng)求,GET請(qǐng)求不能使用@Body注解,否則會(huì)報(bào)錯(cuò)),就像上面我的不合格代碼一樣,POST請(qǐng)求中一直采用@QueryMap,雖然也能拿到接口數(shù)據(jù),但是這么寫是不合格的。
引以為戒吧~~
接下來就是調(diào)用了:
一樣的創(chuàng)建Retrofit對(duì)象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL.SERVICE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
// 創(chuàng)建網(wǎng)絡(luò)請(qǐng)求接口的實(shí)例
IServiceApi mApi = retrofit.create(IServiceApi .class);
一樣的發(fā)起網(wǎng)絡(luò)請(qǐng)求:
//對(duì)發(fā)送請(qǐng)求進(jìn)行封裝
Observable<PublicResponseEntity<PreclaimsResponseEntity>> news = mApi.postClaimPreclaims("你的Header信息", "你要傳到接口中的HashMap參數(shù)", "你的實(shí)體類");
//發(fā)送網(wǎng)絡(luò)請(qǐng)求(異步)
news.enqueue(new Callback<News>() {
//請(qǐng)求成功時(shí)回調(diào)
@Override
public void onResponse(Call<News> call, Response<News> response) {
//請(qǐng)求處理,輸出結(jié)果-response.body().show();
}
@Override
public void onFailure(Call<News> call, Throwable t) {
//請(qǐng)求失敗時(shí)候的回調(diào)
}
});
OK,到這里,你就能成功拿到一次POST請(qǐng)求的數(shù)據(jù)了。
這一塊我講的比較少,因?yàn)槲乙彩窃谶厡戇厡W(xué),不會(huì)的不敢寫,先把掌握到的寫下來做個(gè)積累,以后慢慢把學(xué)到的東西補(bǔ)充進(jìn)來。
Retrofit下載文件
其實(shí)用Retrofit下載文件方式與其他請(qǐng)求幾乎無異,拿我用到下載PDF的程序來舉例子
step1:編寫API,執(zhí)行下載接口功能
public interface IServiceApi {
····
//PDF文件Retrofit下載
@Streaming
@GET
Observable<ResponseBody> retrofitDownloadFile(@Url String fileUrl);
...
}
上面的代碼有幾個(gè)注意的點(diǎn):
①@Streaming 是注解大文件的,小文件可以忽略不加注釋,但是大文件一定需要注釋,不然會(huì)出現(xiàn)OOM。
②fileUrl就是PDF的下載地址,通過參數(shù)形式傳進(jìn)來
③正常來講,API接口的返回類型是Call<ResponseBody>,即:
public interface IServiceApi {
····
//PDF文件Retrofit下載
@Streaming
@GET
Call<ResponseBody> retrofitDownloadFile(@Url String fileUrl);
...
}
但是我項(xiàng)目中是Retrofit結(jié)合RXJava來使用的,我把它的返回值類型定義為Observable<ResponseBody>,強(qiáng)烈推薦這種寫法,便利于后續(xù)的數(shù)據(jù)處理
step2:實(shí)現(xiàn)一個(gè)下載管理工具
它的作用有很多:寫入文件、判斷文件類型、計(jì)算文件大小...當(dāng)然最主要的還是用來把下載下來的文件寫入本地
public class DownLoadManager {
//Log標(biāo)記
private static final String TAG = "eeeee";
//APK文件類型
private static String APK_CONTENTTYPE = "application/vnd.android.package-archive";
//PNG文件類型
private static String PNG_CONTENTTYPE = "image/png";
//JPG文件類型
private static String JPG_CONTENTTYPE = "image/jpg";
//文件后綴名
private static String fileSuffix="";
/**
* 寫入文件到本地
* @param file
* @param body
* @return
*/
public static boolean writeResponseBodyToDisk(File file, ResponseBody body) {
Log.d(TAG, "contentType:>>>>" + body.contentType().toString());
//下載文件類型判斷,并對(duì)fileSuffix賦值
String type = body.contentType().toString();
if (type.equals(APK_CONTENTTYPE)) {
fileSuffix = ".apk";
} else if (type.equals(PNG_CONTENTTYPE)) {
fileSuffix = ".png";
}
// 其他類型同上 需要的判斷自己加入.....
//下面就是一頓寫入,文件寫入的位置是通過參數(shù)file來傳遞的
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
try {
is = body.byteStream();
long total = body.contentLength();
fos = new FileOutputStream(file);
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
int progress = (int) (sum * 1.0f / total * 100);
}
fos.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
}
}
}
}
上面的代碼沒啥需要注意的點(diǎn),都挺基本的Android代碼,硬要說的話,就注意一下ResponseBody這個(gè)類吧,導(dǎo)包的時(shí)候有很多,別導(dǎo)錯(cuò)了,要導(dǎo)入OKHTTP的包,因?yàn)镽etrofit的底層就是OKHTTP,當(dāng)時(shí)我在這里手快導(dǎo)錯(cuò)了,蒙了一下。
step3:接下來就是調(diào)用API下載寫入文件了
我項(xiàng)目結(jié)構(gòu)是MVVM,又使用了RXJava和Dagger2,看起來代碼寫的簡(jiǎn)單沒多少,但是沒用過的可能看不懂。那這里貼兩份代碼,一份我項(xiàng)目中的代碼(強(qiáng)烈推薦這種寫法,Retrofit結(jié)合RXJava來用不僅解決了線程安全問題而且特別簡(jiǎn)單),一份是OKHTTP原始的代碼
項(xiàng)目是MVVM的,為下載的Activity創(chuàng)建一個(gè)ViewModel來執(zhí)行下載的耗時(shí)操作,并在Activity中用Dagger2來注入該ViewModel對(duì)象
public class ElectronicImageSynthesisViewModel {
private IServiceApi mServiceApi;
@Inject
public ElectronicImageSynthesisViewModel(IServiceApi serviceApi){
mServiceApi = serviceApi;
}
//下載PDF文件
public Observable<ResponseBody> retrofitDownloadFile(String fileUrl){
return mServiceApi.retrofitDownloadFile(fileUrl).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
}
上面代碼中注意一點(diǎn)(RXJava的內(nèi)容)
要指定下載的線程與數(shù)據(jù)返回的線程:
subscribeOn(Schedulers.io()):在io線程中下載文件
observeOn(AndroidSchedulers.mainThread()):在UI線程中處理返回結(jié)果
public class ElectronicImageSynthesisActivity extends BaseActivity {
...
@Inject
ElectronicImageSynthesisViewModel mElectronicImageSynthesisViewModel;
/**
* pdf下載
*/
private void pdfDownLoad() {
mElectronicImageSynthesisViewModel.retrofitDownloadFile(mPDFDownloadUrl)
.map(new Function<ResponseBody, Boolean>() {
@Override
public Boolean apply(ResponseBody responseBody) throws Exception {
return DownLoadManager.writeResponseBodyToDisk(mPDFSavedFile, responseBody);
}
}).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
if (aBoolean) {
//這一步就是對(duì)你下載下來的文件進(jìn)行你想要的操作了,我這里是展示PDF
displayFromFile(mPDFSavedFile);
}
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//onError
showToast(throwable.getMessage());
}
});
}
...
}
上面代碼是Retrofit和RXJava結(jié)合來使用的,代碼看起來沒多少行,很簡(jiǎn)潔。最讓我喜歡的是它的鏈?zhǔn)浇Y(jié)構(gòu),邏輯一目了然,而不是以往那種層層遞進(jìn)。
pdfDownLoad()的代碼有很多東西需要講,因?yàn)樗婕暗絉XJava了,我們這里重點(diǎn)是Retrofit下載,只挑一些重點(diǎn)來說明一下:
①.首先它調(diào)用了ViewModel里面的mElectronicImageSynthesisViewModel(),并給它傳遞了一個(gè)PDF的下載鏈接mPDFDownloadUrl
②.mElectronicImageSynthesisViewModel()方法返回的就是攜帶下載數(shù)據(jù)的Observable<ResponseBody>
③.這個(gè)Observable在RXJava中叫做被觀察者,它的泛型就是下載的數(shù)據(jù):ResponseBody,現(xiàn)在我們通過retrofitDownloadFile()返回了它,那么我們需要做的就是把它寫入到手機(jī)本地
④.writeResponseBodyToDisk()就上場(chǎng)了,把我們定義的想要存儲(chǔ)到手機(jī)哪里的文件File給它傳遞進(jìn)去。我們只需要做的就是根據(jù)writeResponseBodyToDisk()返回的boolean值來判斷文件到底寫沒寫入成功:true-寫入成功,false-寫入失敗。
⑤.這里就涉及到一個(gè)類型轉(zhuǎn)換了,我們拿到的是ResponseBody,想要的卻是寫入成功與否的標(biāo)記,RXJava就給提供了一個(gè)操作符:map
⑥.使用map操作符,實(shí)現(xiàn)里面的apply方法,在apply里面調(diào)用我們的writeResponseBodyToDisk(),把拿到的boolean值返回,ok,轉(zhuǎn)換完成~
⑦.接下來就是accept中對(duì)下載好的文件進(jìn)行操作了,判斷下boolean,如果true怎么怎么樣,false怎么怎么樣。
⑧.最后一定要寫new Consumer<Throwable>(),因?yàn)槟憔W(wǎng)絡(luò)請(qǐng)求失敗了,Observable就會(huì)發(fā)送一個(gè)異常,你如果不捕獲它,程序就會(huì)崩潰
以上簡(jiǎn)單了解~~
如果使用源生的OKHTTP,那就簡(jiǎn)單的多,直接調(diào)用下載:
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(baseUrl)
.build();
IServiceApi apiService = retrofit.create(IServiceApi.class);
Call<ResponseBody> call = apiService.retrofitDownloadFile(mPDFDownloadUrl);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
//下載成功,寫入文件
boolean bl = DownLoadManager.writeResponseBodyToDisk(mPDFSavedFile, response.body());
if (bl) {
//這一步就是對(duì)你下載下來的文件進(jìn)行你想要的操作了,我這里是展示PDF
displayFromFile(mPDFSavedFile);
}
} else {
//下載失敗
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//下載失敗
}
});
上面的代碼只涉及到Retrofit下載,就沒啥干貨可以講,用法也很簡(jiǎn)單。不過大家可以看到兩種寫法的區(qū)別,先不說鏈接邏輯和層層遞進(jìn)邏輯上的區(qū)別,光這個(gè)代碼量和排版就不討人喜歡。所以還是推薦大家學(xué)習(xí)一下RXJava,雖然入門挺難,但是用熟了你會(huì)發(fā)現(xiàn)有很多驚喜,太棒了~
誤區(qū)
在這里記錄我遇到的坑或者使用錯(cuò)誤的地方:
①:一開始我理解的是GET請(qǐng)求一定要用@Path注解,POST請(qǐng)求一定要用@Query注解,但這是錯(cuò)誤的。@Path、@Query具體怎么用要看具體的Url形式。
特別感謝以下作者,讓我從一個(gè)不會(huì)Retrofit的小白成長(zhǎng)到會(huì)使用這個(gè)網(wǎng)絡(luò)框架的程序猿,這篇文章也是在這幾篇博文基礎(chǔ)上總結(jié)了下自己的理解:
Retrofit分析-漂亮的解耦套路
Android retrofit 注解@QueryMap和@Body的區(qū)別
這是一份很詳細(xì)的 Retrofit 2.0 使用教程(含實(shí)例講解)
Retrofit 2.0 超能實(shí)踐(四),完成大文件斷點(diǎn)下載