面對一個項目,對于android應用開發框架的選擇,我想過三種方案:
1.使用loader + httpclient + greendao + gson + fragment,優點是可定制性強,由于使用google家自己的loader和loadermanager,代碼健壯性強。
缺點是整套代碼學習成本較高,使用過程中樣板代碼較多,(比如每一個request都需要產生一個新類)
2.volley,作為google在io大會上得瑟過的一個網絡庫,其實不算什么新東西(2013 io發布),使用較為簡單,請求可以取消,可以提供優先級請求,看起來還是不錯的。
3.retrofit,一款為了使請求極度簡單化的rest api client,呼聲也很高,使用門檻幾乎是小白型。
如何選擇呢?首先干掉1,因為對新人的學習成本確實太高,如果要快速開發一個項目,高學習成本是致命的,同時使用起來樣板代碼很多。
那么如何在volley和retrofit中選擇呢?盡管網上有很多文章在介紹兩個框架的使用方法,而對于其原理,特別是對比分析較少,如果你手里有一個項目,如何選擇網絡模塊呢?
首先說明一下這兩個網絡框架在項目中的層次:
從上圖可知,不管volley還是retrofit,它們都是對現有各種方案進行整合,并提供一個友好,快速開發的方案,在整合過程中,各個模塊都可以自行定制 或者替換。比如反序列化的工作,再比如httpclient。
而在本文我們將簡略地來看一下retrofit的源碼部分。
注意,本文并不是使用retrofit的幫助文檔,建議先看retrofit的文檔和okhttp的文檔,這些對于理解余下部分很重要。
使用retrofit發送一個請求
假設我們要從這個地址 http://www.exm.com/search.json?key=retrofit中獲取如下json返回:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
{ "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.引入依賴
1
2
3
|
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' //gson解析 compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' |
2.配置retrofit
1
2
3
4
5
|
retrofit retrofit = new retrofit.builder() .baseurl( "http://www.exm.com" ) .addconverterfactory(gsonconverterfactory.create()) .client( new okhttpclient()) .build(); |
3.新建model類 searchresult來解析結果
4.新建請求接口
retrofit使用注解來定義一個請求,在方法上面指定請求的方法等信息,在參數中指定參數等信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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.發送請求,我們可以發送同步請求(阻塞當前線程)和異步請求,并在回調中處理請求結果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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整個項目中使用了動態代理和靜態代理,如果你不太清楚代理模式,建議先google一下,如果對于java的動態代理原理不是太熟悉,強烈建議先看:這篇文章-戲說代理和java動態代理
ok,下面按照我們使用retrofit發送請求的步驟來:
1
|
restapi restapi = retrofit.create(restapi. class ); //產生一個restapi的實例 |
輸入一個接口,直接輸出一個實例。
這里岔開說一句,現在隨便在百度上搜一下java動態代理,出來一堆介紹aop(面向切面編程)和spring,導致一部分人本末倒置,認為動態代理幾乎等于aop,甚至有些人認為動態代理是專門在一個函數執行前和執行后添加一個操作,比如統計時間(因為現在幾乎所有介紹動態代理的地方都有這個例子),害人不淺。實際上動態代理是jdk提供的api,并不是由這些上層建筑決定的,它還可以做很多別的事情,retrofit中對動態代理的使用就是佐證。
摟一眼這里的源碼,再次建議,如果這里代碼看不明白,先看看上面提到的那篇文章:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public <t> t create( final class <t> service) { //返回一個動態代理類的實例 return (t) proxy.newproxyinstance(service.getclassloader(), new class <?>[] { service }, //這個invocationhandler是關鍵所在,以后調用restapi接口中的方法都會被發送到這里 new invocationhandler() { private final platform platform = platform.get(); @override public object invoke(object proxy, method method, object... args) throws throwable { /* 如果是object的方法,如tostring()等,直接調用invocationhandler的方法, *注意,這里其實是沒有任何意義的,因為invocationhandler其實是一個命令傳送者 *在動態代理中,這些方法是沒有任何語義的,所以不需要在意 */ if (method.getdeclaringclass() == object. class ) { return method.invoke( this , args); } //對于java8的兼容,在android中不需要考慮 if (platform.isdefaultmethod(method)) { return platform.invokedefaultmethod(method, service, proxy, args); } //返回一個call對象 return loadmethodhandler(method).invoke(args); } }); } |
我們可以看到retrofit.create()之后,返回了一個接口的動態代理類的實例,那么我們調用這個代理類的方法時,調用自然就被發送到我們定義的invocationhandler中,所以調用
1
|
call<list<searchresult>> searchresultscall = resetapi.search( "retrofit" ); |
時,直接調用到invocationhandler的invoke方法,下面是invoke此時的上下文:
1
2
3
4
5
6
7
8
9
10
11
|
@override public object invoke(object proxy, method method, object... args) throws throwable { //proxy對象就是你在外面調用方法的resetapi對象 //method是restapi中的函數定義, //據此,我們可以獲取定義在函數和參數上的注解,比如@get和注解中的參數 //args,實際參數,這里傳送的就是字符串"retrofit" //這里必然是return 一個call對象 return loadmethodhandler(method).invoke(args); } |
此時,invoke的返回必然是一個call,call是retrofit中對一個request的抽象,由此,大家應該不難想象到loadmethodhandler(method).invoke(args); 這句代碼應該就是去解析接口中傳進來的注解,并生成一個okhttpclient中對應的請求,這樣我們調用searchresultscall時,調用okhttpclient走網絡即可。確實,retrofit的主旋律的確就是這樣滴。
注意,實際上call,callback這種描述方式是在okhttp中引入的,retrofit底層使用okhttp所以也是使用這兩個類名來抽象一個網絡請求和一個請求回來之后的回調。總體來看,retrofit中的call callback持有一個okhttp的call callback,將對retrofit中的各種調用轉發到okhttp的類庫中,實際上這里就是靜態代理啦,因為我們會定義各種代理類,比如okhttpcall
注 下文中如不無明確支出,則所有的call,callback都是retrofit中的類
methodhandler類
methodhandler類,它是retrofit中最重要的抽象了,每一個methodhandler對應于本例的restapi中的一個每個方法代表的請求以及和這個請求相關其它配置,我們來看看吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//這個okhttp的工廠,用于產生一個okhttp類庫中的call,實際上就是傳入配置的builder的okhttpclient private final okhttp3.call.factory callfactory; //通過method中的注解和傳入的參數,組建一個okhttp的request private final requestfactory requestfactory; //用于對retrofit中的call進行代理 private final calladapter<?> calladapter; //用于反序列化返回結果 private final converter<responsebody, ?> responseconverter; // 返回一個call對象 object invoke(object... args) { return calladapter.adapt( new okhttpcall<>(callfactory, requestfactory, args, responseconverter)); } |
在retrofit中通過添加converter.factory來為retrofit添加請求和響應的數據編碼和解析。所以我們可以添加多個converter.factory為retrofit提供處理不同數據的功能。
calladapter 可以對執行的call進行代理,這里是靜態代理。我們也可以通過添加自己的calladapter來作一些操作,比如為請求加上緩存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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) { //查看是否有緩存,否則直接走網絡 if (cached) { return cache; } this .delegate.enqueue(callback); } } |
至此,我們在調用resetapi.search("retrofit");時,實際上調用的層層代理之后的okhttpcall,它是methodhandler中invoke的時候塞入的。看看okhttpcall中的代碼吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
@override public void enqueue( final callback<t> callback) { okhttp3.call rawcall; try { //創建一個okhttp的call rawcall = createrawcall(); } catch (throwable t) { callback.onfailure(t); return ; } //直接調用okhttp的入隊操作 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 { //解析結果 response = parseresponse(rawresponse); } catch (throwable e) { callfailure(e); return ; } callsuccess(response); } }); } |
如此一來,okhttpclient的回調也被引導到我們的callback上來,整個流程就已經走通了。
總結
終于到了總結的時候了,一般來說,這都是干貨的時候,哈哈~
volley對比優勢
1.緩存處理;volley自己就提供了一套完整的緩存處理方案,默認使用文件存儲到磁盤中,并且提供了ttl softttl這么體貼入微的機制;一個request可能存在兩個response,對于需要顯示緩存,再顯示網絡數據的場景真是爽的不要不要的。而retrofit中并沒有提供任何和緩存相關的方案。
2.代碼簡單,可讀性高。volley的代碼是寫的如此的直接了當,讓你看起代碼來都不需要喝口茶,這樣的好處是我們我們需要修改代碼時不太容易引入bug....囧
同一個請求如果同時都在發送,那么實際上只會有一個請求真正發出去, 其它的請求會等待這個結果回來,算小小優化吧。實際上這種場景不是很多哈,如果有,可能是你代碼有問題...
請求發送的時候提供了優先級的概念,但是是只保證順序出去,不保證順序回來,然并卵。
支持不同的http客戶端實現,默認提供了httpclient和httpurlconnection的實現,而retrofit在2.0版本之后,鎖死在okhttp上了。
3.支持請求取消
retrofit
1.發送請求真簡單,定義一個方法就可以了,這么簡單的請求框架還有誰?volley?
2.較好的可擴展性,volley中每一個新建一個request時都需要指定一個父類,告知序列化數據的方式,而retrofit中只需要在配置時,指定各種轉換器即可。calladapter的存在,可以使你隨意代理調用的call,不錯不錯。。。
3.okhttpclient自帶并發光環,而volley中的工作線程是自己維護的,那么就有可能存在線程由于異常退出之后,沒有下一個工作線程補充的風險(線程池可以彌補這個缺陷),這在retrofit中不存在,因為即使有問題,這個鍋也會甩給okhttp,嘿嘿
4.支持請求取消
再次說明的是,retrofit沒有對緩存提供任何額外支持,也就是說它只能通過http的cache control做文件存儲,這樣就會有一些問題:
1.需要server通過cache control頭部來控制緩存,需要修改后臺代碼
2.有些地方比較適合使用數據庫來存儲,比如關系存儲,此時,retrofit就無能為力了
3.緩存不在我們的控制范圍之內,而是完全通過okhttp來管理,多少有些不便,比如我們要刪除某一個指定的緩存,或者更新某一個指定緩存,代碼寫起來很別扭(自己hack請求頭里面的cache contrl)
而在我們項目的實際使用過程中,緩存是一個比較重要的角色,retrofit對緩存的支持度不是很好,真是讓人傷心。。。
但是,我們還是覺得在使用中retrofit真心比較方便,容易上手,通過注解代碼可讀性和可維護性提升了n個檔次,幾乎沒有樣板代碼(好吧,如果你覺得每個請求都需要定義一個方法,那這也算。。),所以最后的決定是選擇retrofit。
有人說了,volley中的兩次響應和緩存用起來很happy怎么辦?嗯,我們會修改retrofit,使它支持文件存儲和orm存儲,并將volley的緩存 網絡兩次響應回調移接過來,這個項目正在測試階段,待我們項目做完小白鼠,上線穩定之后,我會把代碼開源,大家敬請關注。