Retrofit2 完全解析 探索与okhttp之间的关系
http://blog.csdn.net/lmj623565791/article/details/51304204; 本文出自:【張鴻洋的博客】
1. 概述
之前寫了個(gè)okhttputils的工具類,然后有很多同學(xué)詢問這個(gè)工具類和retrofit什么區(qū)別,于是上了下官網(wǎng),發(fā)現(xiàn)其底層對(duì)網(wǎng)絡(luò)的訪問默認(rèn)也是基于okhttp,不過retrofit非常適合于restful url格式的請(qǐng)求,更多使用注解的方式提供功能。
既然這樣,我們本篇博文首先研究其所提供的常用的用法:
- 一般的get、post請(qǐng)求
- 動(dòng)態(tài)url,動(dòng)態(tài)參數(shù)設(shè)置,各種注解的使用
- 上傳文件(單文件,多文件上傳等)
- 下載文件等(這個(gè)不推薦retrofit去做,具體看下文)
此外,由于其內(nèi)部提供了ConverterFactory用于對(duì)返回的requestBody進(jìn)行轉(zhuǎn)化和特殊的requestBody的構(gòu)造,所以本文也包含:
- 如何自定義ConverterFactory
最后呢,因?yàn)槠湓创a并不復(fù)雜,本文將對(duì)源碼進(jìn)行整體的介紹,即
- Retrofit源碼分析
ok,說這么多,既然需要restful url,我只能撿起我那個(gè)半桶水的spring mvc 搭建一個(gè)服務(wù)端的小例子~~
最后本文使用版本:
compile 'com.squareup.retrofit2:retrofit:2.0.2'11主要是源碼解析,自定義Converter.Factory等一些細(xì)節(jié)的探索。
恩,寫完后,發(fā)現(xiàn)本文很長,中途請(qǐng)沒事站起來走兩步。
retrofit2官網(wǎng)地址:https://github.com/square/retrofit/
2. Retrofit用法示例
2.1 一般的get請(qǐng)求
retrofit在使用的過程中,需要定義一個(gè)接口對(duì)象,我們首先演示一個(gè)最簡單的get請(qǐng)求,接口如下所示:
public interface IUserBiz {@GET("users")Call<List<User>> getUsers(); }可以看到有一個(gè)getUsers()方法,通過@GET注解標(biāo)識(shí)為get請(qǐng)求,@GET中所填寫的value和baseUrl組成完整的路徑,baseUrl在構(gòu)造retrofit對(duì)象時(shí)給出。
下面看如何通過retrofit完成上述的請(qǐng)求:
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.31.242:8080/springmvc_users/user/").addConverterFactory(GsonConverterFactory.create()).build(); IUserBiz userBiz = retrofit.create(IUserBiz.class); Call<List<User>> call = userBiz.getUsers();call.enqueue(new Callback<List<User>>(){@Overridepublic void onResponse(Call<List<User>> call, Response<List<User>> response){Log.e(TAG, "normalGet:" + response.body() + "");}@Overridepublic void onFailure(Call<List<User>> call, Throwable t){}});依然是構(gòu)造者模式,指定了baseUrl和Converter.Factory,該對(duì)象通過名稱可以看出是用于對(duì)象轉(zhuǎn)化的,本例因?yàn)榉?wù)器返回的是json格式的數(shù)組,所以這里設(shè)置了GsonConverterFactory完成對(duì)象的轉(zhuǎn)化。
ok,這里可以看到很神奇,我們通過Retrofit.create就可以拿到我們定義的IUserBiz的實(shí)例,調(diào)用其方法即可拿到一個(gè)Call對(duì)象,通過call.enqueue即可完成異步的請(qǐng)求。
具體retrofit怎么得到我們接口的實(shí)例的,以及對(duì)象的返回結(jié)果是如何轉(zhuǎn)化的,我們后面具體分析。
這里需要指出的是:
接口中的方法必須有返回值,且比如是Call<T>類型
.addConverterFactory(GsonConverterFactory.create())這里如果使用gson,需要額外導(dǎo)入:
當(dāng)然除了gson以外,還提供了以下的選擇:
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
當(dāng)然也支持自定義,你可以選擇自己寫轉(zhuǎn)化器完成數(shù)據(jù)的轉(zhuǎn)化,這個(gè)后面將具體介紹。
那么,通過這么一個(gè)簡單的例子,應(yīng)該對(duì)retrofit已經(jīng)有了一個(gè)直觀的認(rèn)識(shí),下面看更多其支持的特性。
2.2 動(dòng)態(tài)的url訪問@PATH
文章開頭提過,retrofit非常適用于restful url的格式,那么例如下面這樣的url:
//用于訪問zhy的信息 http://192.168.1.102:8080/springmvc_users/user/zhy //用于訪問lmj的信息 http://192.168.1.102:8080/springmvc_users/user/lmj12341234即通過不同的username訪問不同用戶的信息,返回?cái)?shù)據(jù)為json字符串。
那么可以通過retrofit提供的@PATH注解非常方便的完成上述需求。
我們?cè)俣x一個(gè)方法:
public interface IUserBiz {@GET("{username}")Call<User> getUser(@Path("username") String username); }1234512345可以看到我們定義了一個(gè)getUser方法,方法接收一個(gè)username參數(shù),并且我們的@GET注解中使用{username}聲明了訪問路徑,這里你可以把{username}當(dāng)做占位符,而實(shí)際運(yùn)行中會(huì)通過@PATH("username")所標(biāo)注的參數(shù)進(jìn)行替換。
那么訪問的代碼很類似:
//省略了retrofit的構(gòu)建代碼 Call<User> call = userBiz.getUser("zhy"); //Call<User> call = userBiz.getUser("lmj"); call.enqueue(new Callback<User>() {@Overridepublic void onResponse(Call<User> call, Response<User> response){Log.e(TAG, "getUsePath:" + response.body());}@Overridepublic void onFailure(Call<User> call, Throwable t){} });2.3 查詢參數(shù)的設(shè)置@Query
看下面的url
http://baseurl/users?sortby=username http://baseurl/users?sortby=id1212即一般的傳參,我們可以通過@Query注解方便的完成,我們?cè)俅卧诮涌谥刑砑右粋€(gè)方法:
public interface IUserBiz {@GET("users")Call<List<User>> getUsersBySort(@Query("sortby") String sort); }訪問的代碼,其實(shí)沒什么寫的:
//省略retrofit的構(gòu)建代碼 Call<List<User>> call = userBiz.getUsersBySort("username"); //Call<List<User>> call = userBiz.getUsersBySort("id"); //省略call執(zhí)行相關(guān)代碼12341234ok,這樣我們就完成了參數(shù)的指定,當(dāng)然相同的方式也適用于POST,只需要把注解修改為@POST即可。
對(duì)了,我能剛才學(xué)了@PATH,那么會(huì)不會(huì)有這樣嘗試的沖動(dòng),對(duì)于剛才的需求,我們這么寫:
@GET("users?sortby={sortby}")Call<List<User>> getUsersBySort(@Path("sortby") String sort);1212乍一看別說好像有點(diǎn)感覺,哈,實(shí)際上運(yùn)行是不支持的~估計(jì)是@ Path的定位就是用于url的路徑而不是參數(shù),對(duì)于參數(shù)還是選擇通過@Query來設(shè)置。
2.4 POST請(qǐng)求體的方式向服務(wù)器傳入json字符串@Body
大家都清楚,我們app很多時(shí)候跟服務(wù)器通信,會(huì)選擇直接使用POST方式將json字符串作為請(qǐng)求體發(fā)送到服務(wù)器,那么我們看看這個(gè)需求使用retrofit該如何實(shí)現(xiàn)。
再次添加一個(gè)方法:
public interface IUserBiz {@POST("add")Call<List<User>> addUser(@Body User user); }1234512345提交的代碼其實(shí)基本都是一致的:
//省略retrofit的構(gòu)建代碼Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com")); //省略call執(zhí)行相關(guān)代碼123123ok,可以看到其實(shí)就是使用@Body這個(gè)注解標(biāo)識(shí)我們的參數(shù)對(duì)象即可,那么這里需要考慮一個(gè)問題,retrofit是如何將user對(duì)象轉(zhuǎn)化為字符串呢?下文將詳細(xì)解釋~
下面對(duì)應(yīng)okhttp,還有兩種requestBody,一個(gè)是FormBody,一個(gè)是MultipartBody,前者以表單的方式傳遞簡單的鍵值對(duì),后者以POST表單的方式上傳文件可以攜帶參數(shù),retrofit也二者也有對(duì)應(yīng)的注解,下面繼續(xù)~
2.5 表單的方式傳遞鍵值對(duì)@FormUrlEncoded
這里我們模擬一個(gè)登錄的方法,添加一個(gè)方法:
public interface IUserBiz {@POST("login")@FormUrlEncodedCall<User> login(@Field("username") String username, @Field("password") String password); }訪問的代碼:
//省略retrofit的構(gòu)建代碼 Call<User> call = userBiz.login("zhy", "123"); //省略call執(zhí)行相關(guān)代碼123123ok,看起來也很簡單,通過@POST指明url,添加FormUrlEncoded,然后通過@Field添加參數(shù)即可。
2.6 單文件上傳@Multipart
下面看一下單文件上傳,依然是再次添加個(gè)方法:
public interface IUserBiz {@Multipart@POST("register")Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password); }這里@MultiPart的意思就是允許多個(gè)@Part了,我們這里使用了3個(gè)@Part,第一個(gè)我們準(zhǔn)備上傳個(gè)文件,使用了MultipartBody.Part類型,其余兩個(gè)均為簡單的鍵值對(duì)。
使用:
File file = new File(Environment.getExternalStorageDirectory(), "icon.png"); RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file); MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);Call<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));1234512345ok,這里感覺略為麻煩。不過還是蠻好理解~~多個(gè)@Part,每個(gè)Part對(duì)應(yīng)一個(gè)RequestBody。
這里插個(gè)實(shí)驗(yàn)過程,其實(shí)我最初對(duì)于文件,也是嘗試的@Part RequestBody,因?yàn)?#64;Part("key"),然后傳入一個(gè)代表文件的RequestBody,我覺得更加容易理解,后來發(fā)現(xiàn)試驗(yàn)無法成功,而且查了下issue,給出了一個(gè)很奇怪的解決方案,這里可以參考:retrofit#1063。
給出了一個(gè)類似如下的方案:
public interface ApiInterface {@Multipart@POST ("/api/Accounts/editaccount")Call<User> editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);}可以看到對(duì)于文件的那個(gè)@Partvalue竟然寫了這么多奇怪的東西,而且filename竟然硬編碼了~~這個(gè)不好吧,我上傳的文件名竟然不能動(dòng)態(tài)指定。
為了文件名不會(huì)被寫死,所以給出了最上面的上傳單文件的方法,ps:上面這個(gè)方案經(jīng)測試也是可以上傳成功的。
恩,這個(gè)奇怪方案,為什么這么做可行,下文會(huì)給出非常詳細(xì)的解釋。
最后看下多文件上傳~
2.7 多文件上傳@PartMap
再添加一個(gè)方法~~~
public interface IUserBiz{@Multipart@POST("register")Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password); }這里使用了一個(gè)新的注解@PartMap,這個(gè)注解用于標(biāo)識(shí)一個(gè)Map,Map的key為String類型,代表上傳的鍵值對(duì)的key(與服務(wù)器接受的key對(duì)應(yīng)),value即為RequestBody,有點(diǎn)類似@Part的封裝版本。
執(zhí)行的代碼:
File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");RequestBody photo = RequestBody.create(MediaType.parse("image/png", file); Map<String,RequestBody> photos = new HashMap<>(); photos.put("photos\"; filename=\"icon.png", photo); photos.put("username", RequestBody.create(null, "abc"));Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));可以看到,可以在Map中put進(jìn)一個(gè)或多個(gè)文件,鍵值對(duì)等,當(dāng)然你也可以分開,單獨(dú)的鍵值對(duì)也可以使用@Part,這里又看到設(shè)置文件的時(shí)候,相對(duì)應(yīng)的key很奇怪,例如上例"photos\"; filename=\"icon.png",前面的photos就是與服務(wù)器對(duì)應(yīng)的key,后面filename是服務(wù)器得到的文件名,ok,參數(shù)雖然奇怪,但是也可以動(dòng)態(tài)的設(shè)置文件名,不太影響使用~~
2.8 下載文件
這個(gè)其實(shí)我覺得直接使用okhttp就好了,使用retrofit去做這個(gè)事情真的有點(diǎn)瞎用的感覺~~
增加一個(gè)方法:
@GET("download") Call<ResponseBody> downloadTest();1212調(diào)用:
Call<ResponseBody> call = userBiz.downloadTest(); call.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){InputStream is = response.body().byteStream();//save file}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t){} });可以看到可以返回ResponseBody,那么很多事都能干了~~
but,也看出這種方式下載感覺非常雞肋,并且onReponse回調(diào)雖然在UI線程,但是你還是要處理io操作,也就是說你在這里還要另外開線程操作,或者你可以考慮同步的方式下載。
最后還是建議使用okhttp去下載,例如使用okhttputils.
有人可能會(huì)問,使用okhttp,和使用retrofit會(huì)不會(huì)造成新建多個(gè)OkHttpClient對(duì)象呢,其實(shí)是可設(shè)置的,參考下文。
ok,上面就是一些常用的方法,當(dāng)然還涉及到一些沒有介紹的注解,但是通過上面這么多方法的介紹,再多一二個(gè)注解的使用方式,相信大家能夠解決。
3. 配置OkHttpClient
這個(gè)需要簡單提一下,很多時(shí)候,比如你使用retrofit需要統(tǒng)一的log管理,給每個(gè)請(qǐng)求添加統(tǒng)一的header等,這些都應(yīng)該通過okhttpclient去操作,比如addInterceptor
例如:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,統(tǒng)一的header等 {@Overridepublic okhttp3.Response intercept(Chain chain) throws IOException{return null;} }).build();或許你需要更多的配置,你可以單獨(dú)寫一個(gè)OkhttpClient的單例生成類,在這個(gè)里面完成你所需的所有的配置,然后將OkhttpClient實(shí)例通過方法公布出來,設(shè)置給retrofit。
設(shè)置方式:
Retrofit retrofit = new Retrofit.Builder().callFactory(OkHttpUtils.getClient()).build();123123callFactory方法接受一個(gè)okhttp3.Call.Factory對(duì)象,OkHttpClient即為一個(gè)實(shí)現(xiàn)類。
4. Retrofit源碼解析
ok,接下來我們隊(duì)retrofit的源碼做簡單的分析,首先我們看retrofit如何為我們的接口實(shí)現(xiàn)實(shí)例;然后看整體的執(zhí)行流程;最后再看詳細(xì)的細(xì)節(jié);
4.1 Retrofit如何為我們的接口實(shí)現(xiàn)實(shí)例
通過上文的學(xué)習(xí),我們發(fā)現(xiàn)使用retrofit需要去定義一個(gè)接口,然后可以通過調(diào)用retrofit.create(IUserBiz.class);方法,得到一個(gè)接口的實(shí)例,最后通過該實(shí)例執(zhí)行我們的操作,那么retrofit如何實(shí)現(xiàn)我們指定接口的實(shí)例呢?
其實(shí)原理是:動(dòng)態(tài)代理。但是不要被動(dòng)態(tài)代理這幾個(gè)詞嚇唬到,Java中已經(jīng)提供了非常簡單的API幫助我們來實(shí)現(xiàn)動(dòng)態(tài)代理。
看源碼前先看一個(gè)例子:
public interface ITest {@GET("/heiheihei")public void add(int a, int b);} public static void main(String[] args) {ITest iTest = (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), new Class<?>[]{ITest.class}, new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{Integer a = (Integer) args[0];Integer b = (Integer) args[1];System.out.println("方法名:" + method.getName());System.out.println("參數(shù):" + a + " , " + b);GET get = method.getAnnotation(GET.class);System.out.println("注解:" + get.value());return null;}});iTest.add(3, 5); }輸出結(jié)果為:
方法名:add 參數(shù):3 , 5 注解:/heiheihei123123可以看到我們通過Proxy.newProxyInstance產(chǎn)生的代理類,當(dāng)調(diào)用接口的任何方法時(shí),都會(huì)調(diào)用InvocationHandler#invoke方法,在這個(gè)方法中可以拿到傳入的參數(shù),注解等。
試想,retrofit也可以通過同樣的方式,在invoke方法里面,拿到所有的參數(shù),注解信息然后就可以去構(gòu)造RequestBody,再去構(gòu)建Request,得到Call對(duì)象封裝后返回。
ok,下面看retrofit#create的源碼:
public <T> T create(final Class<T> service) {return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object... args) throws Throwable {});}12345671234567哈,和上面對(duì)應(yīng)。到這里,你應(yīng)該明白retrofit為我們接口生成實(shí)例對(duì)象并不神奇,僅僅是使用了Proxy這個(gè)類的API而已,然后在invoke方法里面拿到足夠的信息去構(gòu)建最終返回的Call而已。
哈,其實(shí)真正的動(dòng)態(tài)代理一般是有具體的實(shí)現(xiàn)類的,只是在這個(gè)類調(diào)用某個(gè)方法的前后去執(zhí)行一些別的操作,比如開事務(wù),打log等等。當(dāng)然,本博文并不需要涉及這些詳細(xì)的內(nèi)容,如果你希望詳細(xì)去了解,可以搜索關(guān)鍵字:Proxy InvocationHandler。
4.2 retrofit整體實(shí)現(xiàn)流程
4.2.1 Retrofit的構(gòu)建
這里依然是通過構(gòu)造者模式進(jìn)行構(gòu)建retrofit對(duì)象,好在其內(nèi)部的成員變量比較少,我們直接看build()方法。
public Builder() {this(Platform.get()); }public Retrofit build() {if (baseUrl == null) {throw new IllegalStateException("Base URL required.");}okhttp3.Call.Factory callFactory = this.callFactory;if (callFactory == null) {callFactory = new OkHttpClient();}Executor callbackExecutor = this.callbackExecutor;if (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor();}// Make a defensive copy of the adapters and add the default Call adapter.List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));// Make a defensive copy of the converters.List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,callbackExecutor, validateEagerly); }- baseUrl必須指定,這個(gè)是理所當(dāng)然的;
- 然后可以看到如果不著急設(shè)置callFactory,則默認(rèn)直接new OkHttpClient(),可見如果你需要對(duì)okhttpclient進(jìn)行詳細(xì)的設(shè)置,需要構(gòu)建OkHttpClient對(duì)象,然后傳入;
- 接下來是callbackExecutor,這個(gè)想一想大概是用來將回調(diào)傳遞到UI線程了,當(dāng)然這里設(shè)計(jì)的比較巧妙,利用platform對(duì)象,對(duì)平臺(tái)進(jìn)行判斷,判斷主要是利用Class.forName("")進(jìn)行查找,具體代碼已經(jīng)被放到文末,如果是Android平臺(tái),會(huì)自定義一個(gè)Executor對(duì)象,并且利用Looper.getMainLooper()實(shí)例化一個(gè)handler對(duì)象,在Executor內(nèi)部通過handler.post(runnable),ok,整理憑大腦應(yīng)該能構(gòu)思出來,暫不貼代碼了。
- 接下來是adapterFactories,這個(gè)對(duì)象主要用于對(duì)Call進(jìn)行轉(zhuǎn)化,基本上不需要我們自己去自定義。
- 最后是converterFactories,該對(duì)象用于轉(zhuǎn)化數(shù)據(jù),例如將返回的responseBody轉(zhuǎn)化為對(duì)象等;當(dāng)然不僅僅是針對(duì)返回的數(shù)據(jù),還能用于一般備注解的參數(shù)的轉(zhuǎn)化例如@Body標(biāo)識(shí)的對(duì)象做一些操作,后面遇到源碼詳細(xì)再描述。
ok,總體就這幾個(gè)對(duì)象去構(gòu)造retrofit,還算比較少的~~
4.2.2 具體Call構(gòu)建流程
我們構(gòu)造完成retrofit,就可以利用retrofit.create方法去構(gòu)建接口的實(shí)例了,上面我們已經(jīng)分析了這個(gè)環(huán)節(jié)利用了動(dòng)態(tài)代理,而且我們也分析了具體的Call的構(gòu)建流程在invoke方法中,下面看代碼:
public <T> T create(final Class<T> service) {Utils.validateServiceInterface(service);//...return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object... args){//...ServiceMethod serviceMethod = loadServiceMethod(method);OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);return serviceMethod.callAdapter.adapt(okHttpCall);}}); }主要也就三行代碼,第一行是根據(jù)我們的method將其包裝成ServiceMethod,第二行是通過ServiceMethod和方法的參數(shù)構(gòu)造retrofit2.OkHttpCall對(duì)象,第三行是通過serviceMethod.callAdapter.adapt()方法,將OkHttpCall進(jìn)行代理包裝;
下面一個(gè)一個(gè)介紹:
- ServiceMethod應(yīng)該是最復(fù)雜的一個(gè)類了,包含了將一個(gè)method轉(zhuǎn)化為Call的所有的信息。
直接看build方法,首先拿到這個(gè)callAdapter最終拿到的是我們?cè)跇?gòu)建retrofit里面時(shí)adapterFactories時(shí)添加的,即為:new ExecutorCallbackCall<>(callbackExecutor, call),該ExecutorCallbackCall唯一做的事情就是將原本call的回調(diào)轉(zhuǎn)發(fā)至UI線程。
接下來通過callAdapter.responseType()返回的是我們方法的實(shí)際類型,例如:Call<User>,則返回User類型,然后對(duì)該類型進(jìn)行判斷。
接下來是createResponseConverter拿到responseConverter對(duì)象,其當(dāng)然也是根據(jù)我們構(gòu)建retrofit時(shí),addConverterFactory添加的ConverterFactory對(duì)象來尋找一個(gè)合適的返回,尋找的依據(jù)主要看該converter能否處理你編寫方法的返回值類型,默認(rèn)實(shí)現(xiàn)為BuiltInConverters,僅僅支持返回值的實(shí)際類型為ResponseBody和Void,也就說明了默認(rèn)情況下,是不支持Call<User>這類類型的。
接下來就是對(duì)注解進(jìn)行解析了,主要是對(duì)方法上的注解進(jìn)行解析,那么可以拿到httpMethod以及初步的url(包含占位符)。
后面是對(duì)方法中參數(shù)中的注解進(jìn)行解析,這一步會(huì)拿到很多的ParameterHandler對(duì)象,該對(duì)象在toRequest()構(gòu)造Request的時(shí)候調(diào)用其apply方法。
ok,這里我們并沒有去一行一行查看代碼,其實(shí)意義也不太大,只要知道ServiceMethod主要用于將我們接口中的方法轉(zhuǎn)化為一個(gè)Request對(duì)象,于是根據(jù)我們的接口返回值確定了responseConverter,解析我們方法上的注解拿到初步的url,解析我們參數(shù)上的注解拿到構(gòu)建RequestBody所需的各種信息,最終調(diào)用toRequest的方法完成Request的構(gòu)建。
- 接下來看OkHttpCall的構(gòu)建,構(gòu)造函數(shù)僅僅是簡單的賦值
- 最后一步是serviceMethod.callAdapter.adapt(okHttpCall)
我們已經(jīng)確定這個(gè)callAdapter是ExecutorCallAdapterFactory.get()對(duì)應(yīng)代碼為:
final class ExecutorCallAdapterFactory extends CallAdapter.Factory {final Executor callbackExecutor;ExecutorCallAdapterFactory(Executor callbackExecutor) {this.callbackExecutor = callbackExecutor;}@Overridepublic CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {if (getRawType(returnType) != Call.class) {return null;}final Type responseType = Utils.getCallResponseType(returnType);return new CallAdapter<Call<?>>() {@Override public Type responseType() {return responseType;}@Override public <R> Call<R> adapt(Call<R> call) {return new ExecutorCallbackCall<>(callbackExecutor, call);}};}可以看到adapt返回的是ExecutorCallbackCall對(duì)象,繼續(xù)往下看:
static final class ExecutorCallbackCall<T> implements Call<T> {final Executor callbackExecutor;final Call<T> delegate;ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.callbackExecutor = callbackExecutor;this.delegate = delegate;}@Override public void enqueue(final Callback<T> callback) {if (callback == null) throw new NullPointerException("callback == null");delegate.enqueue(new Callback<T>() {@Override public void onResponse(Call<T> call, final Response<T> response) {callbackExecutor.execute(new Runnable() {@Override public void run() {if (delegate.isCanceled()) {// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}}});}@Override public void onFailure(Call<T> call, final Throwable t) {callbackExecutor.execute(new Runnable() {@Override public void run() {callback.onFailure(ExecutorCallbackCall.this, t);}});}});}@Override public Response<T> execute() throws IOException {return delegate.execute();}}可以看出ExecutorCallbackCall僅僅是對(duì)Call對(duì)象進(jìn)行封裝,類似裝飾者模式,只不過將其執(zhí)行時(shí)的回調(diào)通過callbackExecutor進(jìn)行回調(diào)到UI線程中去了。
4.2.3 執(zhí)行Call
在4.2.2我們已經(jīng)拿到了經(jīng)過封裝的ExecutorCallbackCall類型的call對(duì)象,實(shí)際上就是我們實(shí)際在寫代碼時(shí)拿到的call對(duì)象,那么我們一般會(huì)執(zhí)行enqueue方法,看看源碼是怎么做的
首先是ExecutorCallbackCall.enqueue方法,代碼在4.2.2,可以看到除了將onResponse和onFailure回調(diào)到UI線程,主要的操作還是delegate完成的,這個(gè)delegate實(shí)際上就是OkHttpCall對(duì)象,我們看它的enqueue方法
@Override public void enqueue(final Callback<T> callback) {okhttp3.Call call;Throwable failure;synchronized (this){if (executed) throw new IllegalStateException("Already executed.");executed = true;try{call = rawCall = createRawCall();} catch (Throwable t){failure = creationFailure = t;}}if (failure != null){callback.onFailure(this, failure);return;}if (canceled){call.cancel();}call.enqueue(new okhttp3.Callback(){@Overridepublic void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)throws IOException{Response<T> response;try{response = parseResponse(rawResponse);} catch (Throwable e){callFailure(e);return;}callSuccess(response);}@Overridepublic void onFailure(okhttp3.Call call, IOException e){try{callback.onFailure(OkHttpCall.this, e);} catch (Throwable t){t.printStackTrace();}}private void callFailure(Throwable e){try{callback.onFailure(OkHttpCall.this, e);} catch (Throwable t){t.printStackTrace();}}private void callSuccess(Response<T> response){try{callback.onResponse(OkHttpCall.this, response);} catch (Throwable t){t.printStackTrace();}}}); }沒有任何神奇的地方,內(nèi)部實(shí)際上就是okhttp的Call對(duì)象,也是調(diào)用okhttp3.Call.enqueue方法。
中間對(duì)于okhttp3.Call的創(chuàng)建代碼為:
private okhttp3.Call createRawCall() throws IOException {Request request = serviceMethod.toRequest(args);okhttp3.Call call = serviceMethod.callFactory.newCall(request);if (call == null){throw new NullPointerException("Call.Factory returned null.");}return call; }可以看到,通過serviceMethod.toRequest完成對(duì)request的構(gòu)建,通過request去構(gòu)造call對(duì)象,然后返回.
中間還涉及一個(gè)parseResponse方法,如果順利的話,執(zhí)行的代碼如下:
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody = rawResponse.body();ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);T body = serviceMethod.toResponse(catchingBody);return Response.success(body, rawResponse);通過serviceMethod對(duì)ResponseBody進(jìn)行轉(zhuǎn)化,然后返回,轉(zhuǎn)化實(shí)際上就是通過responseConverter的convert方法。
// ServiceMethodT toResponse(ResponseBody body) throws IOException {return responseConverter.convert(body);}ok,關(guān)于responseConverter后面還會(huì)細(xì)說,不用擔(dān)心。
到這里,我們整個(gè)源碼的流程分析就差不多了,目的就掌握一個(gè)大體的原理和執(zhí)行流程,了解下幾個(gè)核心的類。
那么總結(jié)一下:
- 首先構(gòu)造retrofit,幾個(gè)核心的參數(shù)呢,主要就是baseurl,callFactory(默認(rèn)okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
- 然后通過create方法拿到接口的實(shí)現(xiàn)類,這里利用Java的Proxy類完成動(dòng)態(tài)代理的相關(guān)代理
- 在invoke方法內(nèi)部,拿到我們所聲明的注解以及實(shí)參等,構(gòu)造ServiceMethod,ServiceMethod中解析了大量的信息,最痛可以通過toRequest構(gòu)造出okhttp3.Request對(duì)象。有了okhttp3.Request對(duì)象就可以很自然的構(gòu)建出okhttp3.call,最后calladapter對(duì)Call進(jìn)行裝飾返回。
- 拿到Call就可以執(zhí)行enqueue或者execute方法了
ok,了解這么多足以。
下面呢,有幾個(gè)地方需要注意,一方面是一些特殊的細(xì)節(jié);另一方面就是Converter。
5. retrofit中的各類細(xì)節(jié)
5.1 上傳文件中使用的奇怪value值
第一個(gè)問題涉及到文件上傳,還記得我們?cè)趩挝募蟼髂抢锼f的嗎?有種類似于hack的寫法,上傳文件是這么做的?
public interface ApiInterface {@Multipart@POST ("/api/Accounts/editaccount")Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username);}首先我們一點(diǎn)明確,因?yàn)檫@里使用了@ Multipart,那么我們認(rèn)為@Part應(yīng)當(dāng)支持普通的key-value,以及文件。
對(duì)于普通的key-value是沒問題的,只需要這樣@Part("username") String username。
那么對(duì)于文件,為什么需要這樣呢?@Part("file_key\"; filename=\"pp.png")
這個(gè)value設(shè)置的值不用看就會(huì)覺得特別奇怪,然而卻可以正常執(zhí)行,原因是什么呢?
原因是這樣的:
當(dāng)上傳key-value的時(shí)候,實(shí)際上對(duì)應(yīng)這樣的代碼:
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),RequestBody.create(null, params.get(key)));1212也就是說,我們的@Part轉(zhuǎn)化為了
Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")11這么一看,很隨意,只要把key放進(jìn)去就可以了。
但是,retrofit2并沒有對(duì)文件做特殊處理,文件的對(duì)應(yīng)的字符串應(yīng)該是這樣的
Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");11與鍵值對(duì)對(duì)應(yīng)的字符串相比,多了個(gè);filename="filename.png,就因?yàn)閞etrofit沒有做特殊處理,所以你現(xiàn)在看這些hack的做法
@Part("file_key\"; filename=\"pp.png") 拼接:==> Content-Disposition", "form-data; name=\"" + key + "\" 結(jié)果:==> Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"1234512345ok,到這里我相信你已經(jīng)理解了,為什么要這么做,而且為什么這么做可以成功!
恩,值得一提的事,因?yàn)檫@種方式文件名寫死了,我們上文使用的的是@Part MultipartBody.Part file,可以滿足文件名動(dòng)態(tài)設(shè)置,這個(gè)方式貌似也是2.0.1的時(shí)候支持的。
上述相關(guān)的源碼:
// ServiceMethod if (annotation instanceof Part) {if (!isMultipart) {throw parameterError(p, "@Part parameters can only be used with multipart encoding.");}Part part = (Part) annotation;gotPart = true;String partName = part.value();Headers headers =Headers.of("Content-Disposition", "form-data; name=\"" + partName + "\"","Content-Transfer-Encoding", part.encoding()); }可以看到呢,并沒有對(duì)文件做特殊處理,估計(jì)下個(gè)版本說不定@Part會(huì)多個(gè)isFile=true|false屬性,甚至修改對(duì)應(yīng)形參,然后在這里做簡單的處理。
ok,最后來到關(guān)鍵的ConverterFactory了~
6. 自定義Converter.Factory
6.1 responseBodyConverter
關(guān)于Converter.Factory,肯定是通過addConverterFactory設(shè)置的
Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).build();123123該方法接受的是一個(gè)Converter.Factory factory對(duì)象
該對(duì)象呢,是一個(gè)抽象類,內(nèi)部包含3個(gè)方法:
abstract class Factory {public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,Retrofit retrofit) {return null;}public Converter<?, RequestBody> requestBodyConverter(Type type,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {return null;}public Converter<?, String> stringConverter(Type type, Annotation[] annotations,Retrofit retrofit) {return null;}}可以看到呢,3個(gè)方法都是空方法而不是抽象的方法,也就表明了我們可以選擇去實(shí)現(xiàn)其中的1個(gè)或多個(gè)方法,一般只需要關(guān)注requestBodyConverter和responseBodyConverter就可以了。
ok,我們先看如何自定義,最后再看GsonConverterFactory.create的源碼。
先來個(gè)簡單的,實(shí)現(xiàn)responseBodyConverter方法,看這個(gè)名字很好理解,就是將responseBody進(jìn)行轉(zhuǎn)化就可以了。
ok,這里呢,我們先看一下上述中我們使用的接口:
package com.zhy.retrofittest.userBiz;public interface IUserBiz {@GET("users")Call<List<User>> getUsers();@POST("users")Call<List<User>> getUsersBySort(@Query("sort") String sort);@GET("{username}")Call<User> getUser(@Path("username") String username);@POST("add")Call<List<User>> addUser(@Body User user);@POST("login")@FormUrlEncodedCall<User> login(@Field("username") String username, @Field("password") String password);@Multipart@POST("register")Call<User> registerUser(@Part("photos") RequestBody photos, @Part("username") RequestBody username, @Part("password") RequestBody password);@Multipart@POST("register")Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);@GET("download")Call<ResponseBody> downloadTest();}不知不覺,方法還蠻多的,假設(shè)哈,我們這里去掉retrofit構(gòu)造時(shí)的GsonConverterFactory.create,自己實(shí)現(xiàn)一個(gè)Converter.Factory來做數(shù)據(jù)的轉(zhuǎn)化工作。
首先我們解決responseBodyConverter,那么代碼很簡單,我們可以這么寫:
public class UserConverterFactory extends Converter.Factory {@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){//根據(jù)type判斷是否是自己能處理的類型,不能的話,return null ,交給后面的Converter.Factoryreturn new UserConverter(type);}}public class UserResponseConverter<T> implements Converter<ResponseBody, T> {private Type type;Gson gson = new Gson();public UserResponseConverter(Type type){this.type = type;}@Overridepublic T convert(ResponseBody responseBody) throws IOException{String result = responseBody.string();T users = gson.fromJson(result, type);return users;} }使用的時(shí)候呢,可以
Retrofit retrofit = new Retrofit.Builder() .callFactory(new OkHttpClient()) .baseUrl("http://example/springmvc_users/user/") //.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(new UserConverterFactory()).build();1234512345ok,這樣的話,就可以完成我們的ReponseBody到List<User>或者User的轉(zhuǎn)化了。
可以看出,我們這里用的依然是Gson,那么有些同學(xué)肯定不希望使用Gson就能實(shí)現(xiàn),如果不使用Gson的話,一般需要針對(duì)具體的返回類型,比如我們針對(duì)返回List<User>或者User
你可以這么寫:
package com.zhy.retrofittest.converter; /*** Created by zhy on 16/4/30.*/ public class UserResponseConverter<T> implements Converter<ResponseBody, T> {private Type type;Gson gson = new Gson();public UserResponseConverter(Type type){this.type = type;}@Overridepublic T convert(ResponseBody responseBody) throws IOException{String result = responseBody.string();if (result.startsWith("[")){return (T) parseUsers(result);} else{return (T) parseUser(result);}}private User parseUser(String result){JSONObject jsonObject = null;try{jsonObject = new JSONObject(result);User u = new User();u.setUsername(jsonObject.getString("username"));return u;} catch (JSONException e){e.printStackTrace();}return null;}private List<User> parseUsers(String result){List<User> users = new ArrayList<>();try{JSONArray jsonArray = new JSONArray(result);User u = null;for (int i = 0; i < jsonArray.length(); i++){JSONObject jsonObject = jsonArray.getJSONObject(i);u = new User();u.setUsername(jsonObject.getString("username"));users.add(u);}} catch (JSONException e){e.printStackTrace();}return users;} }這里簡單讀取了一個(gè)屬性,大家肯定能看懂,這樣就能滿足返回值是Call<List<User>>或者Call<User>.
這里鄭重提醒:如果你針對(duì)特定的類型去寫Converter,一定要在UserConverterFactory#responseBodyConverter中對(duì)類型進(jìn)行檢查,發(fā)現(xiàn)不能處理的類型return null,這樣的話,可以交給后面的Converter.Factory處理,比如本例我們可以按照下列方式檢查:
public class UserConverterFactory extends Converter.Factory {@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit){//根據(jù)type判斷是否是自己能處理的類型,不能的話,return null ,交給后面的Converter.Factoryif (type == User.class)//支持返回值是User{return new UserResponseConverter(type);}if (type instanceof ParameterizedType)//支持返回值是List<User>{Type rawType = ((ParameterizedType) type).getRawType();Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];if (rawType == List.class && actualType == User.class){return new UserResponseConverter(type);}}return null;}}好了,到這呢responseBodyConverter方法告一段落了,謹(jǐn)記就是將reponseBody->返回值返回中的實(shí)際類型,例如Call<User>中的User;還有對(duì)于該converter不能處理的類型一定要返回null。
6.2 requestBodyConverter
ok,上面接口一大串方法呢,使用了我們的Converter之后,有個(gè)方法我們現(xiàn)在還是不支持的。
@POST("add") Call<List<User>> addUser(@Body User user);1212ok,這個(gè)@Body需要用到這個(gè)方法,叫做requestBodyConverter,根據(jù)參數(shù)轉(zhuǎn)化為RequestBody,下面看下我們?nèi)绾翁峁┲С帧?/p> public class UserRequestBodyConverter<T> implements Converter<T, RequestBody> {private Gson mGson = new Gson();@Overridepublic RequestBody convert(T value) throws IOException{String string = mGson.toJson(value);return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string);} }
然后在UserConverterFactory中復(fù)寫requestBodyConverter方法,返回即可:
public class UserConverterFactory extends Converter.Factory {@Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){return new UserRequestBodyConverter<>();} }這里偷了個(gè)懶,使用Gson將對(duì)象轉(zhuǎn)化為json字符串了,如果你不喜歡使用框架,你可以選擇拼接字符串,或者反射寫一個(gè)支持任何對(duì)象的,反正就是對(duì)象->json字符串的轉(zhuǎn)化。最后構(gòu)造一個(gè)RequestBody返回即可。
ok,到這里,我相信如果你看的細(xì)致,自定義Converter.Factory是干嘛的,但是我還是要總結(jié)下:
- responseBodyConverter 主要是對(duì)應(yīng)@Body注解,完成ResponseBody到實(shí)際的返回類型的轉(zhuǎn)化,這個(gè)類型對(duì)應(yīng)Call<XXX>里面的泛型XXX,其實(shí)@Part等注解也會(huì)需要responseBodyConverter,只不過我們的參數(shù)類型都是RequestBody,由默認(rèn)的converter處理了。
- requestBodyConverter 完成對(duì)象到RequestBody的構(gòu)造。
- 一定要注意,檢查type如果不是自己能處理的類型,記得return null (因?yàn)榭梢蕴砑佣鄠€(gè),你不能處理return null ,還會(huì)去遍歷后面的converter).
7. 值得學(xué)習(xí)的API
其實(shí)一般情況下看源碼呢,可以讓我們更好的去使用這個(gè)庫,當(dāng)然在看的過程中如果發(fā)現(xiàn)了一些比較好的處理方式呢,是非常值得記錄的。如果每次看別人的源碼都能吸取一定的精華,比你單純的去理解會(huì)好很多,因?yàn)槟愕挠洃浟υ俸?#xff0c;源碼解析你也是會(huì)忘的,而你記錄下來并能夠使用的優(yōu)越的代碼,可能用久了就成為你的代碼了。
我舉個(gè)例子:比如retrofit2中判斷當(dāng)前運(yùn)行的環(huán)境代碼如下,如果下次你有這樣的需求,你也可以這么寫,甚至源碼中根據(jù)不同的運(yùn)行環(huán)境還提供了不同的Executor都很值得記錄:
class Platform {private static final Platform PLATFORM = findPlatform();static Platform get() {return PLATFORM;}private static Platform findPlatform() {try {Class.forName("android.os.Build");if (Build.VERSION.SDK_INT != 0) {return new Android();}} catch (ClassNotFoundException ignored) {}try {Class.forName("java.util.Optional");return new Java8();} catch (ClassNotFoundException ignored) {}try {Class.forName("org.robovm.apple.foundation.NSObject");return new IOS();} catch (ClassNotFoundException ignored) {}return new Platform();}ok,文章結(jié)束,最后打個(gè)廣告,個(gè)人微信公眾號(hào)目前關(guān)注大概9000人左右,無奈個(gè)人博文更新頻率無法做到很好的為這么多人服務(wù),所以本公眾號(hào)歡迎大家投稿,如果你希望你的文章可以被更多人看到,直接將md、doc等格式的文章發(fā)我郵箱即可(623565791@qq.com),也可以加我好友,需要注明(投稿),謝謝。
文章要求:原創(chuàng)+你覺得通過文章你能學(xué)到一些有價(jià)值的東西。
福利:您的文章還可以發(fā)到csdn,簡書等其他平臺(tái),但不能投稿至其他微信公眾號(hào)了;更多人能夠發(fā)現(xiàn)你的文章(好文我會(huì)幫你發(fā)送至我的各大群)+您的署名+您的原文鏈接,如果后期有打賞收益均歸投稿人所有。
總結(jié)
以上是生活随笔為你收集整理的Retrofit2 完全解析 探索与okhttp之间的关系的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Https相关完全解析
- 下一篇: 即时通讯基础