簡略分析Android的Retrofit應(yīng)用開發(fā)框架源碼
面對一個(gè)項(xiàng)目,對于Android應(yīng)用開發(fā)框架的選擇,我想過三種方案:
1.使用Loader + HttpClient + GreenDao + Gson + Fragment,優(yōu)點(diǎn)是可定制性強(qiáng),由于使用Google家自己的Loader和LoaderManager,代碼健壯性強(qiáng)。
缺點(diǎn)是整套代碼學(xué)習(xí)成本較高,使用過程中樣板代碼較多,(比如每一個(gè)Request都需要產(chǎn)生一個(gè)新類)
2.Volley,作為Google在IO大會上得瑟過的一個(gè)網(wǎng)絡(luò)庫,其實(shí)不算什么新東西(2013 IO發(fā)布),使用較為簡單,請求可以取消,可以提供優(yōu)先級請求,看起來還是不錯(cuò)的。
3.Retrofit,一款為了使請求極度簡單化的REST API Client,呼聲也很高,使用門檻幾乎是小白型。
如何選擇呢?首先干掉1,因?yàn)閷π氯说膶W(xué)習(xí)成本確實(shí)太高,如果要快速開發(fā)一個(gè)項(xiàng)目,高學(xué)習(xí)成本是致命的,同時(shí)使用起來樣板代碼很多。
那么如何在Volley和Retrofit中選擇呢?盡管網(wǎng)上有很多文章在介紹兩個(gè)框架的使用方法,而對于其原理,特別是對比分析較少,如果你手里有一個(gè)項(xiàng)目,如何選擇網(wǎng)絡(luò)模塊呢?
首先說明一下這兩個(gè)網(wǎng)絡(luò)框架在項(xiàng)目中的層次:
從上圖可知,不管Volley還是Retrofit,它們都是對現(xiàn)有各種方案進(jìn)行整合,并提供一個(gè)友好,快速開發(fā)的方案,在整合過程中,各個(gè)模塊都可以自行定制 或者替換。比如反序列化的工作,再比如HttpClient。
而在本文我們將簡略地來看一下Retrofit的源碼部分。
注意,本文并不是使用Retrofit的幫助文檔,建議先看Retrofit的文檔和OkHttp的文檔,這些對于理解余下部分很重要。
使用Retrofit發(fā)送一個(gè)請求
假設(shè)我們要從這個(gè)地址 http://www.exm.com/search.json?key=retrofit中獲取如下Json返回:
{ "data": [ { "title":"Retrofit使用簡介", "desc":"Retrofit是一款面向Android和Java的HttpClient", "link":"http://www.exm.com/retrofit" }, { "title":"Retrofit使用簡介", "desc":"Retrofit是一款面向Android和Java的HttpClient", "link":"http://www.exm.com/retrofit" }, { "title":"Retrofit使用簡介", "desc":"Retrofit是一款面向Android和Java的HttpClient", "link":"http://www.exm.com/retrofit" } ] }
1.引入依賴
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' //gson解析 compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
2.配置Retrofit
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://www.exm.com") .addConverterFactory(GsonConverterFactory.create()) .client(new OkHttpClient()) .build();
3.新建Model類 SearchResult來解析結(jié)果
4.新建請求接口
Retrofit使用注解來定義一個(gè)請求,在方法上面指定請求的方法等信息,在參數(shù)中指定參數(shù)等信息。
public interface RestApi { @GET("/search.json") Call<List<SearchResult>> search( @Query("key") String key ); //可以定義其它請求 @GET("/something.json") Call<SomeThing> dosomething( @Query("params") long params ....... ....... ); }
5.發(fā)送請求,我們可以發(fā)送同步請求(阻塞當(dāng)前線程)和異步請求,并在回調(diào)中處理請求結(jié)果。
RestApi restApi = retrofit.create(RestApi.class); Call<List<SearchResult>> searchResultsCall = resetApi.search("retrofit"); //Response<List<SearchResult> searchResults = searchResultsCall.execute(); 同步方法 searchResultsCall.enqueue(new Callback<List<SearchResult>>() { @Override public void onResponse(Response<List<SearchResult>> response, Retrofit retrofit) { content.setText(response.body().toString()); } @Override public void onFailure(Throwable t) { content.setText("error"); } });
Retrofit源碼分析
Retrofit整個(gè)項(xiàng)目中使用了動態(tài)代理和靜態(tài)代理,如果你不太清楚代理模式,建議先google一下,如果對于Java的動態(tài)代理原理不是太熟悉,強(qiáng)烈建議先看:這篇文章-戲說代理和Java動態(tài)代理
ok,下面按照我們使用Retrofit發(fā)送請求的步驟來:
RestApi restApi = retrofit.create(RestApi.class); //產(chǎn)生一個(gè)RestApi的實(shí)例
輸入一個(gè)接口,直接輸出一個(gè)實(shí)例。
這里岔開說一句,現(xiàn)在隨便在百度上搜一下Java動態(tài)代理,出來一堆介紹AOP(面向切面編程)和Spring,導(dǎo)致一部分人本末倒置,認(rèn)為動態(tài)代理幾乎等于AOP,甚至有些人認(rèn)為動態(tài)代理是專門在一個(gè)函數(shù)執(zhí)行前和執(zhí)行后添加一個(gè)操作,比如統(tǒng)計(jì)時(shí)間(因?yàn)楝F(xiàn)在幾乎所有介紹動態(tài)代理的地方都有這個(gè)例子),害人不淺。實(shí)際上動態(tài)代理是JDK提供的API,并不是由這些上層建筑決定的,它還可以做很多別的事情,Retrofit中對動態(tài)代理的使用就是佐證。
摟一眼這里的源碼,再次建議,如果這里代碼看不明白,先看看上面提到的那篇文章:
public <T> T create(final Class<T> service) { //返回一個(gè)動態(tài)代理類的實(shí)例 return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, //這個(gè)InvocationHandler是關(guān)鍵所在,以后調(diào)用restapi接口中的方法都會被發(fā)送到這里 new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { /* 如果是Object的方法,如toString()等,直接調(diào)用InvocationHandler的方法, *注意,這里其實(shí)是沒有任何意義的,因?yàn)镮nvocationHandler其實(shí)是一個(gè)命令傳送者 *在動態(tài)代理中,這些方法是沒有任何語義的,所以不需要在意 */ if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } //對于Java8的兼容,在Android中不需要考慮 if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } //返回一個(gè)Call對象 return loadMethodHandler(method).invoke(args); } }); }
我們可以看到Retrofit.create()之后,返回了一個(gè)接口的動態(tài)代理類的實(shí)例,那么我們調(diào)用這個(gè)代理類的方法時(shí),調(diào)用自然就被發(fā)送到我們定義的InvocationHandler中,所以調(diào)用
Call<List<SearchResult>> searchResultsCall = resetApi.search("retrofit");
時(shí),直接調(diào)用到InvocationHandler的invoke方法,下面是invoke此時(shí)的上下文:
@Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { //proxy對象就是你在外面調(diào)用方法的resetApi對象 //method是RestApi中的函數(shù)定義, //據(jù)此,我們可以獲取定義在函數(shù)和參數(shù)上的注解,比如@GET和注解中的參數(shù) //args,實(shí)際參數(shù),這里傳送的就是字符串"retrofit" //這里必然是return 一個(gè)Call對象 return loadMethodHandler(method).invoke(args); }
此時(shí),invoke的返回必然是一個(gè)Call,Call是Retrofit中對一個(gè)Request的抽象,由此,大家應(yīng)該不難想象到loadMethodHandler(method).invoke(args); 這句代碼應(yīng)該就是去解析接口中傳進(jìn)來的注解,并生成一個(gè)OkHttpClient中對應(yīng)的請求,這樣我們調(diào)用searchResultsCall時(shí),調(diào)用OkHttpClient走網(wǎng)絡(luò)即可。確實(shí),Retrofit的主旋律的確就是這樣滴。
注意,實(shí)際上Call,CallBack這種描述方式是在OkHttp中引入的,Retrofit底層使用OkHttp所以也是使用這兩個(gè)類名來抽象一個(gè)網(wǎng)絡(luò)請求和一個(gè)請求回來之后的回調(diào)??傮w來看,Retrofit中的Call Callback持有一個(gè)OkHttp的Call Callback,將對Retrofit中的各種調(diào)用轉(zhuǎn)發(fā)到OkHttp的類庫中,實(shí)際上這里就是靜態(tài)代理啦,因?yàn)槲覀儠x各種代理類,比如OkHttpCall
注 下文中如不無明確支出,則所有的Call,CallBack都是Retrofit中的類
MethodHandler類
MethodHandler類,它是Retrofit中最重要的抽象了,每一個(gè)MethodHandler對應(yīng)于本例的RestApi中的一個(gè)每個(gè)方法代表的請求以及和這個(gè)請求相關(guān)其它配置,我們來看看吧。
//這個(gè)OkHttp的工廠,用于產(chǎn)生一個(gè)OkHttp類庫中的Call,實(shí)際上就是傳入配置的Builder的OkHttpClient private final okhttp3.Call.Factory callFactory; //通過Method中的注解和傳入的參數(shù),組建一個(gè)OkHttp的Request private final RequestFactory requestFactory; //用于對Retrofit中的Call進(jìn)行代理 private final CallAdapter<?> callAdapter; //用于反序列化返回結(jié)果 private final Converter<ResponseBody, ?> responseConverter; // 返回一個(gè)Call對象 Object invoke(Object... args) { return callAdapter.adapt(new OkHttpCall<>(callFactory, requestFactory, args, responseConverter)); }
在Retrofit中通過添加Converter.Factory來為Retrofit添加請求和響應(yīng)的數(shù)據(jù)編碼和解析。所以我們可以添加多個(gè)Converter.Factory為Retrofit提供處理不同數(shù)據(jù)的功能。
CallAdapter 可以對執(zhí)行的Call進(jìn)行代理,這里是靜態(tài)代理。我們也可以通過添加自己的CallAdapter來作一些操作,比如為請求加上緩存:
new CallAdapter.Factory { @Override public <R> Call<R> adapt(Call<R> call) { return call;} } class CacheCall implements Call { Call delegate; CacheCall(Call call) { this.delegate = call; } @Override public void enqueue(Callback<T> callback) { //查看是否有緩存,否則直接走網(wǎng)絡(luò) if(cached) { return cache; } this.delegate.enqueue(callback); } }
至此,我們在調(diào)用resetApi.search("retrofit");時(shí),實(shí)際上調(diào)用的層層代理之后的OkHttpCall,它是MethodHandler中invoke的時(shí)候塞入的。看看OkHttpCall中的代碼吧:
@Override public void enqueue(final Callback<T> callback) { okhttp3.Call rawCall; try { //創(chuàng)建一個(gè)okhttp的Call rawCall = createRawCall(); } catch (Throwable t) { callback.onFailure(t); return; } //直接調(diào)用okhttp的入隊(duì)操作 rawCall.enqueue(new okhttp3.Callback() { private void callFailure(Throwable e) { try { callback.onFailure(e); } catch (Throwable t) { t.printStackTrace(); } } private void callSuccess(Response<T> response) { try { callback.onResponse(response); } catch (Throwable t) { t.printStackTrace(); } } @Override public void onFailure(Request request, IOException e) { callFailure(e); } @Override public void onResponse(okhttp3.Response rawResponse) { Response<T> response; try { //解析結(jié)果 response = parseResponse(rawResponse); } catch (Throwable e) { callFailure(e); return; } callSuccess(response); } }); }
如此一來,OkHttpClient的回調(diào)也被引導(dǎo)到我們的Callback上來,整個(gè)流程就已經(jīng)走通了。
總結(jié)
終于到了總結(jié)的時(shí)候了,一般來說,這都是干貨的時(shí)候,哈哈~
Volley對比優(yōu)勢
1.緩存處理;Volley自己就提供了一套完整的緩存處理方案,默認(rèn)使用文件存儲到磁盤中,并且提供了TTL SOFTTTL這么體貼入微的機(jī)制;一個(gè)Request可能存在兩個(gè)Response,對于需要顯示緩存,再顯示網(wǎng)絡(luò)數(shù)據(jù)的場景真是爽的不要不要的。而Retrofit中并沒有提供任何和緩存相關(guān)的方案。
2.代碼簡單,可讀性高。Volley的代碼是寫的如此的直接了當(dāng),讓你看起代碼來都不需要喝口茶,這樣的好處是我們我們需要修改代碼時(shí)不太容易引入bug....囧
同一個(gè)請求如果同時(shí)都在發(fā)送,那么實(shí)際上只會有一個(gè)請求真正發(fā)出去, 其它的請求會等待這個(gè)結(jié)果回來,算小小優(yōu)化吧。實(shí)際上這種場景不是很多哈,如果有,可能是你代碼有問題...
請求發(fā)送的時(shí)候提供了優(yōu)先級的概念,但是是只保證順序出去,不保證順序回來,然并卵。
支持不同的Http客戶端實(shí)現(xiàn),默認(rèn)提供了HttpClient和HttpUrlConnection的實(shí)現(xiàn),而Retrofit在2.0版本之后,鎖死在OkHttp上了。
3.支持請求取消
Retrofit
1.發(fā)送請求真簡單,定義一個(gè)方法就可以了,這么簡單的請求框架還有誰?Volley?
2.較好的可擴(kuò)展性,Volley中每一個(gè)新建一個(gè)Request時(shí)都需要指定一個(gè)父類,告知序列化數(shù)據(jù)的方式,而Retrofit中只需要在配置時(shí),指定各種轉(zhuǎn)換器即可。CallAdapter的存在,可以使你隨意代理調(diào)用的Call,不錯(cuò)不錯(cuò)。。。
3.OkHttpClient自帶并發(fā)光環(huán),而Volley中的工作線程是自己維護(hù)的,那么就有可能存在線程由于異常退出之后,沒有下一個(gè)工作線程補(bǔ)充的風(fēng)險(xiǎn)(線程池可以彌補(bǔ)這個(gè)缺陷),這在Retrofit中不存在,因?yàn)榧词褂袉栴},這個(gè)鍋也會甩給OkHttp,嘿嘿
4.支持請求取消
再次說明的是,Retrofit沒有對緩存提供任何額外支持,也就是說它只能通過HTTP的Cache control做文件存儲,這樣就會有一些問題:
1.需要Server通過Cache control頭部來控制緩存,需要修改后臺代碼
2.有些地方比較適合使用數(shù)據(jù)庫來存儲,比如關(guān)系存儲,此時(shí),Retrofit就無能為力了
3.緩存不在我們的控制范圍之內(nèi),而是完全通過OkHttp來管理,多少有些不便,比如我們要?jiǎng)h除某一個(gè)指定的緩存,或者更新某一個(gè)指定緩存,代碼寫起來很別扭(自己hack請求頭里面的cache contrl)
而在我們項(xiàng)目的實(shí)際使用過程中,緩存是一個(gè)比較重要的角色,Retrofit對緩存的支持度不是很好,真是讓人傷心。。。
但是,我們還是覺得在使用中Retrofit真心比較方便,容易上手,通過注解代碼可讀性和可維護(hù)性提升了N個(gè)檔次,幾乎沒有樣板代碼(好吧,如果你覺得每個(gè)請求都需要定義一個(gè)方法,那這也算。。),所以最后的決定是選擇Retrofit。
有人說了,Volley中的兩次響應(yīng)和緩存用起來很happy怎么辦?嗯,我們會修改Retrofit,使它支持文件存儲和ORM存儲,并將Volley的緩存 網(wǎng)絡(luò)兩次響應(yīng)回調(diào)移接過來,這個(gè)項(xiàng)目正在測試階段,待我們項(xiàng)目做完小白鼠,上線穩(wěn)定之后,我會把代碼開源,大家敬請關(guān)注。
- Android網(wǎng)絡(luò)請求框架Retrofit詳解
- Android Retrofit 2.0框架上傳圖片解決方案
- Android app開發(fā)中Retrofit框架的初步上手使用
- Retrofit和OkHttp如何實(shí)現(xiàn)Android網(wǎng)絡(luò)緩存
- Android Retrofit2網(wǎng)路編程實(shí)現(xiàn)方法詳解
- Android Retrofit2數(shù)據(jù)解析代碼解析
- Android中Retrofit的簡要介紹
- 基于Retrofit2+RxJava2實(shí)現(xiàn)Android App自動更新
- Android retrofit上傳文件實(shí)例(包含頭像)
- Android 封裝Okhttp+Retrofit+RxJava,外加攔截器實(shí)例
- Android Retrofit 中文亂碼問題的解決辦法
- Android使用 Retrofit 2.X 上傳多文件和多表單示例
- Android中Retrofit 2.0直接使用JSON進(jìn)行數(shù)據(jù)交互
- Android Retrofit框架的使用
相關(guān)文章
Android編程實(shí)現(xiàn)禁止StatusBar下拉的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)禁止StatusBar下拉的方法,涉及Android StatusBarManager相關(guān)屬性控制操作技巧,需要的朋友可以參考下2017-08-08基于Android6.0實(shí)現(xiàn)彈出Window提示框
這篇文章主要為大家詳細(xì)介紹了基于Android6.0實(shí)現(xiàn)彈出Window提示框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Android EdText編輯框禁止輸入表情符號(使用正則表達(dá)式)
這篇文章主要介紹了Android EdText編輯框禁止輸入表情符號使用正則表達(dá)式,需要的朋友可以參考下2017-06-06解決Android popupWindow設(shè)置背景透明度無效的問題
這篇文章主要介紹了解決Android popupWindow設(shè)置背景透明度無效的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08Android截取指定View為圖片的實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android截取指定View為圖片的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06Android 實(shí)現(xiàn)圖片模糊、高斯模糊、毛玻璃效果的三種方法
在前幾天寫過一個(gè)使用glide-transformations的方法實(shí)現(xiàn)高斯模糊的方法,今天偶然間有發(fā)現(xiàn)一個(gè)大神寫的另一個(gè)方法,感覺挺不錯(cuò)的,分享一下2016-12-12Android設(shè)備adb連接后顯示device unauthorized解決方案
這篇文章主要為大家介紹了Android設(shè)備adb連接后顯示device unauthorized解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Android SharedPreferences存儲用法詳解
這篇文章主要為大家詳細(xì)介紹了Android SharedPreferences存儲用法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02