
前言
因?yàn)橹癆ndroid開發(fā)第三方庫總結(jié)的文章在交流群分享廣受好評(píng),就有不少朋友讓我推薦對(duì)應(yīng)的功能庫。想著,既然大家都覺得不錯(cuò),那也想著順手把自己常用的這些功能庫給整理出來,今后要是問我怎么用,直接甩文章。哈哈哈,想想都開心。
本文面向?qū)ο螅河幸欢ɑA(chǔ)的Android開發(fā)者。
本篇文章大致內(nèi)容從如下三個(gè)方面講進(jìn)行講解:
- 什么是Retrofit?為什么要用Retrofit?如何使用Retrofit?
- Retrofit的常用注解以及使用場(chǎng)景。
- 如何給Retrofit設(shè)置攔截器。
當(dāng)然,假若各位覺得內(nèi)容寫得不好,本人心態(tài)不好,不接受任何批評(píng)。還是那句話:you can you up,no can no ……
好了,話不多說,今天講Retrofit。小板凳拿好,開始上課。坐后面的朋友請(qǐng)到前面來。
什么是Retrofit?
Retrofit,英文翻譯過來是翻新,改進(jìn)的意思,光看名字很難聯(lián)想到他的具體作用,官方給的解釋是:Type-safe HTTP client for Android and Java by Square,翻譯過來就是由Square公司開發(fā)的一款針對(duì)Android網(wǎng)絡(luò)請(qǐng)求的框架,底層是基于OkHttp的。retrofit github地址
為什么要用Retrofit? Retrofit有什么優(yōu)勢(shì)?
在Android開發(fā)過程中,有很多的網(wǎng)絡(luò)請(qǐng)求框架,比如Volley、Async Http Client,我們?yōu)槭裁匆肦etrofit?
為什么要用?一個(gè)詞描述:方便。使用方便,修改也方便。
Retrofit的優(yōu)點(diǎn):
- 請(qǐng)求速度快。
- 使用簡單。
- 高度解耦。
- 支持和Rxjava聯(lián)用。
- 支持GET/POST/PUT/DELETE/HEAD/PATCH協(xié)議。
缺點(diǎn):
- 高度封裝導(dǎo)致擴(kuò)展性較差
如何使用Retrofit?
導(dǎo)包和權(quán)限申請(qǐng)
當(dāng)前文章編寫時(shí)最新版本為2.6.1。導(dǎo)包時(shí)到github去導(dǎo)入最新版本即可。
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
Retrofit有一系列的輔助包,當(dāng)我們?cè)趯?dǎo)包的時(shí)候需要根據(jù)我們的數(shù)據(jù)返回導(dǎo)入對(duì)應(yīng)的包,否則會(huì)報(bào)異常:
Could not locate ResponseBody converter for ……
比如我們需要通過Gson轉(zhuǎn)對(duì)象,我們需要增加如下Gson轉(zhuǎn)換輔助包:
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
如果我們使用了protobuf格式,那我們需要添加protobuf轉(zhuǎn)換輔助包:
implementation 'com.squareup.retrofit2:converter-protobuf:2.6.2'
當(dāng)然,還有很多輔助包:比如guava,jackson,jaxb,moshi,scalars等一系列輔助包,當(dāng)然,這些在retrofit的retrofit-converters包下都有,有興趣的可以去深入了解。這里也列出來供大家導(dǎo)入。
Gson: compile 'com.squareup.retrofit2:converter-gson:2.6.2'
Jackson: compile 'com.squareup.retrofit2:converter-jackson:2.6.2'
Moshi: compile 'com.squareup.retrofit2:converter-moshi:2.6.2'
Protobuf: compile 'com.squareup.retrofit2:converter-protobuf:2.6.2'
Wire: compile 'com.squareup.retrofit2:converter-wire:2.6.2'
Simple XML: compile 'com.squareup.retrofit2:converter-simplexml:2.6.2'
Scalars (primitives, boxed, and String): compile 'com.squareup.retrofit2:converter-scalars:2.6.2'
導(dǎo)完包之后,咱們記得在AndroidManifest.xml中聲明網(wǎng)絡(luò)請(qǐng)求權(quán)限,添加如下代碼即可申請(qǐng)網(wǎng)絡(luò)請(qǐng)求權(quán)限。
<uses-permission android:name="android.permission.INTERNET" />
好了,準(zhǔn)備工作做完了,開始講使用步驟吧。
Retrofit的使用步驟
這里我們直接用官方給的例子來做示范:
- 第一步:定義接口。
public interface IGitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
- 第二步:創(chuàng)建裝換對(duì)象。
public class Repo {
……//其他屬性
private String name;
public String getName() {
return name;
}
}
- 第三步:構(gòu)造Retrofit對(duì)象。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
IGitHubService service = retrofit.create(IGitHubService.class);
Call<List<Repo>> listRepos = service.listRepos("aserbao");
- 第四步:執(zhí)行請(qǐng)求。enqueue為異步,execute為同步。和OkHttp一模一樣。
listRepos.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
updateUi(response.body());//返回處理
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
}
});
當(dāng)然,有一些特殊情況,這里也提一下:
當(dāng)創(chuàng)建裝換對(duì)象中的字符不能使用時(shí),我們可以使用annotations注解來解決這個(gè)問題。舉一個(gè)例子:json返回字段中若返回private屬性,眾所周知private不能在Android中當(dāng)屬性名,那這時(shí)候怎么辦呢?可以這樣做:
@com.google.gson.annotations.SerializedName("private")
private boolean privateX;
好了,接下來介紹下Api的使用吧,Api的使用官方介紹也挺詳細(xì)的,這里就作下簡單介紹。
Retrofit 注解
上面使用案例中我們用了兩個(gè)注解,分別是@GET和@Path,他們的作用是什么呢?是否有其他注解,接下來咱們一起去了解下Retrofit中的諸多注解以及他們的作用。
第一類:網(wǎng)絡(luò)請(qǐng)求注解
用于網(wǎng)絡(luò)請(qǐng)求方式的注解,比如@GET注解的就是通過get請(qǐng)求接口,@Post注解標(biāo)記的就是通過post請(qǐng)求接口。類似的是還有@PUT、@DELETE、@HEAD(常用)。作用相同。不多敘述。
當(dāng)然,在網(wǎng)絡(luò)請(qǐng)求注解中有一個(gè)特殊注解:@HTTP,這個(gè)注解類似于一個(gè)融合器,他可以在使用上述請(qǐng)求的同時(shí),并進(jìn)行擴(kuò)展配置。比如我們通過@HTTP配置一個(gè)Get請(qǐng)求,且配置body,那我們可以這樣配置:
@HTTP(method = "GET",path = "{user}/repos", hasBody = true)
Call<List<Repo>> listRepos3(@Path("user") String user);
當(dāng)然,如果不知道如何使用,直接點(diǎn)到HTTP接口的源碼中查看,注釋里面便有具體使用案例。
第二類:網(wǎng)絡(luò)請(qǐng)求標(biāo)記注解
retrofit中的標(biāo)記注解有是三個(gè),分別是:@FormUrlEncoded、@Multipart、@Streaming,見名知意,咱們也稍微解釋下作用。
- @FormUrlEncoded:當(dāng)方法被@FormUrlEncoded標(biāo)記時(shí),表示將發(fā)送Form表單的數(shù)據(jù)。并且每個(gè)鍵值對(duì)都用@Field注釋,其中包含名稱和提供值的對(duì)象。使用方法如下(這里偷懶,直接從官方案例):
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
- @Multipart:可以將多個(gè)請(qǐng)求分開進(jìn)行處理。使用@Part批注請(qǐng)求內(nèi)容。這里舉兩個(gè)例子:
比如我們需要實(shí)現(xiàn)上傳文件的同時(shí),還需要傳給后臺(tái)一個(gè)name字段,這時(shí)候我們方法可以這樣寫:
@POST("/file")
@Multipart
Observable<DataResponse<UploadBean>> uploadFile(@Part("file\"; filename=\"aserbao.png\"") RequestBody file,@Part("name") RequestBody nickName);
當(dāng)然,@Multipart還可以通過@PartMap添加多個(gè)上傳信息來實(shí)現(xiàn)一次上傳多個(gè)文件。比如我們需要上傳aserbao/imgs這個(gè)目錄下的所有圖片,我們可以這樣實(shí)現(xiàn)。
@Multipart
@POST("/files")
Call<UploadBean> uploadFiles(@PartMap Map<String, RequestBody> params);
String aserbao_path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/aserbao/imgs/";
File file1 = new File(aserbao_path);
if (!file1.exists()) return;
Map<String, RequestBody> partMap = new HashMap<>();
for (File file : file1) {
RequestBody fileBody = RequestBody.create(MediaType.parse("image/*"), file);
partMap.put("file\"; filename=\"" + file.getName() + "\"", fileBody);
}
……
- @Streaming:流標(biāo)記,當(dāng)我們?cè)谔幚硐螺d大文件請(qǐng)求時(shí),通??梢允褂眠@個(gè)注解進(jìn)行標(biāo)記,主要作用是能處理返回的響應(yīng)體,當(dāng)我們寫入存儲(chǔ)文件的時(shí)候不需要將返回內(nèi)容轉(zhuǎn)換成byte[],可以直接使用response.body().source()進(jìn)行寫入。
第三類:網(wǎng)絡(luò)請(qǐng)求參數(shù)注解
這類注解也是使用最多的一類,下面咱們來一起了解下:
- @Header,@Headers:前者用來添加請(qǐng)求頭的,后者用來添加不固定請(qǐng)求頭。區(qū)別在于前者作用在參數(shù)上,后者作用在方法上。當(dāng)然,如果多個(gè)請(qǐng)求頭設(shè)置參數(shù)我們也可以使用@HeaderMap。
//@Header作用在參數(shù)上
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
//@Headers作用在方法上
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
//@HeaderMap通過鍵值對(duì)的方式給參數(shù)添加請(qǐng)求頭
@GET("user")
Call<User> getUser(@HeaderMap Map<String, String> headers)
- @Body:在POST/PUT請(qǐng)求時(shí),可以使用@Body注釋將對(duì)象指定為HTTP請(qǐng)求正文。
@POST("users/new")
Call<User> createUser(@Body User user);
值得注意的是,當(dāng)沒有添加轉(zhuǎn)換器的使用,@Body注釋的對(duì)象只能是RequestBody。
- @Field,@FieldMap:用在發(fā)POST/PUT請(qǐng)求時(shí)提交字段,這兩個(gè)方法在@FormUrlEncoded標(biāo)記的方法內(nèi)使用,作用是為發(fā)送的表單所提供的鍵值對(duì)對(duì)象。前者表示當(dāng)個(gè)鍵值對(duì),后者表示多個(gè)鍵值對(duì)。
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
- @Part,@PartMap: 這兩個(gè)方法用在發(fā)POST/PUT請(qǐng)求時(shí)提交字段,和@Field,@FieldMap的區(qū)別在于:@Field,@FieldMap在@@FormUrlEncoded標(biāo)記的方法內(nèi)使用,而@Part,@PartMap在@Multipart標(biāo)記的方法內(nèi)使用。
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
- @Path:用于指定參數(shù)替換。
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
這里當(dāng)我們調(diào)用方法listRepos("aserbao")時(shí),對(duì)應(yīng)@GET中的values值就會(huì)變成users/aserbao/repos
- @Query,@QueryMap:用于添加查詢參數(shù)。前者添加單個(gè),后者以鍵值對(duì)的形式添加一個(gè)或者多個(gè)。
@GET("/user/test")
Call<Test> testQuery(@Query("id") String id);
調(diào)用testQuery(15),對(duì)應(yīng)生成@GET中的values就是/user/test?id=15
- @Url:標(biāo)記用來傳url的。下面這兩種寫法效果是一樣的。
@GET("https://api.github.com/users/aserbao/repos")
Call<List<Repo>> listAbsRepos();
@GET
Call<List<Repo>> listAbsRepos(@Url String url);
url的配置
Retfoit的注解有一個(gè)value的參數(shù),比如@GET("users/{user}/repos")。當(dāng)然不同的baseUrl配置,value參數(shù)起的作用也是不同的。
繼續(xù)拿上面例子來說,比如我們要請(qǐng)求https://api.github.com/users/aserbao/repos這個(gè)url,我們可以怎么配置呢?
第一種:baseUrl中只添加host也就是https://api.github.com/,后面的參數(shù)寫在@GET注解里。
這里直接拿上面的例子中的代碼就可以了。
new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build()
.create(IGitHubService.class)
.listRepos("aserbao");
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
第二種:我們?cè)赽aseUrl中添加https://api.github.com/users/,后面的參數(shù)添加到values里面也可以。
new Retrofit.Builder()
.baseUrl("https://api.github.com/users/") //這里注意最后有添加/。
.build()
.create(IGitHubService.class)
.listRepos("aserbao");
@GET("{user}/repos")
Call<List<Repo>> listRepos2(@Path("user") String user);
第三種:我們可以直接將請(qǐng)求鏈接放到參數(shù)里面。,
new Retrofit.Builder()
.baseUrl("")//baseUrl里面可以填空
.build()
.create(IGitHubService.class)
.listAbsRepos();
@GET("https://api.github.com/users/aserbao/repos")
Call<List<Repo>> listAbsRepos();
上面三種情況,在實(shí)際項(xiàng)目開發(fā)過程中我們使用的較多的還是第一種。
特別注意:baseUrl中添加的鏈接最后必須要添加/符號(hào),否則會(huì)報(bào)java.lang.IllegalArgumentException: baseUrl must end in / 的異常。
如何給Retrofit設(shè)置攔截器?
在請(qǐng)求的時(shí)候給設(shè)置請(qǐng)求攔截器是很有必要的一步,不僅可以讓我們清楚的了解請(qǐng)求內(nèi)容,快速定位請(qǐng)求過程中遇到的問題。還可以通過攔截請(qǐng)求添加通用參數(shù)和頭部字段。
那如何給Retrofit設(shè)置請(qǐng)求攔截器呢?前面也說了Retofit實(shí)際上市Okhttp的高度封裝,Okhttp如何設(shè)置,Retrofit就怎么配置即可。當(dāng)然,有興趣想了解更多關(guān)于Okhttp的內(nèi)容可以參考我的另外一篇文章HTTP 網(wǎng)絡(luò)請(qǐng)求庫 OkHttp 的全面講解。
好了,話不多說,咱們來看Retrofit如何設(shè)置攔截器。
步驟如下:
- 第一步:創(chuàng)建一個(gè)攔截器,這里我們打印請(qǐng)求的基本信息。
class LoggingInterceptor implements Interceptor {
@Override public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.e(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
okhttp3.Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.e(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
- 第二步:創(chuàng)建一個(gè)OkHttpClient,并配置攔截器。
OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS)
//添加OkHttp3的攔截器
.addInterceptor(new LoggingInterceptor())
.writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS)
.build();
- 第三部:通過client方法給Retrofit配飾OkHttpClient。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.baseUrl("https://api.github.com/users")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)//配置OkHttpClient
.build();
通過上面的列子,我們來看下請(qǐng)求運(yùn)行后的攔截效果:
11-24 10:05:12.023 4671-4690/com.example.baseandroidframework E/RetrofitActivity: Received response for https://api.github.com/users/aserbao/repos in 2495.2ms
Date: Tue, 26 Nov 2019 01:36:31 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Server: GitHub.com
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1574735790
Cache-Control: public, max-age=60, s-maxage=60
Vary: Accept
ETag: W/"58bffa073ed17b36ffb324dd04f66539"
X-GitHub-Media-Type: github.v3; format=json
Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type
Access-Control-Allow-Origin: *
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'
Vary: Accept-Encoding
X-GitHub-Request-Id: D99C:7F00:4DADB:61266:5DDC819D
好了,大功告成,我們配置攔截器就這樣完成了。
總結(jié)
首先和大家一起來回顧下這篇文章的大致內(nèi)容,主要講了關(guān)于Retrofit的三個(gè)方面的內(nèi)容,分別是:
- 什么是Retrofit?為什么要用Retrofit?如何使用Retrofit?
- Retrofit的常用注解以及使用場(chǎng)景。
- 講了如何給Retrofit設(shè)置攔截器。
其實(shí)通過文章我們可以發(fā)現(xiàn)RetrofitAPI并不多,正如官方所言,他是一個(gè)關(guān)于OkHttp的高度封裝庫。
好了,文章到這里就要結(jié)束了,如果對(duì)本文有什么疑問或者不理解的地方,歡迎給我留言。當(dāng)然,如果想系統(tǒng)性學(xué)Android,提升Android技術(shù)的朋友,可以關(guān)注我的個(gè)人公眾號(hào)「aserbaocool」,加群討論,一起學(xué)習(xí)交流Android。
