轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/51304204;
本文出自:【張鴻洋的博客】java
以前寫了個okhttputils的工具類,而後有不少同窗詢問這個工具類和retrofit
什麼區別,因而上了下官網,發現其底層對網絡的訪問默認也是基於okhttp
,不過retrofit
很是適合於restful url
格式的請求,更多使用註解的方式提供功能。android
既然這樣,咱們本篇博文首先研究其所提供的經常使用的用法:git
此外,因爲其內部提供了ConverterFactory
用於對返回的requestBody
進行轉化和特殊的requestBody
的構造,因此本文也包含:github
ConverterFactory
最後呢,由於其源碼並不複雜,本文將對源碼進行總體的介紹,即spring
ok,說這麼多,既然須要restful url
,我只能撿起我那個半桶水的spring mvc 搭建一個服務端的小例子~~sql
最後本文使用版本:json
compile 'com.squareup.retrofit2:retrofit:2.0.2'
主要是源碼解析,自定義Converter.Factory
等一些細節的探索。api
恩,寫完後,發現本文很長,中途請沒事站起來走兩步。數組
retrofit2官網地址:https://github.com/square/retrofit/bash
retrofit
在使用的過程當中,須要定義一個接口對象,咱們首先演示一個最簡單的get請求,接口以下所示:
public interface IUserBiz { @GET("users") Call<List<User>> getUsers(); }
能夠看到有一個getUsers()方法,經過@GET
註解標識爲get請求,@GET
中所填寫的value和baseUrl
組成完整的路徑,baseUrl
在構造retrofit對象時給出。
下面看如何經過retrofit
完成上述的請求:
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>>() { @Override public void onResponse(Call<List<User>> call, Response<List<User>> response) { Log.e(TAG, "normalGet:" + response.body() + ""); } @Override public void onFailure(Call<List<User>> call, Throwable t) { } });
依然是構造者模式,指定了baseUrl
和Converter.Factory
,該對象經過名稱能夠看出是用於對象轉化的,本例由於服務器返回的是json格式的數組,因此這裏設置了GsonConverterFactory
完成對象的轉化。
ok,這裏能夠看到很神奇,咱們經過Retrofit.create
就能夠拿到咱們定義的IUserBiz
的實例,調用其方法便可拿到一個Call
對象,經過call.enqueue
便可完成異步的請求。
具體retrofit怎麼獲得咱們接口的實例的,以及對象的返回結果是如何轉化的,咱們後面具體分析。
這裏須要指出的是:
Call<T>
類型.addConverterFactory(GsonConverterFactory.create())
這裏若是使用gson,須要額外導入:
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
固然除了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
固然也支持自定義,你能夠選擇本身寫轉化器完成數據的轉化,這個後面將具體介紹。
既然call.enqueue
是異步的訪問數據,那麼同步的訪問方式爲call.execute
,這一點很是相似okhttp的API,實際上默認狀況下內部也是經過okhttp3.Call
實現。
那麼,經過這麼一個簡單的例子,應該對retrofit
已經有了一個直觀的認識,下面看更多其支持的特性。
@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/lmj
即經過不一樣的username訪問不一樣用戶的信息,返回數據爲json字符串。
那麼能夠經過retrofit提供的@PATH
註解很是方便的完成上述需求。
咱們再定義一個方法:
public interface IUserBiz { @GET("{username}") Call<User> getUser(@Path("username") String username); }
能夠看到咱們定義了一個getUser方法,方法接收一個username參數,而且咱們的@GET
註解中使用{username}
聲明瞭訪問路徑,這裏你能夠把{username}
當作佔位符,而實際運行中會經過@PATH("username")
所標註的參數進行替換。
那麼訪問的代碼很相似:
//省略了retrofit的構建代碼 Call<User> call = userBiz.getUser("zhy"); //Call<User> call = userBiz.getUser("lmj"); call.enqueue(new Callback<User>() { @Override public void onResponse(Call<User> call, Response<User> response) { Log.e(TAG, "getUsePath:" + response.body()); } @Override public void onFailure(Call<User> call, Throwable t) { } });
@Query
看下面的url
http://baseurl/users?sortby=username http://baseurl/users?sortby=id
即通常的傳參,咱們能夠經過@Query
註解方便的完成,咱們再次在接口中添加一個方法:
public interface IUserBiz { @GET("users") Call<List<User>> getUsersBySort(@Query("sortby") String sort); }
訪問的代碼,其實沒什麼寫的:
//省略retrofit的構建代碼 Call<List<User>> call = userBiz.getUsersBySort("username"); //Call<List<User>> call = userBiz.getUsersBySort("id"); //省略call執行相關代碼
ok,這樣咱們就完成了參數的指定,固然相同的方式也適用於POST,只須要把註解修改成@POST
便可。
對了,我能剛纔學了@PATH
,那麼會不會有這樣嘗試的衝動,對於剛纔的需求,咱們這麼寫:
@GET("users?sortby={sortby}") Call<List<User>> getUsersBySort(@Path("sortby") String sort);
乍一看別說好像有點感受,哈,實際上運行是不支持的~估計是@ Path
的定位就是用於url的路徑而不是參數,對於參數仍是選擇經過@Query
來設置。
@Body
你們都清楚,咱們app不少時候跟服務器通訊,會選擇直接使用POST方式將json字符串做爲請求體發送到服務器,那麼咱們看看這個需求使用retrofit
該如何實現。
再次添加一個方法:
public interface IUserBiz { @POST("add") Call<List<User>> addUser(@Body User user); }
提交的代碼其實基本都是一致的:
//省略retrofit的構建代碼 Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com")); //省略call執行相關代碼
ok,能夠看到其實就是使用@Body
這個註解標識咱們的參數對象便可,那麼這裏須要考慮一個問題,retrofit是如何將user對象轉化爲字符串呢?下文將詳細解釋~
下面對應okhttp,還有兩種requestBody,一個是FormBody
,一個是MultipartBody
,前者以表單的方式傳遞簡單的鍵值對,後者以POST表單的方式上傳文件能夠攜帶參數,retrofit
也兩者也有對應的註解,下面繼續~
@FormUrlEncoded
這裏咱們模擬一個登陸的方法,添加一個方法:
public interface IUserBiz { @POST("login") @FormUrlEncoded Call<User> login(@Field("username") String username, @Field("password") String password); }
訪問的代碼:
//省略retrofit的構建代碼 Call<User> call = userBiz.login("zhy", "123"); //省略call執行相關代碼
ok,看起來也很簡單,經過@POST
指明url,添加FormUrlEncoded
,而後經過@Field
添加參數便可。
@Multipart
下面看一下單文件上傳,依然是再次添加個方法:
public interface IUserBiz { @Multipart @POST("register") Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password); }
這裏@MultiPart
的意思就是容許多個@Part
了,咱們這裏使用了3個@Part
,第一個咱們準備上傳個文件,使用了MultipartBody.Part
類型,其他兩個均爲簡單的鍵值對。
使用:
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"));
ok,這裏感受略爲麻煩。不過仍是蠻好理解~~多個@Part
,每一個Part對應一個RequestBody。
這裏插個實驗過程,其實我最初對於文件,也是嘗試的@Part RequestBody
,由於@Part("key")
,而後傳入一個表明文件的RequestBody
,我以爲更加容易理解,後來發現試驗沒法成功,並且查了下issue,給出了一個很奇怪的解決方案,這裏能夠參考:retrofit#1063。
給出了一個相似以下的方案:
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); }
能夠看到對於文件的那個@Part
value居然寫了這麼多奇怪的東西,並且filename居然硬編碼了~~這個很差吧,我上傳的文件名居然不能動態指定。
爲了文件名不會被寫死,因此給出了最上面的上傳單文件的方法,ps:上面這個方案經測試也是能夠上傳成功的。
恩,這個奇怪方案,爲何這麼作可行,下文會給出很是詳細的解釋。
最後看下多文件上傳~
@PartMap
再添加一個方法~~~
public interface IUserBiz { @Multipart @POST("register") Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password); }
這裏使用了一個新的註解@PartMap
,這個註解用於標識一個Map,Map的key爲String類型,表明上傳的鍵值對的key(與服務器接受的key對應),value即爲RequestBody,有點相似@Part
的封裝版本。
執行的代碼:
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進一個或多個文件,鍵值對等,固然你也能夠分開,單獨的鍵值對也可使用@Part
,這裏又看到設置文件的時候,相對應的key很奇怪,例如上例"photos\"; filename=\"icon.png"
,前面的photos就是與服務器對應的key,後面filename是服務器獲得的文件名,ok,參數雖然奇怪,可是也能夠動態的設置文件名,不太影響使用~~
這個其實我以爲直接使用okhttp就行了,使用retrofit去作這個事情真的有點瞎用的感受~~
增長一個方法:
@GET("download") Call<ResponseBody> downloadTest();
調用:
Call<ResponseBody> call = userBiz.downloadTest();
call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { InputStream is = response.body().byteStream(); //save file } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { } });
能夠看到能夠返回ResponseBody
,那麼不少事都能幹了~~
but,也看出這種方式下載感受很是雞肋,而且onReponse回調雖然在UI線程,可是你仍是要處理io操做,也就是說你在這裏還要另外開線程操做,或者你能夠考慮同步的方式下載。
最後仍是建議使用okhttp去下載,例如使用okhttputils.
有人可能會問,使用okhttp,和使用retrofit會不會形成新建多個OkHttpClient
對象呢,實際上是可設置的,參考下文。
ok,上面就是一些經常使用的方法,固然還涉及到一些沒有介紹的註解,可是經過上面這麼多方法的介紹,再多一二個註解的使用方式,相信你們可以解決。
這個須要簡單提一下,不少時候,好比你使用retrofit須要統一的log管理,給每一個請求添加統一的header等,這些都應該經過okhttpclient去操做,好比addInterceptor
例如:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,統一的header等 { @Override public okhttp3.Response intercept(Chain chain) throws IOException { return null; } }).build();
或許你須要更多的配置,你能夠單獨寫一個OkhttpClient的單例生成類,在這個裏面完成你所需的全部的配置,而後將OkhttpClient
實例經過方法公佈出來,設置給retrofit。
設置方式:
Retrofit retrofit = new Retrofit.Builder() .callFactory(OkHttpUtils.getClient()) .build();
callFactory
方法接受一個okhttp3.Call.Factory
對象,OkHttpClient
即爲一個實現類。
ok,接下來咱們隊retrofit的源碼作簡單的分析,首先咱們看retrofit如何爲咱們的接口實現實例;而後看總體的執行流程;最後再看詳細的細節;
經過上文的學習,咱們發現使用retrofit須要去定義一個接口,而後能夠經過調用retrofit.create(IUserBiz.class);
方法,獲得一個接口的實例,最後經過該實例執行咱們的操做,那麼retrofit如何實現咱們指定接口的實例呢?
其實原理是:動態代理。可是不要被動態代理這幾個詞嚇唬到,Java中已經提供了很是簡單的API幫助咱們來實現動態代理。
看源碼前先看一個例子:
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() { @Override public 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("參數:" + a + " , " + b); GET get = method.getAnnotation(GET.class); System.out.println("註解:" + get.value()); return null; } }); iTest.add(3, 5); }
輸出結果爲:
方法名:add
參數:3 , 5 註解:/heiheihei
能夠看到咱們經過Proxy.newProxyInstance
產生的代理類,當調用接口的任何方法時,都會調用InvocationHandler#invoke
方法,在這個方法中能夠拿到傳入的參數,註解等。
試想,retrofit也能夠經過一樣的方式,在invoke方法裏面,拿到全部的參數,註解信息而後就能夠去構造RequestBody
,再去構建Request
,獲得Call
對象封裝後返回。
ok,下面看retrofit#create
的源碼:
public <T> T create(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { }); }
哈,和上面對應。到這裏,你應該明白retrofit爲咱們接口生成實例對象並不神奇,僅僅是使用了Proxy
這個類的API而已,而後在invoke
方法裏面拿到足夠的信息去構建最終返回的Call而已。
哈,其實真正的動態代理通常是有具體的實現類的,只是在這個類調用某個方法的先後去執行一些別的操做,好比開事務,打log等等。固然,本博文並不須要涉及這些詳細的內容,若是你但願詳細去了解,能夠搜索關鍵字:Proxy InvocationHandler
。
這裏依然是經過構造者模式進行構建retrofit對象,好在其內部的成員變量比較少,咱們直接看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); }
new OkHttpClient()
,可見若是你須要對okhttpclient進行詳細的設置,須要構建OkHttpClient
對象,而後傳入;Class.forName("")
進行查找,具體代碼已經被放到文末,若是是Android平臺,會自定義一個Executor
對象,而且利用Looper.getMainLooper()
實例化一個handler對象,在Executor
內部經過handler.post(runnable)
,ok,整理憑大腦應該能構思出來,暫不貼代碼了。responseBody
轉化爲對象等;固然不只僅是針對返回的數據,還能用於通常備註解的參數的轉化例如@Body
標識的對象作一些操做,後面遇到源碼詳細再描述。ok,整體就這幾個對象去構造retrofit,還算比較少的~~
咱們構造完成retrofit,就能夠利用retrofit.create方法去構建接口的實例了,上面咱們已經分析了這個環節利用了動態代理,並且咱們也分析了具體的Call的構建流程在invoke方法中,下面看代碼:
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); //... return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object... args){ //... ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
主要也就三行代碼,第一行是根據咱們的method將其包裝成ServiceMethod,第二行是經過ServiceMethod和方法的參數構造retrofit2.OkHttpCall
對象,第三行是經過serviceMethod.callAdapter.adapt()
方法,將OkHttpCall
進行代理包裝;
下面一個一個介紹:
#Retrofit class
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = new ServiceMethod.Builder(this, method).build(); serviceMethodCache.put(method, result); } } return result; } #ServiceMethod public ServiceMethod build() { callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); if (responseType == Response.class || responseType == okhttp3.Response.class) { throw methodError("'" + Utils.getRawType(responseType).getName() + "' is not a valid response body type. Did you mean ResponseBody?"); } responseConverter = createResponseConverter(); for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount]; for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } return new ServiceMethod<>(this); }
直接看build方法,首先拿到這個callAdapter最終拿到的是咱們在構建retrofit裏面時adapterFactories時添加的,即爲:new ExecutorCallbackCall<>(callbackExecutor, call)
,該ExecutorCallbackCall
惟一作的事情就是將本來call的回調轉發至UI線程。
接下來經過callAdapter.responseType()
返回的是咱們方法的實際類型,例如:Call<User>
,則返回User
類型,而後對該類型進行判斷。
接下來是createResponseConverter
拿到responseConverter對象,其固然也是根據咱們構建retrofit時,addConverterFactory
添加的ConverterFactory對象來尋找一個合適的返回,尋找的依據主要看該converter可否處理你編寫方法的返回值類型,默認實現爲BuiltInConverters
,僅僅支持返回值的實際類型爲ResponseBody
和Void
,也就說明了默認狀況下,是不支持Call<User>
這類類型的。
接下來就是對註解進行解析了,主要是對方法上的註解進行解析,那麼能夠拿到httpMethod以及初步的url(包含佔位符)。
後面是對方法中參數中的註解進行解析,這一步會拿到不少的ParameterHandler
對象,該對象在toRequest()
構造Request的時候調用其apply方法。
ok,這裏咱們並無去一行一行查看代碼,其實意義也不太大,只要知道ServiceMethod主要用於將咱們接口中的方法
轉化爲一個Request對象
,因而根據咱們的接口返回值肯定了responseConverter,解析咱們方法上的註解拿到初步的url,解析咱們參數上的註解拿到構建RequestBody所需的各類信息,最終調用toRequest的方法完成Request的構建。
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
this.serviceMethod = serviceMethod; this.args = args; }
serviceMethod.callAdapter.adapt(okHttpCall)
咱們已經肯定這個callAdapter是ExecutorCallAdapterFactory.get()
對應代碼爲:
final class ExecutorCallAdapterFactory extends CallAdapter.Factory { final Executor callbackExecutor; ExecutorCallAdapterFactory(Executor callbackExecutor) { this.callbackExecutor = callbackExecutor; } @Override public 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
對象,繼續往下看:
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僅僅是對Call對象進行封裝,相似裝飾者模式,只不過將其執行時的回調經過callbackExecutor進行回調到UI線程中去了。
在4.2.2咱們已經拿到了通過封裝的ExecutorCallbackCall
類型的call對象,實際上就是咱們實際在寫代碼時拿到的call對象,那麼咱們通常會執行enqueue
方法,看看源碼是怎麼作的
首先是ExecutorCallbackCall.enqueue
方法,代碼在4.2.2,能夠看到除了將onResponse和onFailure回調到UI線程,主要的操做仍是delegate完成的,這個delegate實際上就是OkHttpCall對象,咱們看它的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() { @Override public 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); } @Override public 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(); } } }); }
沒有任何神奇的地方,內部實際上就是okhttp的Call對象,也是調用okhttp3.Call.enqueue
方法。
中間對於okhttp3.Call
的建立代碼爲:
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
完成對request的構建,經過request去構造call對象,而後返回.
中間還涉及一個parseResponse
方法,若是順利的話,執行的代碼以下:
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對ResponseBody進行轉化,而後返回,轉化實際上就是經過responseConverter
的convert方法。
#ServiceMethod
T toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body); }
ok,關於responseConverter
後面還會細說,不用擔憂。
到這裏,咱們整個源碼的流程分析就差很少了,目的就掌握一個大致的原理和執行流程,瞭解下幾個核心的類。
那麼總結一下:
Proxy
類完成動態代理的相關代理toRequest
構造出okhttp3.Request
對象。有了okhttp3.Request
對象就能夠很天然的構建出okhttp3.call
,最後calladapter對Call進行裝飾返回。ok,瞭解這麼多足以。
下面呢,有幾個地方須要注意,一方面是一些特殊的細節;另外一方面就是Converter
。
第一個問題涉及到文件上傳,還記得咱們在單文件上傳那裏所說的嗎?有種相似於hack的寫法,上傳文件是這麼作的?
public interface ApiInterface { @Multipart @POST ("/api/Accounts/editaccount") Call<User> editUser (@Part("file_key\"; filename=\"pp.png"),@Part("username") String username); }
首先咱們一點明確,由於這裏使用了@ Multipart
,那麼咱們認爲@Part
應當支持普通的key-value,以及文件。
對於普通的key-value是沒問題的,只須要這樣@Part("username") String username
。
那麼對於文件,爲何須要這樣呢?@Part("file_key\"; filename=\"pp.png")
這個value設置的值不用看就會以爲特別奇怪,然而卻能夠正常執行,緣由是什麼呢?
緣由是這樣的:
當上傳key-value的時候,實際上對應這樣的代碼:
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""), RequestBody.create(null, params.get(key)));
也就是說,咱們的@Part
轉化爲了
Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")
這麼一看,很隨意,只要把key放進去就能夠了。
可是,retrofit2並無對文件作特殊處理,文件的對應的字符串應該是這樣的
Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");
與鍵值對對應的字符串相比,多了個;filename="filename.png
,就由於retrofit沒有作特殊處理,因此你如今看這些hack的作法
@Part("file_key\"; filename=\"pp.png") 拼接:==> Content-Disposition", "form-data; name=\"" + key + "\" 結果:==> Content-Disposition", "form-data; name=file_key\"; filename=\"pp.png\"
ok,到這裏我相信你已經理解了,爲何要這麼作,並且爲何這麼作能夠成功!
恩,值得一提的事,由於這種方式文件名寫死了,咱們上文使用的的是@Part MultipartBody.Part file
,能夠知足文件名動態設置,這個方式貌似也是2.0.1的時候支持的。
上述相關的源碼:
#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()); }
能夠看到呢,並無對文件作特殊處理,估計下個版本說不定@Part
會多個isFile=true|false
屬性,甚至修改對應形參,而後在這裏作簡單的處理。
ok,最後來到關鍵的ConverterFactory
了~
關於Converter.Factory
,確定是經過addConverterFactory
設置的
Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .build();
該方法接受的是一個Converter.Factory factory
對象
該對象呢,是一個抽象類,內部包含3個方法:
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個方法都是空方法而不是抽象的方法,也就代表了咱們能夠選擇去實現其中的1個或多個方法,通常只須要關注requestBodyConverter
和responseBodyConverter
就能夠了。
ok,咱們先看如何自定義,最後再看GsonConverterFactory.create
的源碼。
先來個簡單的,實現responseBodyConverter
方法,看這個名字很好理解,就是將responseBody進行轉化就能夠了。
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") @FormUrlEncoded Call<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(); }
不知不覺,方法還蠻多的,假設哈,咱們這裏去掉retrofit構造時的GsonConverterFactory.create
,本身實現一個Converter.Factory
來作數據的轉化工做。
首先咱們解決responseBodyConverter
,那麼代碼很簡單,咱們能夠這麼寫:
public class UserConverterFactory extends Converter.Factory { @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { //根據type判斷是不是本身能處理的類型,不能的話,return null ,交給後面的Converter.Factory return 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; } @Override public T convert(ResponseBody responseBody) throws IOException { String result = responseBody.string(); T users = gson.fromJson(result, type); return users; } }
使用的時候呢,能夠
Retrofit retrofit = new Retrofit.Builder() .callFactory(new OkHttpClient()) .baseUrl("http://example/springmvc_users/user/") //.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(new UserConverterFactory()) .build();
ok,這樣的話,就能夠完成咱們的ReponseBody
到List<User>
或者User
的轉化了。
能夠看出,咱們這裏用的依然是Gson,那麼有些同窗確定不但願使用Gson就能實現,若是不使用Gson的話,通常須要針對具體的返回類型,好比咱們針對返回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; } @Override public 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; } }
這裏簡單讀取了一個屬性,你們確定能看懂,這樣就能知足返回值是Call<List<User>>
或者Call<User>
.
這裏鄭重提醒:若是你針對特定的類型去寫Converter
,必定要在UserConverterFactory#responseBodyConverter
中對類型進行檢查,發現不能處理的類型return null
,這樣的話,能夠交給後面的Converter.Factory
處理,好比本例咱們能夠按照下列方式檢查:
public class UserConverterFactory extends Converter.Factory { @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { //根據type判斷是不是本身能處理的類型,不能的話,return null ,交給後面的Converter.Factory if (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
方法告一段落了,謹記就是將reponseBody->返回值返回中的實際類型,例如Call<User>中的User
;還有對於該converter不能處理的類型必定要返回null。
ok,上面接口一大串方法呢,使用了咱們的Converter以後,有個方法咱們如今仍是不支持的。
@POST("add")
Call<List<User>> addUser(@Body User user);
ok,這個@Body
須要用到這個方法,叫作requestBodyConverter
,根據參數轉化爲RequestBody
,下面看下咱們如何提供支持。
public class UserRequestBodyConverter<T> implements Converter<T, RequestBody> { private Gson mGson = new Gson(); @Override public RequestBody convert(T value) throws IOException { String string = mGson.toJson(value); return RequestBody.create(MediaType.parse("application/json; charset=UTF-8"),string); } }
而後在UserConverterFactory中複寫requestBodyConverter方法,返回便可:
public class UserConverterFactory extends Converter.Factory { @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return new UserRequestBodyConverter<>(); } }
這裏偷了個懶,使用Gson將對象轉化爲json字符串了,若是你不喜歡使用框架,你能夠選擇拼接字符串,或者反射寫一個支持任何對象的,反正就是對象->json字符串的轉化。最後構造一個RequestBody返回便可。
ok,到這裏,我相信若是你看的細緻,自定義Converter.Factory
是幹嗎的,可是我仍是要總結下:
@Body
註解,完成ResponseBody到實際的返回類型的轉化,這個類型對應Call<XXX>
裏面的泛型XXX,其實@Part
等註解也會須要responseBodyConverter,只不過咱們的參數類型都是RequestBody
,由默認的converter處理了。其實通常狀況下看源碼呢,可讓咱們更好的去使用這個庫,固然在看的過程當中若是發現了一些比較好的處理方式呢,是很是值得記錄的。若是每次看別人的源碼都能吸收必定的精華,比你單純的去理解會好不少,由於你的記憶力再好,源碼解析你也是會忘的,而你記錄下來並可以使用的優越的代碼,可能用久了就成爲你的代碼了。
我舉個例子:好比retrofit2中判斷當前運行的環境代碼以下,若是下次你有這樣的需求,你也能夠這麼寫,甚至源碼中根據不一樣的運行環境還提供了不一樣的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(); }