前言
Retrofit是當(dāng)下比較熱門的一個(gè)網(wǎng)絡(luò)框架豫领,相信很多公司都在使用赞枕,即使你沒使用過這個(gè)框架窒舟,也應(yīng)該聽過
MVP+Retrofit+Rxjava 或者 Retrofit+Rxjava+Okhttp+MVP 這樣的組合怎憋。本篇只是一篇對Retrofit的入門介紹及簡單使用姨丈,后面再逐步框架上過渡,慢慢來埃难!
1 簡介
如果沒有一些基礎(chǔ)莹弊,像我一樣涤久,直接看官方文檔,估計(jì)也會(huì)看的一頭霧水箱硕,因?yàn)槲臋n很簡潔拴竹,上來就直接給出使用的方法。
需要的一些基礎(chǔ)知識還得自己去補(bǔ)剧罩,上一篇關(guān)于 Retrofit 中涉及到的一些基礎(chǔ),主要是 Http 和 RESTful Api座泳,需要補(bǔ)的童鞋可以看看
Retrofit2.0學(xué)習(xí)第一彈——準(zhǔn)備篇
Retrofit 是 Android 之神 JakeWharton 寫的網(wǎng)絡(luò)框架惠昔,github 地址。
Retrofit 是基于 OkHttp 進(jìn)行的封裝挑势,遵循 RESTful Api,通過注解來設(shè)計(jì)請求接口镇防,Retrofit 實(shí)際上是對網(wǎng)絡(luò)請求參數(shù)通過注解方式設(shè)置,包括 Header潮饱、URL等来氧,
然后請求操作通過 okhttp 來完成,從服務(wù)器返回的請求結(jié)果香拉,okhttp 又交給 Retrofit 根據(jù)需要進(jìn)行解析(通過 Converter)啦扬,
對于開發(fā)者來說,更加簡潔和方便凫碌。OkHttp 也是 square 公司開發(fā)的網(wǎng)絡(luò)框架扑毡,JakeWharton 也是主要開發(fā)者之一。
2 特點(diǎn)
對于 Retrofit 的描述盛险,官方給出了這樣一句話:
A type-safe HTTP client for Android and Java
意思是說 Retrofit 是方便 Android 和 Java 使用的一種 Http 安全客戶端請求框架瞄摊。
下面給出一張圖,說明 Retrofit 的主要特點(diǎn)苦掘。
另外换帜,既然 Retrofit 是依賴 Okhttp,那么到底他們之間的關(guān)系是怎樣的呢鹤啡?惯驼,再來一張圖。
理清了關(guān)系揉忘,我們該干什么呢跳座?首先肯定是先動(dòng)手實(shí)踐一下,看看 Retrofit 如何使用泣矛,是否真的很好用疲眷,然后要想深入研究的話,可以去看源碼啦您朽。
3 使用
步驟一:添加依賴庫
1. 在 build.gradle 中添加依賴庫狂丝,由于 Retrofit 依賴 OkHttp换淆,所以還需要添加 okhttp 的依賴,版本號可以在官網(wǎng)上的 Github 中獲取几颜。
dependencies {
//引入okhttp
compile 'com.squareup.okhttp3:okhttp:3.5.0'
//引入retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
//引入rxjava
//compile 'io.reactivex.rxjava2:rxjava:2.0.4'
//引入Log攔截器倍试,方便DEBUG模式輸出log信息
//compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
//引入json轉(zhuǎn)換器,方便將返回的數(shù)據(jù)轉(zhuǎn)換為json格式
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
}
步驟二:建立數(shù)據(jù)實(shí)體類
這里使用的是郭神《第一行代碼》天氣例子中的城市請求的 URL蛋哭。
public class City {
int id;
String name;
public String toString() {
return "[id = " + id + ", name = " + name + "]";
}
}
步驟三:設(shè)計(jì)請求接口
Retrofit 請求通過 java 接口創(chuàng)建县习,這句話描述的不太清楚,實(shí)際上我們使用時(shí)谆趾,只要將 請求方法放在接口中躁愿,通過注解方式對請求參數(shù)進(jìn)行描述和配置,調(diào)用方法的實(shí)例是框架通過動(dòng)態(tài)代理完成的沪蓬,在源碼中能夠找到彤钟。
有以下兩點(diǎn)需要注意:
設(shè)計(jì)的接口不能繼承和嵌套,嵌套意思是在一個(gè)接口中不能再有一個(gè)接口跷叉,這一點(diǎn)也是實(shí)驗(yàn)過程中發(fā)現(xiàn)逸雹,想了下,如果熟悉注解的話云挟,應(yīng)該能夠理解梆砸,注解也是不能繼承和嵌套。
接口中的所有方法都需要進(jìn)行注解植锉,注解相應(yīng)的網(wǎng)絡(luò)請求操作辫樱。
public interface GetService {
@GET("api/china")
Call<List<Province>> getProvinces();
}
上面是最簡單的一個(gè)網(wǎng)絡(luò)請求接口,下面對注解的各種類型進(jìn)行說明俊庇。
1 請求方法注解
方法注解常用的有這幾個(gè):@GET狮暑、@POST、@PUT辉饱、@PATCH 搬男、@DELETE、@HEAD 彭沼、@OPTIONS缔逛、@HTTP
注解對應(yīng)著 Http 中的方法,這在上一篇文章的講解中已有了詳細(xì)的說明姓惑,是對應(yīng)著請求的具體的動(dòng)作褐奴。其中,GET于毙、POST最常用敦冬,HTTP也可能用到,反正我沒用到唯沮,那就對這三個(gè)進(jìn)行說明脖旱,其他的遇到了再補(bǔ)充堪遂。
@GET
上面例子中就是 @GET 的請求,注解 @GET 的方法萌庆,向服務(wù)器請求指定 URL 地址的資源溶褪,數(shù)據(jù)流方向:服務(wù)器--->客戶端,使用時(shí)一般會(huì)配合 @Path践险,通過 @Path 注解方法參數(shù)猿妈,使得請求的 URL 能夠動(dòng)態(tài)改變,更加靈活捏境,后面會(huì)對 @Path 詳細(xì)講解于游。
@POST
注解成 @POST 方法,表明向服務(wù)器發(fā)送數(shù)據(jù)垫言,所以數(shù)據(jù)流方向很明確,客戶端--->服務(wù)器
@POST 注解的方法向服務(wù)器發(fā)送數(shù)據(jù)倾剿,主要 Form 表單類型筷频,一般配合 FormUrlEncoded 或者 MultiPart 注解。
@HTTP
@HTTP 是概括性的注解方法前痘,具體的參數(shù)是在后面括號中進(jìn)行說明凛捏,這里只給出請求方法的配置,還有其他參數(shù)可以設(shè)置芹缔,如 path,hasBody 等
/**
* method:網(wǎng)絡(luò)請求的方法坯癣,對應(yīng) HTTP 的請求方法
*/
@HTTP(method = "GET",path = "api/china")
Call<ResponseBody> getCall();
//等同于
@GET("api/china")
Call<List<Province>> getProvinces();
2 方法標(biāo)記類注解
方法注解類參數(shù)主要是對方法進(jìn)行一個(gè)標(biāo)記最欠,不同于方法請求示罗,主要對數(shù)據(jù)類型進(jìn)行說明。
@FormUrlEncoded
用于 POST 請求方法芝硬,注解后蚜点,表明發(fā)送 Form 表單數(shù)據(jù),與 @Field 或 @FieldMap 配合使用拌阴。
public interface PostService {
@POST("path")
@FormUrlEncoded
Call<Translation> getTranslate(@Field("key") String value);
//或者使用FieldMap绍绘,根據(jù)需要來選擇
@POST("path")
@FormUrlEncoded
Call<Translation> getTranslate(@FieldMap Map<String,String> maps);
}
@MultiPart
使用 @MultiPart 標(biāo)記的方法中可以傳遞三種類型的數(shù)據(jù):
(1)RequestBody,參數(shù)中需要使用 @Part 標(biāo)記字段
(2)MultipartBody.Part 類型,@Part 標(biāo)記迟赃,但不需要注明字段陪拘,一般用于文件
(3)其他類型,如 String 類型或者其他自定義類型
直接來上代碼:
public interface UpLoadService {
//1 upload a image
@Multipart
@POST("user")
Call<ResponseBody> upload(@Part("file\";filename=\"image.jpg") RequestBody file);
//2 upload some images,the number is certain
@Multipart
@POST("user")
Call<ResponseBody> upload(@Part("file\";filename=\"image1.jpg") RequestBody file1,@Part("file\";filename=\"image2.jpg") RequestBody file2,@Part("file\";filename=\"image3.jpg") RequestBody file3);
//3 upload one image and text
@Multipart
@POST("user")
Call<ResponseBody> upload(@Part("description") String description,
@Part MultipartBody.Part file);
//4 upload one image and text
@Multipart
@POST("user")
Call<ResponseBody> upload(@Part("description") RequestBody description,@Part MultipartBody.Part file);
/**********************************************************************/
//5 upload images and text
@Multipart
@POST("user")
Call<ResponseBody> upload(@Part("description") RequestBody description,
@PartMap() Map<String,RequestBody> maps);
}
下面一個(gè)一個(gè)分析:
1 和 2 是用來上傳圖片的纤壁,只不過 2 是用來上傳多張圖片左刽。可能一上來有些迷糊摄乒,這就是上一篇文章中提到的關(guān)于請求的消息結(jié)構(gòu)部分組成悠反,這里再拿出來說明一下残黑。
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
//第一部分,文本
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
//第二部分斋否,圖片
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="image1.jpg" //注意這里的 name 和 filename
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
需要上傳 RequestBody梨水,而 @Part 部分注明就是圖片的 Content-Disposition:
看注釋部分的 name 和 filename,就是 @Part("file";filename="image1.jpg") 給出的茵臭。這里沒有實(shí)際驗(yàn)證疫诽,因?yàn)槲覜]有自己搭建后臺部分,所示這里只是說明旦委。
3 和 4 方法是一樣的奇徒,實(shí)現(xiàn)文字和圖片同時(shí)上傳,MultipartBody.Part 部分的 @Part 不需要給出相應(yīng)的字段缨硝。另外摩钙,為啥給出 3 和 4 這兩種方式,看起來 3 更加簡單查辩,但是有篇文章 Retrofit上傳單圖胖笛、多圖、圖文 說上傳圖文時(shí)遇到了坑宜岛,使用 String 類型的話长踊,服務(wù)端接收到的參數(shù)會(huì)帶雙引號。這里仍需要實(shí)際驗(yàn)證萍倡,我沒有做驗(yàn)證身弊,為了保險(xiǎn)起見,使用 RequestBody 類型肯定沒有問題列敲。
5 實(shí)現(xiàn)的是多張圖片和文字的上傳阱佛,使用 @PartMap() 進(jìn)行標(biāo)記,其中 Map< String,RequestBody > 也可換成 List< MultipartBody.Part>酿炸,同樣能夠?qū)崿F(xiàn)多張圖片上傳瘫絮。
使用過程
private void upload() {
UpLoadService upLoadService = getService("http://webset", UpLoadService.class);
File file1 = new File("d:/1/test1.jpg");
File file2 = new File("d:/1/test2.jpg");
File file3 = new File("d:/1/test3.jpg");
//upload serveral images, the number is certain
RequestBody requestFile1 = RequestBody.create(MediaType.parse("multipart/form-data"), file1);
RequestBody requestFile2 = RequestBody.create(MediaType.parse("multipart/form-data"), file2);
RequestBody requestFile3 = RequestBody.create(MediaType.parse("multipart/form-data"), file3);
Map<String, RequestBody> maps = new HashMap<>();
//the key is the field in the database
maps.put("image1", requestFile1);
maps.put("image2", requestFile2);
maps.put("image3", requestFile3);
String describString = "this are images and text";
RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), describString);
Call<ResponseBody> uploadImagesAndTextCall = upLoadService.upload(description, maps);
//execute the call
uploadImagesAndTextCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e("tag", "upload the images and text success");
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("tag", "upload the image and text failed");
}
});
}
Content-Type
Content-Type即內(nèi)容類型,上面使用的都是 multipart/form-data填硕,代表二進(jìn)制數(shù)據(jù)類型麦萤,對于圖片也可以使用 image/jpg 格式,對于 Json 類型可以使用 application/json 類型
City city = new City(1, "Beijing");
String jsonString = new Gson().toJson(city);
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8")
, jsonString);
小結(jié)
@MultiPart 很不好理解扁眯,反正我是看了很久壮莹,才有了一些理解,另外還需要實(shí)際操作姻檀,加深理解命满,實(shí)際使用中可能不需要這么多方式,那就根據(jù)需求選擇其中一種先用起來绣版,然后再改進(jìn)胶台。
@Streaming
嗯歼疮,沒用過,自己腦補(bǔ)去吧诈唬,哈哈韩脏!
3 方法參數(shù)注解
方法參數(shù)這部分也很多,為了方便理解和記憶铸磅,有些可以看成是相同的赡矢,像 @Field 和 @FieldMap,@Query 和 @QueryMap阅仔,@Part 和 @PartMap吹散,@Headers 和 @Header(這兩個(gè)和前面三對還有些區(qū)別)。
@Path
@GET("api/{Country}")
Call<List<Province>> getProvinces(@Path("Country") String country);
通過 Path 注解八酒,可以將 GET 中{Country}部分進(jìn)行動(dòng)態(tài)替換空民,這個(gè)很好理解。
@Url
@Url 注解同樣可設(shè)置 URL羞迷,達(dá)到上面 GET 后面設(shè)置的效果袭景。
@GET
Call<List<Province>> getProvinces(@Url String url);
@Field 和 @FieldMap
在進(jìn)行 POST 請求時(shí)提交請求的表單字段,可以理解為鍵值對闭树,與 @FormUrlEncoded 配合使用,在 @FormUrlEncoded 說明是也提到了荒澡,表單格式的請求類型為 Content-Type:application/x-www-form-urlencoded报辱,也是請求消息結(jié)構(gòu)的默認(rèn)類型。
@Query 和 @QueryMap
如单山,我要訪問下面這個(gè)URL碍现,在 ?之后是查詢參數(shù)米奸,這些參數(shù)就可以通過 @Query 來動(dòng)態(tài)設(shè)定昼接。
查詢參數(shù)分解為鍵值對:
key--------value
a-------------fy
f------------auto
t------------auto
w---------hello%20world
使用 @Query 對每一個(gè)鍵值對進(jìn)行參數(shù)設(shè)置,當(dāng)有多個(gè)鍵值對時(shí)悴晰,就可以通過 @QueryMap
public interface QueryService {
//baseurl 為http://fy.iciba.com/
@GET("ajax.php")
Call<Message> getMessage(@Query("a") String param1,
@Query("f") String param2, @Query("t") String param3,
@Query("w") String param4);
@GET("{message}")
Call<Message> getMessage(@QueryMap Map<String, String> params);
}
使用
//getService 創(chuàng)建接口實(shí)例的方法慢睡,重點(diǎn)看下面的代碼
QueryService getService = (QueryService) getService("http://fy.iciba.com/", GetService.class);
/**
* url
* http://fy.iciba.com/ajax.php?a=fy&f=auto&t=auto&w=hello%20world
*/
//@Query
Call<Message> call = getService.getMessage("fy","auto","auto","hello%20world");
//@QueryMap
Map<String, String> paramsMap = new HashMap<>();
paramsMap.put("a", "fy");
paramsMap.put("f", "auto");
paramsMap.put("t", "auto");
paramsMap.put("w", "hello%20world");
Call<Message> call = getService.getMessage(paramsMap);
@Body
@Body 用來封裝自定義類型的數(shù)據(jù), 一般是通過實(shí)體對象進(jìn)行封裝的數(shù)據(jù)類型铡溪,如String 或者自己定義的實(shí)體類型漂辐。
實(shí)體類
class User{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
請求接口
public interface UserService {
@POST("api/users")
Call<ResponseBody> uploadUser(@Body User user);
}
調(diào)用
Call<ResponseBody> call = userService.uploadUser(new User("name",24));
需要注意的是,在服務(wù)器端需要自己進(jìn)行解析棕硫,不能直接獲取相應(yīng)字段數(shù)據(jù)髓涯。
@Part 和 @PartMap
這個(gè)不用多說了,在上面方法標(biāo)記類型里列出了使用方法哈扮,這部分可能稍微有點(diǎn)不好理解纬纪,多看一些文章蚓再,多練習(xí),著重看下消息結(jié)構(gòu)組成包各,就可以完全搞明白摘仅。
@Header 和 @Headers
這兩個(gè)注解都是作用于請求頭,區(qū)別在于 @Header 用于不固定的請求頭髓棋,@Headers 用于添加固定請求頭实檀,關(guān)于請求頭的概念,需要去了解下消息結(jié)構(gòu)的 header 部分按声。
@Headers 設(shè)置請求頭的Content-type膳犹,即設(shè)置編碼格式,固定請求頭 意思是使用這個(gè)請求接口的編碼格式都是一樣的签则。
public interface HeadersService {
@Headers("Content-type:application/x-www-form-urlencoded;charset=UTF-8")
@POST("api/users")
Call<ResponseBody> uploadUser(@Field("username") String username);
}
@Header 設(shè)定動(dòng)態(tài)設(shè)置請求頭編碼格式须床,在調(diào)用方式,通過參數(shù)傳遞
public interface HeaderService {
@POST("api/users")
Call<ResponseInfo> uploadUser(@Header("Content-Type") String contentType,@Field("username") String username);
}
// 調(diào)用
Call<ResponseBody> call = service.uploadNewUser("application/x-www-form-urlencoded;charset=UTF-8","Ralf");
當(dāng)然渐裂,請求頭部分還有其他可以設(shè)置的豺旬,根據(jù)實(shí)際需要進(jìn)行設(shè)置。
至此柒凉,經(jīng)歷漫長的過程族阅,各種注解類型已經(jīng)講完,是不是有點(diǎn)多膝捞,不過不要緊坦刀,實(shí)際中使用用到的應(yīng)該不會(huì)太多吧,為啥不確定呢? 因?yàn)槲覜]用過啊蔬咬,也只是學(xué)習(xí)階段鲤遥,哈哈!在腦圖中也已經(jīng)標(biāo)出來重點(diǎn)了林艘。
步驟四:創(chuàng)建 Retrofit 實(shí)例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(urlStr)
.addConverterFactory(GsonConverterFactory.create()) // Gson 數(shù)據(jù)解析器
//.addConverterFactory(MyConverterFactory.create()) // 自定義數(shù)據(jù)解析器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // RxJava 適配器
.build();
創(chuàng)建 Retrofit 實(shí)例可以設(shè)置解析器和適配器盖奈。
Adapter
Retrofit主要支持這幾種網(wǎng)絡(luò)請求適配器:guava、Java8 和 Rxjava
如果不設(shè)置適配器狐援,則使用 Android 默認(rèn)的 CallAdapter 钢坦。
Adopter | gradle |
---|---|
guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
RxJava | compile 'io.reactivex.rxjava2:rxjava:2.0.4' |
Converter
數(shù)據(jù)解析器,主要由以下幾種類型咕村,另外可以自己定義數(shù)據(jù)解析器
Converter | gradle |
---|---|
Gson | com.squareup.retrofit2:converter-gson:2.1.0 |
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 |
自定義數(shù)據(jù)解析器
解析出實(shí)體類场钉,如獲取城市的 JsonArray,解析出實(shí)體類懈涛,返回一個(gè) List
http://guolin.tech/api/china/12
[{
"id": 57,
"name": "石家莊"
}, {
"id": 58,
"name": "保定"
}, {
"id": 59,
"name": "張家口"
}, {
"id": 60,
"name": "唐山"
}, {
"id": 61,
"name": "廊坊"
}, {
"id": 62,
"name": "滄州"
}, {
"id": 63,
"name": "衡水"
}, {
"id": 64,
"name": "邢臺"
}, {
"id": 65,
"name": "邯鄲"
}, {
"id": 66,
"name": "秦皇島"
}]
public class MyConverterFactory extends Converter.Factory {
public static MyConverterFactory create() {
return new MyConverterFactory();
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return new MyConverter();
}
class MyConverter implements Converter<ResponseBody, List<City>> {
@Override
public List<City> convert(ResponseBody value) throws IOException {
List<City> resultList = new ArrayList<>();
String cityInfo = value.string();
if (cityInfo == null || TextUtils.isEmpty(cityInfo)) {
return resultList;
}
try {
JSONArray jsonArray = new JSONArray(cityInfo);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject object = jsonArray.getJSONObject(i);
String cityName = object.getString("name");
int cityId = object.getInt("id");
resultList.add(new City(cityId, cityName));
}
} catch (JSONException e) {
e.printStackTrace();
}
return resultList;
}
}
}
以上作用完全可以使用 GsonConverterFactory逛万,這里自己解析主要是熟悉一下自定義數(shù)據(jù)解析器的流程。
步驟五:獲取 Call 實(shí)例
Call<Message> call = retrofit.create(XxService.class);
步驟六:執(zhí)行網(wǎng)絡(luò)請求 和 返回?cái)?shù)據(jù)處理
異步
call.enqueue(new Callback<Message>() {
//請求成功回調(diào)該函數(shù)
@Override
public void onResponse(Call<Message> call, Response<Message> response) {
Message message = response.body(); // 返回?cái)?shù)據(jù)部分
message.show();
}
//請求失敗回調(diào)該函數(shù)
@Override
public void onFailure(Call<Message> call, Throwable t) {
}
});
同步
try {
Response<Message> response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
小結(jié)
Retrofit 網(wǎng)絡(luò)請求按照上面 6 個(gè) 步驟就可以正常運(yùn)行了。需要注意以下幾點(diǎn):
定義的接口類型要與服務(wù)店返回的類型對應(yīng)宇植,否則會(huì)解析錯(cuò)誤
在 AndroidManifest 中需要設(shè)置權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
- 異步請求中得封,不需要再單獨(dú)再開啟線程
- 每個(gè)請求接口放在一個(gè)文件中,不能繼承
還有需要補(bǔ)充的地方指郁, 大家可以一起來討論一下忙上,避免一些不必要的坑。
后續(xù)將學(xué)習(xí)梳理一下Rxjava的結(jié)合闲坎,以及攔截器和一些封裝操作疫粥,敬請期待!