前言
2016年以來,越來越多Android開發(fā)者使用Retrofit
作為HTTP請求框架。原因其一,Google發(fā)布Android 6.0 SDK (API 23) 拋棄了HttpClient;其二,Square在2016.1.2發(fā)布okhttp3.0
、2016.3.11正式發(fā)布Retrofit 2.0
。
HttpClient時代
作為深受Apache HttpClient
毒害的一代青年,不得不吐槽HttpClient
的版本維護和API文檔有多糟糕。詬病纏身的HttpClient
從3.x到4.x,api變更面目全非,甚至4.0-4.5,api改動也不少。如果你以前使用3.x,升級到4.0后,http代碼幾乎全改了。大家可以看看Apache官網(wǎng)看看httpClient
發(fā)布歷史(3.x歷史、4.x歷史)。文檔嘛,Apache官網(wǎng)簡直....連程序猿這審美觀都不想看!
HttpClient
發(fā)展歷史相當長,最早是2001.10發(fā)布2.0-alpha 1
,2004.11發(fā)布3.0-beta1
,2008.1發(fā)布4.0-beta1
,直到2012.2才發(fā)布4.2-beta1
,2014.12發(fā)布4.4-release
,2016.1發(fā)布5.0-alpha
。由于源遠流長,httpClient
在國人心中根心蒂固。可以想象當年讀書(也就4年前嘻嘻_),翻墻未普及,天朝百度蠻橫,搜“java http請求”出來的幾乎都是httpClient
(不信你現(xiàn)在百度)。
2013年以來,Google逐漸意識到httpClient
的詬病,狠心之下,拋棄httpClient,因為我們有更好的選擇:okhttp
.
OkHttp
美國移動支付公司Square,在2013.5.6開源一款 java http請求框架——OkHttp. 發(fā)布之后,在國外迅速流行起來,一方面是httpClient
太繁瑣、更新慢,另一方面okHttp
確實好用。okHttp
發(fā)布之后不斷地改進,2014.5發(fā)布2.0-rc1
,2016.1發(fā)布3.0
,更新速度相當快,而且開發(fā)人員經(jīng)常對代碼進行維護,看看http://square.github.io/okhttp就知道了。相比之下,httpClient維護相當糟糕。
Api文檔方面,我非常喜歡Square公司的設(shè)計風格,okHttp
首頁相當簡潔,Overview、Example、Download全在首頁展示,詳細使用案例、說明,在github
上很清晰。
Retrofit
從發(fā)布歷史上來看,Retrofit
和okhttp
是兄弟,Square公司在2013.5.13發(fā)布1.0
,2015.8發(fā)布2.0-beta1
。
Retrofit
底層基于OkHttp
·,并且可以加很多Square開發(fā)的“周邊產(chǎn)品”:converter-gson
、adapter-rxjava
等。Retrofit
抱著gson
&rxjava
的大腿,這種聰明做法,也是最近大受歡迎的原因之一,所謂“Rxjava
火了,Retrofit
也火了”。Retrofit
·不僅僅支持這兩種周邊,我們可以自定義converter
&call adapter
,可以你喜歡的其他第三方庫。
介紹了主流java http請求庫歷史,大家對“為什么用retrofit”有個印象了吧?想想,如果沒有Square公司,apahce httpClient還將毒害多少無知青年。
何為非Restful Api?
Restful Api
User
數(shù)據(jù),有uid、name,Restful Api返回數(shù)據(jù):
{
"name": "kkmike999",
"uid": 1
}
在數(shù)據(jù)庫沒找到User,直接返回錯誤的http code。但弊端是當在瀏覽器調(diào)試api,后端查詢出錯時,很難查看錯誤碼&錯誤信息。(當然用chrome的開發(fā)者工具可以看,但麻煩)
Not Restful Api
但不少后端工程師,并不一定喜歡用Restful Api,他們會自己在json中加入ret、msg這種數(shù)據(jù)。當User正確返回:
{
"ret": 0,
"msg": "成功",
"data": {
"uid": 1,
"name": "kkmike999"
}
}
錯誤返回:
{
"ret": -1,
"msg": "失敗"
}
這樣的好處,就是調(diào)試api方便,在任意瀏覽器都可以直觀地看到錯誤碼&錯誤信息。
還有一個例子,百度地圖Web api
Retrofit一般用法
本來Retrofit
對restful
的支持,可以讓我們寫少很多冤枉代碼。但后端這么搞一套,前端怎么玩呀?既然木已成舟,我們做APP的總不能老對后端指手畫腳,友誼小船說翻就翻。
先說說retrofit
普通用法
public class User {
int uid;
String name;
}
public interface UserService {
@GET("not_restful/user/{name}.json")
Call<User> loadUser(@Path("name") String name);
}
Bean
和Service
準備好,接下來就是調(diào)用Retrofit
了:
OkHttpClient client = new OkHttpClient.Builder().build();
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://***.b0.upaiyun.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
UserService userService = retrofit.create(UserService.class);
User user = userService.loadUser("kkmike999")
.execute()
.body();
此處加入了GsonConverterFactory
,沒有使用RxJavaCallAdapter
。如果是restful api,直接返回User
的json
,那調(diào)用execute().body()
就能獲得正確的User
了。然而,not restful api,返回一個不正確的User
,也不拋錯,挺難堪的。
ResponseConverter
我們留意到GsonConverterFactory
,看看源碼:
package retrofit2.converter.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
public final class GsonConverterFactory extends Converter.Factory {
public static GsonConverterFactory create() {
return create(new Gson());
}
public static GsonConverterFactory create(Gson gson) {
return new GsonConverterFactory(gson);
}
private final Gson gson;
private GsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
}
responseBodyConverter
方法返回GsonResponseBodyConverter
,我們再看看GsonResponseBodyConverter
源碼:
package retrofit2.converter.gson;
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
}
先給大家科普下,TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
這里TypeAdapter
是什么。TypeAdapter
是gson
讓使用者自定義解析的json,Type
是service
方法返回值Call<?>
的泛型類型。UserService
中Call<User> loadUser(...)
,泛型參數(shù)是User
,所以type
就是User
類型。詳細用法參考:你真的會用Gson嗎?Gson使用指南(四)
重寫GsonResponseConverter
由源碼看出,是GsonResponseBodyConverter
對json
進行解析的,只要重寫GsonResponseBodyConverter
,自定義解析,就能達到我們目的了。
但GsonResponseBodyConverter
和GsonConverterFactory
都是final class
,并不能重寫。靠~ 不讓重寫,我就copy代碼!
新建retrofit2.converter.gson
目錄,新建CustomConverterFactory
,把GsonConverterFactory
源碼拷貝過去,同時新建CustomResponseConverter
。 把CustomConverterFactory
的GsonResponseBodyConverter
替換成CustomResponseConverter
:
public final class CustomConverterFactory extends Converter.Factory {
......
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new CustomResponseConverter<>(gson, adapter);
}
......
}
寫CustomResponseConverter
:
public class CustomResponseConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
public CustomResponseConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
String body = value.string();
JSONObject json = new JSONObject(body);
int ret = json.optInt("ret");
String msg = json.optString("msg", "");
if (ret == 0) {
if (json.has("data")) {
Object data = json.get("data");
body = data.toString();
return adapter.fromJson(body);
} else {
return (T) msg;
}
} else {
throw new RuntimeException(msg);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
value.close();
}
}
}
為什么我們要新建retrofit2.converter.gson
目錄?因為GsonRequestBodyConverter
不是public class
,所以CustomConverterFactory
要import GsonRequestBodyConverter
就得在同一目錄下。當然你喜歡放在自己目錄下,可以拷貝源碼如法炮制。
接下來,只要 new Retrofit.Builder().addConverterFactory(CustomConverterFactory.create())
就大功告成了!
更靈活的寫法
上述做法,我們僅僅踏入半條腿進門,為什么?萬一后端不喜歡全用"data",而是根據(jù)返回數(shù)據(jù)類型命名,例如返回User
用"user"
,返回Student
用"student"
呢?
{
"ret": 0,
"msg": "成功",
"user": {
"uid": 1,
"name": "小明"
}
}
{
"ret": 0,
"msg": "成功",
"student": {
"uid": 1,
"name": "小紅"
}
}
(此時是否有打死后端工程師的沖動?)
別怒,魔高一尺,道高一丈。
玩轉(zhuǎn)Service注解
既然retrofit
能“理解”service
方法中的注解,我們?yōu)楹尾辉囋嚕?code>GsonConverterFactory的方法responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
,這里有Annotation[]
,沒錯,這就是service
方法中的注解。
我們寫一個@Data
注解類:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Data {
String value() default "data";
}
在loadUser(...)
添加@Data
:
@Data("user")
@GET("not_restful/user/{name}.json")
Call<User> loadUser(@Path("name") String name);
修改CustomResponseConverter
public class CustomResponseConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
private final String name;
public CustomResponseConverter(Gson gson, TypeAdapter<T> adapter, String name) {
this.gson = gson;
this.adapter = adapter;
this.name = name;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
...
if (ret == 0) {
if (json.has(name)) {
Object data = json.get(name);
body = data.toString();
return adapter.fromJson(body);
}
...
}
}
給CustomConverterFactory
的responseBodyConverter(...)
加上
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
String name = "data";// 默認"data"
for (Annotation annotation : annotations) {
if (annotation instanceof Data) {
name = ((Data) annotation).value();
break;
}
}
...
return new CustomResponseConverter<>(gson, adapter, name);
}
這么寫后,后端改什么名稱都不怕!
更靈活的Converter
有個需求:APP顯示某班級信息&學生信息。后臺拍拍腦袋:
{
"ret": 0,
"msg": "",
"users": [
{
"name": "鳴人",
"uid": 1
},
{
"name": "佐助",
"uid": 2
}
],
"info": {
"cid": 7,
"name": "第七班"
}
}
哭了吧,滅了后端工程師恐怕也難解心頭之恨!
阿尼陀佛, 我不是說了嗎?
魔高又一尺,道又高一丈。
我們意識到,CustomResponseConverter
責任太重,又是判斷ret
、msg
,又是解析json
數(shù)據(jù)并返回bean
,如果遇到奇葩json,CustomResponseConverter
遠遠不夠強大,而且不靈活。
怎么辦,干嘛不自定義converter呢?
問題來了,這個converter應(yīng)該如何傳給CustomConverterFactory
?因為在new Retrofit.Builder().addConvertFactory(…)
時就要添加ConverterFactory
,那時并不知道返回json
是怎樣,哪個service
要用哪個adapter
。反正通過構(gòu)造方法給CustomConverterFactory
傳Converter
肯定行不通。
我們上面不是用過Annotaion嗎?同樣手段再玩一把如何。寫一個@Converter
注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Converter {
Class<? extends AbstractResponseConverter> converter();
}
并且寫一個Converter
抽象類:
public abstract class AbstractResponseConverter<T> implements Converter<ResponseBody, T>{
protected Gson gson;
public AbstractResponseConverter(Gson gson) {
this.gson = gson;
}
}
為什么要寫一個繼承Converter
抽象類?讓我們自定義的Converter
直接繼承Converter
不行嗎?
注意了,@Adapter
只能攜帶Class<?>
和int``String
等基本類型,并不能帶converter對象
。而我們需要CustomConverterFactory
在responseBodyConverter()
方法中,通過反射,new
一個converter對象
,而CustomConverterFactory
并不知道調(diào)用Converter
哪個構(gòu)造函數(shù),傳什么參數(shù)。所以,干脆就寫一個AbstractResponseConverter
,讓子類繼承它,實現(xiàn)固定的構(gòu)造方法。這樣CustomConverterFactory
就可以獲取固定的構(gòu)造方法,生成Converter對象并傳入如gson``typeAdapter
參數(shù)了。
public class ClazzInfo{
List<Student> students;
Info info;
}
public class ClassConverter implements AbstractResponseConverter<ClazzInfo>{
public ClassConverter(Gson gson){
super(gson);
}
@Override
public ClazzInfo convert(ResponseBody value) throws IOException {
// 這里你想怎么解析json就怎么解析啦
ClazzInfo clazz = ...
return clazz;
}
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for (Annotation annotation : annotations) {
if (annotation instanceof Converter) {
try {
Class<? extends AbstractResponseConverter> converterClazz = ((Converter) annotation). converter();
// 獲取有 以gson參數(shù)的 構(gòu)造函數(shù)
Constructor<? extends AbstractResponseConverter> constructor = converterClazz .getConstructor(Gson.class);
AbstractResponseConverter converter = constructor.newInstance(gson);
return converter;
} catch (Exception e) {
e.printStackTrace();
}
}
}
...
return new CustomResponseConverter<>(gson, adapter, name);
}
Service方法注解:
@Converter(converter = ClassConverter.class)
@GET("not_restful/class/{cid}.json")
Call<ClazzInfo> loadClass(@Path("cid") String cid);
寫到這里,已經(jīng)快吐血了。怎么會有這么奇葩的后端.... 正常情況下,應(yīng)該把"users"
和"class"
封裝在"data"
里,這樣我們就可以直接把返回結(jié)果寫成Call<ClassInfo>
就可以了。
小結(jié)
Retrofit
可以大量減少寫無謂的代碼,減少工作量之余,還能讓http層更加清晰、解耦。當你遇到非Restful Api時,應(yīng)該跟后端協(xié)商一種固定的json
格式,便于APP寫代碼。
代碼越少,錯得越少
同時,使用Retrofit讓你更容易寫單元測試。由于Retrofit
基于okhttp
,完全不依賴android
庫,所以可以用junit
直接進行單元測試,而不需要robolectric
或者在真機、模擬器上運行單元測試。之后有空我會寫關(guān)于Android單元測試的文章。
“我們可以相信的變革”( CHANGE WE CAN BELIEVE IN ) ——美國總統(tǒng)第44任總統(tǒng),奧巴馬
如果你還用httpClient
,請盡管大膽嘗試Retrofit
,don't afraid change,絕對給你意想不到的驚喜!并希望作為開發(fā)者的你,受此啟發(fā),寫出更加靈活的代碼。
關(guān)于作者
我是鍵盤男。
在廣州生活,在創(chuàng)業(yè)公司上班,猥瑣文藝碼農(nóng)。喜歡科學、歷史,玩玩投資,偶爾獨自旅行。希望成為獨當一面的工程師。