編輯:關於Android編程
隨著Google對HttpClient的摒棄,和Volley的逐漸沒落,OkHttp開始異軍突起,而Retrofit則對okHttp進行了強制依賴。Retrofit也是Square公司開發的一款針對Android網絡請求的框架,其實質就是對okHttp的封裝,使用面向接口的方式進行網絡請求,利用動態生成的代理類封裝了網絡接口。retrofit非常適合於RESTful url格式的請求,更多使用注解的方式提供功能。
既然是RESTful架構,那麼我們就來看一下什麼是REST吧。
REST(REpresentational State Transfer)是一組架構約束條件和原則。RESTful架構都滿足以下規則:
(1)每一個URI代表一種資源;
(2)客戶端和服務器之間,傳遞這種資源的某種表現層;
(3)客戶端通過四個HTTP動詞(GET,POST,PUT,DELETE),對服務器端資源進行操作,實現”表現層狀態轉化”。
Eclipse的用戶,添加Jar包和網絡訪問權限
下載最新的jar:我將整理的所有jar包已上傳
注意:
1.Retrofit必須使用okhttp請求了,如果項目中沒有okhttp的依賴的話,肯定會出錯 。
2.okhttp內部依賴okio所以也要添加。
在retrofit中通過一個Java接口作為http請求的api接口。
//定以接口 public interface GitHubService { @GET("users/{user}/repos") Call> listRepos(@Path("user") String user); }
/**獲取實例*/ Retrofit retrofit = new Retrofit.Builder() //設置OKHttpClient,如果不設置會提供一個默認的 .client(new OkHttpClient()) //設置baseUrl .baseUrl("https://api.github.com/") //添加Gson轉換器 .addConverterFactory(GsonConverterFactory.create()) .build();
注:
1.retrofit2.0後:BaseUrl要以/結尾;@GET 等請求不要以/開頭;@Url: 可以定義完整url,不要以 / 開頭。
2.addConverterFactory提供Gson支持,可以添加多種序列化Factory,但是GsonConverterFactory必須放在最後,否則會拋出異常。
GitHubService service = retrofit.create(GitHubService.class); //同步請求 //https://api.github.com/users/octocat/repos Call> call = service.listRepos("octocat"); try { Response
> repos = call.execute(); } catch (IOException e) { e.printStackTrace(); } //不管同步還是異步,call只能執行一次。否則會拋 IllegalStateException Call
> clone = call.clone(); //異步請求 clone.enqueue(new Callback
>() { @Override public void onResponse(Response
> response, Retrofit retrofit) { // Get result bean from response.body() List
repos = response.body(); // Get header item from response String links = response.headers().get("Link"); /** * 不同於retrofit1 可以同時操作序列化數據javabean和header */ } @Override public void onFailure(Throwable throwable) { showlog(throwable.getCause().toString()); } });
我們可以終止一個請求。終止操作是對底層的httpclient執行cancel操作。即使是正在執行的請求,也能夠立即終止。
call.cancel();
public interface IWeatherGet { @GET("GetMoreWeather?cityCode=101020100&weatherType=0") CallgetWeather(); }
可以看到有一個getWeather()方法,通過@GET注解標識為get請求,@GET中所填寫的value和baseUrl組成完整的路徑,baseUrl在構造retrofit對象時給出。
Retrofit retrofit = new Retrofit.Builder() /**http://weather.51wnl.com/weatherinfo/GetMoreWeather?cityCode=101020100&weatherType=0*/ //注意baseurl要以/結尾 .baseUrl("http://weather.51wnl.com/weatherinfo/") .addConverterFactory(GsonConverterFactory.create()) .build(); IWeatherGet weather = retrofit.create(IWeatherGet.class); Callcall = weather.getWeather(); call.enqueue(new Callback () { @Override public void onResponse(Response response, Retrofit retrofit) { Weather weather = response.body(); WeatherInfo weatherinfo = weather.weatherinfo; showlog("weather="+weatherinfo.toString()); } @Override public void onFailure(Throwable throwable) { showlog(throwable.getCause().toString()); } });
上面說的@GET注解是將baseUrl和@GET中的value組成完整的路徑。有時候我們可以將路徑中某個字符串設置為不同的值來請求不同的數據,這時候怎麼辦呢?
譬如:
//用於訪問上海天氣 http://weather.51wnl.com/weatherinfo/GetMoreWeather?cityCode=101020100&weatherType=0 //用於訪問上海人口(這裡只是假設,其實這個url並不能返回json) http://weather.51wnl.com/weatherinfo/GetMorePeople?cityCode=101010100&weatherType=0
即通過不同的請求字符串訪問不同的信息,返回數據為json字符串。那麼可以通過retrofit提供的@PATH注解非常方便的完成上述需求。
public interface IWeatherPath { @GET("{info}?cityCode=101020100&weatherType=0") CallgetWeather(@Path("info") String info); }
可以看到我們定義了一個getWeather方法,方法接收一個info參數,並且我們的@GET注解中使用{info}?cityCode=101020100&weatherType=0聲明了訪問路徑,這裡你可以把{info}當做占位符,而實際運行中會通過@PATH(“info”)所標注的參數進行替換。
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://weather.51wnl.com/weatherinfo/") .addConverterFactory(GsonConverterFactory.create()) .build(); IWeatherPath weather = retrofit.create(IWeatherPath.class); Callcall = weather.getWeather("GetMoreWeather"); call.enqueue(new Callback () { @Override public void onResponse(Response response, Retrofit retrofit) { Weather weather = response.body(); WeatherInfo weatherinfo = weather.weatherinfo; showlog("weather="+weatherinfo.toString()); } @Override public void onFailure(Throwable throwable) { showlog(throwable.getCause().toString()); } });
文章開頭提過,retrofit非常適用於restful url的格式,那麼例如下面這樣的url:
//用於訪問上海天氣 http://weather.51wnl.com/weatherinfo/GetMoreWeather?cityCode=101020100&weatherType=0 //用於訪問北京天氣 http://weather.51wnl.com/weatherinfo/GetMoreWeather?cityCode=101010100&weatherType=0
即通過傳參方式使用不同的citycode訪問不同城市的天氣,返回數據為json字符串。我們可以通過@Query注解方便的完成,我們再次在接口中添加一個方法:
public interface IWeatherQuery { @GET("GetMoreWeather") CallgetWeather(@Query("cityCode") String cityCode, @Query("weatherType") String weatherType); }
/**省略retrofit的構建代碼*/ Callcall = weather.getWeather("101020100", "0"); //Call call = weather.getWeather("101010100", "0"); /**省略call執行相關代碼*/
當我們的參數過多的時候我們可以通過@QueryMap注解和map對象參數來指定每個表單項的Key,value的值,同樣是上面的例子,還可以這樣寫:
public interface IWeatherQueryMap { @GET("GetMoreWeather") CallgetWeather(@QueryMap Map map); }
//省略retrofit的構建代碼 Mapmap = new HashMap (); map.put("cityCode", "101020100"); map.put("weatherType", "0"); Call call = weather.getWeather(map); //省略call執行相關代碼
這樣我們就完成了參數的指定,當然相同的方式也適用於POST,只需要把注解修改為@POST即可。
注:對於下面的寫法:
@GET("GetMoreWeather?cityCode={citycode}&weatherType=0") CallgetWeather(@Path("citycode") String citycode);
乍一看可以啊,實際上運行是不支持的~估計是@Path的定位就是用於url的路徑而不是參數,對於參數還是選擇通過@Query來設置。
我們app很多時候跟服務器通信,會選擇直接使用POST方式將json字符串作為請求體發送到服務器,那麼我們看看這個需求使用retrofit該如何實現。
public interface IUser { @POST("add") Call> addUser(@Body User user); }
/省略retrofit的構建代碼 Call> call = user.addUser(new User("watson", "male", "28")); //省略call執行相關代碼
可以看到其實就是使用@Body這個注解標識我們的參數對象即可,那麼這裡需要考慮一個問題,retrofit是如何將user對象轉化為字符串呢?將實例對象根據轉換方式轉換為對應的json字符串參數,這個轉化方式是GsonConverterFactory定義的。
對應okhttp,還有兩種requestBody,一個是FormBody,一個是MultipartBody,前者以表單的方式傳遞簡單的鍵值對,後者以POST表單的方式上傳文件可以攜帶參數,retrofit也二者也有對應的注解,下面繼續~
這裡我們模擬一個登錄的方法,添加一個方法:
public interface IUser { @FormUrlEncoded @POST("login") Calllogin(@Field("username") String username, @Field("password") String password); }
//省略retrofit的構建代碼 Callcall = user.login("watson", "123"); //省略call執行相關代碼
看起來也很簡單,通過@POST指明url,添加FormUrlEncoded,然後通過@Field添加參數即可。
當我們有很多個表單參數時也可以通過@FieldMap注解和Map對象參數來指定每個表單項的Key,value的值。
public interface IUser { @FormUrlEncoded @POST("login") Calllogin(@FieldMap Map fieldMap); }
//省略retrofit的構建代碼 Mappropertity = new HashMap (); positories.put("name", "watson"); positories.put("password", "123"); Call call = user.login(propertity); //省略call執行相關代碼
涉及到操作硬盤文件,首先需要添加權限:
1.下面先看一下單文件上傳,依然是再次添加個方法:
public interface IUser { @Multipart @POST("register") CallregisterUser(@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); Callcall = user.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
這裡感覺略為麻煩。不過還是蠻好理解~~多個@Part,每個Part對應一個RequestBody。
注:這裡還有另外一個方案也是可行的:
public interface ApiInterface { @Multipart @POST ("/api/Accounts/editaccount") CalleditUser (@Header("Authorization") String authorization, @Part("photos\"; filename=\"icon.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id); }
這個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="photos";filename="icon.png"");
與鍵值對對應的字符串相比,多了個\”; filename=\”icon.png,就因為retrofit沒有做特殊處理,所以你現在看這些hack的做法
@Part("photos\"; filename=\"icon.png") ==> key = photos\"; filename=\"icon.png form-data; name=\"" + key + "\" 拼接結果:==> form-data; name="photos"; filename="icon.png"
因為這種方式文件名寫死了,我們上文使用的的是@Part MultipartBody.Part file,可以滿足文件名動態設置。
2.如果是多文件上傳呢?
public interface IUser { @Multipart @POST("register") CallregisterUser(@PartMap Map params, @Part("password") RequestBody password); }
這裡使用了一個新的注解@PartMap,這個注解用於標識一個Map,Map的key為String類型,代表上傳的鍵值對的key(與服務器接受的key對應),value即為RequestBody,有點類似@Part的封裝版本。
File file = new File(Environment.getExternalStorageDirectory(), "local.png"); RequestBody photo = RequestBody.create(MediaType.parse("image/png", file); Mapmap = new HashMap<>(String, RequestBody); map.put("photos\"; filename=\"icon.png", photo); map.put("username", RequestBody.create(null, "abc")); Call call = user.registerUser(map, RequestBody.create(null, "123"));
可以看到,可以在Map中put進一個或多個文件,鍵值對等,當然你也可以分開,單獨的鍵值對也可以使用@Part,這裡又看到設置文件的時候,相對應的key很奇怪,例如上例”photos\”; filename=\”icon.png”,前面的photos就是與服務器對應的key,後面filename是服務器得到的文件名,ok,參數雖然奇怪,但是也可以動態的設置文件名,不影響使用。
下載文件還是推薦OkHttp方式,這裡對retrofit下載也進行說明一下
@GET("download") CalldownloadTest();
Callcall = user.downloadTest(); call.enqueue(new Callback () { @Override public void onResponse(Call call, Response response) { InputStream is = response.body().byteStream(); //save file } @Override public void onFailure(Call call, Throwable t){} });
可以看到這種方式下載非常雞肋,onReponse回調雖然在UI線程,但是你還是要處理io操作,也就是說你在這裡還要另外開線程操作,或者你可以考慮同步的方式下載。所以還是建議使用okhttp去下載。
@Header:header處理,不能被互相覆蓋,所有具有相同名字的header將會被包含到請求中。
//靜態設置Header值 @Headers("Authorization: authorization") @GET("widget/list") CallgetUser()
@Headers 用於修飾方法,用於設置多個Header值。
@Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" }) @GET("users/{username}") CallgetUser(@Path("username") String username);
還可以使用@Header注解動態的更新一個請求的header。必須給@Header提供相應的參數,如果參數的值為空header將會被忽略,否則就調用參數值的toString()方法並使用返回結果。
//動態設置Header值 @GET("user") CallgetUser(@Header("Authorization") String authorization)
很多時候,比如你使用retrofit需要統一的log管理,緩存管理,給每個請求添加統一的header等,這些都應該通過okhttpclient去操作。Retrofit 2.0 底層依賴於okHttp,所以需要使用okHttp的Interceptors來對所有請求進行攔截。
OkHttpClient client = new OkHttpClient(); client.networkInterceptors().add(new Interceptor() { @Override public com.squareup.okhttp.Response intercept(Chain chain) throws IOException { com.squareup.okhttp.Response response = chain.proceed(chain.request()); // Do anything with response here return response; } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) ... .client(client) //傳入自己定義的client .build();
或許你需要更多的配置,你可以單獨寫一個OkhttpClient的單例生成類,在這個裡面完成你所需的所有的配置,然後將OkhttpClient實例通過方法公布出來,設置給retrofit。
Retrofit retrofit = new Retrofit.Builder() .callFactory(OkHttpUtils.getClient()) .build();
callFactory方法接受一個okhttp3.Call.Factory對象,OkHttpClient即為一個實現類。
在上面的例子中通過獲取ResponseBody後,我們自己使用Gson來解析接收到的Json格式數據。在Retrofit中當創建一個Retrofit實例的時候可以為其添加一個Json轉換器,這樣就會自動將Json格式的響應體轉換為所需要的Java對象。
Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) //轉換器 .build();
默認情況下,Retrofit只能夠反序列化Http體為OkHttp的ResponseBody類型,並且只能夠接受ResponseBody類型的參數作為@body。
添加轉換器可以支持其他的類型,為了方便的適應流行的序列化庫,Retrofit提供了六個兄弟模塊:
關於Converter.Factory,肯定是通過addConverterFactory設置的
Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .build();
該方法接受的是一個Converter.Factory factory對象,該對象是一個抽象類,內部包含3個方法:
abstract class Factory { public ConverterresponseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } public Converter stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } }
可以看到呢,3個方法都是空方法而不是抽象的方法,也就表明了我們可以選擇去實現其中的1個或多個方法,一般只需要關注requestBodyConverter和responseBodyConverter就可以了。
(1)responseBodyConverter
實現responseBodyConverter方法,看這個名字很好理解,就是將responseBody進行轉化就可以了。
假設我們這裡去掉retrofit構造時的GsonConverterFactory.create,自己實現一個Converter.Factory來做數據的轉化工作。首先我們解決responseBodyConverter,那麼代碼很簡單,我們可以這麼寫:
public class UserConverterFactory extends Converter.Factory { @Override public ConverterresponseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { //根據type判斷是否是自己能處理的類型,不能的話,return null ,交給後面的Converter.Factory return new UserResponseConverter(type); } } public class UserResponseConverter implements Converter { 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; } }
使用自定義UserConverterFactory
Retrofit retrofit = new Retrofit.Builder() .callFactory(new OkHttpClient()) .baseUrl("http://example/springmvc_users/user/") .addConverterFactory(new UserConverterFactory()) .build();
這樣的話,就可以完成我們的ReponseBody到List<\User>或者User的轉化了。
可以看出,我們這裡用的依然是Gson,那麼有些同學肯定不希望使用Gson就能實現,如果不使用Gson的話,一般需要針對具體的返回類型,比如我們針對返回List<\User>或者User
public class UserResponseConverterimplements Converter { 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 parseUsers(String result) { List 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; } }
這裡簡單讀取了一個屬性,大家肯定能看懂,這樣就能實現我們的ReponseBody到List<\User>或者User的轉化了。
這裡鄭重提醒:如果你針對特定的類型去寫Converter,一定要在UserConverterFactory#responseBodyConverter中對類型進行檢查,發現不能處理的類型return null,這樣的話,可以交給後面的Converter.Factory處理,比如本例我們可以按照下列方式檢查:
public class UserConverterFactory extends Converter.Factory { @Override public ConverterresponseBodyConverter(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 { Type rawType = ((ParameterizedType) type).getRawType(); Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0]; if (rawType == List.class && actualType == User.class) { return new UserResponseConverter(type); } } return null; } }
(2)requestBodyConverter
上面接口一大串方法呢,使用了我們的Converter之後,有個方法我們現在還是不支持的。
@POST("add") Call> addUser(@Body User user);
這個@Body需要用到這個方法,叫做requestBodyConverter,根據參數轉化為RequestBody,下面看下我們如何提供支持。
public class UserRequestBodyConverterimplements Converter { 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 requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return new UserRequestBodyConverter<>(); } }
ok,到這裡,我相信如果你看的細致,相信已經學會了如何自定義Converter.Factory,但是我還是要總結下:
1. responseBodyConverter:主要完成ResponseBody到實際的返回類型的轉化,這個類型對應Call<\XXX>裡面的泛型XXX。
2. requestBodyConverter:完成對象到RequestBody的構造。主要是對應@Body注解,其實@Part等注解也會需要requestBodyConverter,只不過我們的參數類型都是RequestBody,由默認的converter處理了。
3. 一定要注意,檢查type如果不是自己能處理的類型,記得return null (因為可以添加多個,你不能處理return null ,還會去遍歷後面的converter).
接下來我們對retrofit的源碼做簡單的分析,首先我們看retrofit如何為我們的接口實現實例;然後看整體的執行流程;最後再看詳細的細節;
(1)retrofit如何為我們的接口實現實例
使用retrofit需要去定義一個接口,然後可以通過調用retrofit.create(IUser.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對象封裝後返回。
下面看retrofit#create的源碼:
publicT create(final Class 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而已。
(2)retrofit整體實現流程
Retrofit的構建:這裡依然是通過構造者模式進行構建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. ListadapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); // Make a defensive copy of the converters. List converterFactories = new ArrayList<>(this.converterFactories); return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); }
baseUrl必須指定,這個是理所當然的;
然後可以看到如果不著急設置callFactory,則默認直接new OkHttpClient(),可見如果你需要對okhttpclient進行詳細的設置,需要構建OkHttpClient對象,然後傳入;
接下來是callbackExecutor,這個想一想大概是用來將回調傳遞到UI線程了,當然這裡設計的比較巧妙,利用platform對象,對平台進行判斷,判斷主要是利用Class.forName(“”)進行查找,如果是Android平台,會自定義一個Executor對象,並且利用Looper.getMainLooper()實例化一個handler對象,在Executor內部通過handler.post(runnable),ok,整理憑大腦應該能構思出來,暫不貼代碼了。
接下來是adapterFactories,這個對象主要用於對Call進行轉化,基本上不需要我們自己去自定義。
最後是converterFactories,該對象用於轉化數據,例如將返回的responseBody轉化為對象等;當然不僅僅是針對返回的數據,還能用於一般備注解的參數的轉化例如@Body標識的對象做一些操作,後面遇到源碼詳細再描述。
具體Call構建流程:我們構造完成retrofit,就可以利用retrofit.create方法去構建接口的實例了,上面我們已經分析了這個環節利用了動態代理,而且我們也分析了具體的Call的構建流程在invoke方法中,下面看代碼:
publicT create(final Class 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進行代理包裝;
下面一個一個介紹:
ServiceMethod應該是最復雜的一個類了,包含了將一個method轉化為Call的所有的信息。
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方法。
這裡我們並沒有去一行一行查看代碼,其實意義也不太大,只要知道ServiceMethod主要用於將我們接口中的方法轉化為一個Request對象,於是根據我們的接口返回值確定了responseConverter,解析我們方法上的注解拿到初步的url,解析我們參數上的注解拿到構建RequestBody所需的各種信息,最終調用toRequest的方法完成Request的構建。
接下來看OkHttpCall的構建,構造函數僅僅是簡單的賦值
OkHttpCall(ServiceMethodserviceMethod, 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> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { if (getRawType(returnType) != Call.class) { return null; } final Type responseType = Utils.getCallResponseType(returnType); return new CallAdapter >() { @Override public Type responseType() { return responseType; } @Override public Call adapt(Call call) { return new ExecutorCallbackCall<>(callbackExecutor, call); } }; }
可以看到adapt返回的是ExecutorCallbackCall對象,繼續往下看:
static final class ExecutorCallbackCallimplements Call { final Executor callbackExecutor; final Call delegate; ExecutorCallbackCall(Executor callbackExecutor, Call delegate) { this.callbackExecutor = callbackExecutor; this.delegate = delegate; } @Override public void enqueue(final Callback callback) { if (callback == null) throw new NullPointerException("callback == null"); delegate.enqueue(new Callback () { @Override public void onResponse(Call call, final Response 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 call, final Throwable t) { callbackExecutor.execute(new Runnable() { @Override public void run() { callback.onFailure(ExecutorCallbackCall.this, t); } }); } }); } @Override public Response execute() throws IOException { return delegate.execute(); } }
可以看出ExecutorCallbackCall僅僅是對Call對象進行封裝,類似裝飾者模式,只不過將其執行時的回調通過callbackExecutor進行回調到UI線程中去了。
執行Call:我們已經拿到了經過封裝的ExecutorCallbackCall類型的call對象,實際上就是我們實際在寫代碼時拿到的call對象,那麼我們一般會執行enqueue方法,看看源碼是怎麼做的,首先是ExecutorCallbackCall.enqueue方法,代碼見上面,可以看到除了將onResponse和onFailure回調到UI線程,主要的操作還是delegate完成的,這個delegate實際上就是OkHttpCall對象,我們看它的enqueue方法
public void enqueue(final Callbackcallback) { 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 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 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方法,如果順利的話,執行的代碼如下:
ResponseparseResponse(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方法。
T toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body); }
到這裡,我們整個源碼的流程分析就差不多了,目的就掌握一個大體的原理和執行流程,了解下幾個核心的類。
總結一下:
首先構造retrofit,幾個核心的參數呢,主要就是baseurl,callFactory(默認okhttpclient),converterFactories,adapterFactories,excallbackExecutor。 然後通過create方法拿到接口的實現類,這裡利用Java的Proxy類完成動態代理的相關代理 在invoke方法內部,拿到我們所聲明的注解以及實參等,構造ServiceMethod,ServiceMethod中解析了大量的信息,最終可以通過toRequest構造出okhttp3.Request對象。有了okhttp3.Request對象就可以很自然的構建出okhttp3.call,最後calladapter對Call進行裝飾返回。 拿到Call就可以執行enqueue或者execute方法了。在Google發布了support:design:23+以後我們發現有這麼一個東西TextInputLayout,先看下效果圖:<android.support.d
ant 工具:1、為什麼要用到ant這個工具呢?Ant做為一種工具已經廣泛被使用,並且歷史悠久。使用ant的內置命令,可以編譯java源文件(javac),運行java文
一 功能圖二 講解思路1 回顧上一篇內容2 創建加載圖片類(同時創建xib)3 點擊圖片查看大圖4 點擊查看大圖(查看長圖)5 model出展示圖片的控制器6 保存圖片7
本人使用Android開發有一段時間了,但是本身沒有系統學,而且多年專注服務端開發,總覺得因為項目需要接觸Android移動端開發只是暫時的,所以沒有太上心,結果碰到一個