手寫簡易retrofit
首先我們知道retrofit底層是基於OkHttp的:
接著我們通過一個測試類,看一下retrofit的一般使用方法:
public class RetrofitTest { interface Weather{ @GET("/v3/weather/weatherInfo") Call get(@Query("city") String city, @Query("key") String key); } @Test public void test(){ Retrofit retrofit = new Retrofit.Builder().baseUrl("http://restapi.amap.com/").build(); Weather weather = retrofit.creat(Weather.class); Call call = weather.get("北京", "13cb58f5884f9749287abbead9c658f2"); try { Response response = call.execute(); System.out.println(response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }
我們可以看出Retrofit是通過Builder模式生成的,這樣的優點應該都知道,不僅能幫我們初始化一系列的引數,還可以讓呼叫者不用過於關心內部構造。
接著我們看Retrofit的creat傳入的是一個介面類,所以這裡應該是會用到代理模式,通過代理模式拿到Weather的方法,再通過反射的方法獲取到我們需要的地址的字尾和我們需要傳遞的引數。
public class Retrofit { private HttpUrl baseUrl; private Call.Factory callFactory; private ConcurrentHashMap<Method,ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>(); public Retrofit(Builder builder) { this.baseUrl = builder.baseUrl; this.callFactory = builder.callFactory; } public HttpUrl baseUrl() { return baseUrl; } public Call.Factory callFactory() { return callFactory; } public <T> T creat(Class<T> clazz){ return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //採集資料 ServiceMethod serviceMethod = loadServiceMethod(method); return serviceMethod.toCall(args); } }); } private ServiceMethod loadServiceMethod(Method method){ ServiceMethod serviceMethod = serviceMethodCache.get(method); if(null == serviceMethod){ //採集 serviceMethod = new ServiceMethod.Builder(this,method).bulid(); serviceMethodCache.putIfAbsent(method,serviceMethod); } return serviceMethod; } /* 構建者模式 */ public static final class Builder{ private HttpUrl baseUrl; private Call.Factory callFactory; public Builder baseUrl(String baseUrl){ this.baseUrl = HttpUrl.parse(baseUrl); return this; } public Builder callFactory(Call.Factory callFactory){ this.callFactory = callFactory; return this; } public Retrofit build(){ if(baseUrl == null){ throw new IllegalArgumentException("baseUrl not set"); } if(callFactory == null){ callFactory = new OkHttpClient(); } return new Retrofit(this); } } }
這裡有幾點需要說一下:
1、creat方法:其實creat方法主要的功能就是通過動態代理獲取到介面類的例項,然後通過例項獲取到請求方法的各個引數(包括註解上面的地址字尾和每個介面需要傳遞的引數);我在這裡把這些放到了一個快取中,這樣也可以加快效率。在
2、callFactory方法:其實retrofit中這種工廠設計模式使我們想要使用它的理由之一,它的這種插拔式的使用讓我們可以把資料轉為Gson或者Xml等型別的資料,並且還可以和RxJava組合使用,這裡沒有在向外延伸。
3、因為呼叫weather的get方法返回的是Call物件,所以需要在creat方法中的invoke中返回Call物件。這裡的Call是okhttp3包下的。
public Call toCall(Object[] args) { //1.建立Request Request.Builder requestBuilder = new Request.Builder(); //1.1地址 if (urlBuilder == null) { urlBuilder = baseUrl.newBuilder(relativeUrl); } //1.2如果是get請求,將引數放到地址中 for (int i = 0; i < parameterHandlers.length; i++) { parameterHandlers[i].apply(this, String.valueOf(args[i])); } if (formBuilder != null) { formBody = formBuilder.build(); } requestBuilder.url(urlBuilder.build()); Request request = requestBuilder.method(httpMethod, formBody).build(); //2.建立call returncallFactory.newCall(request); }
4、因為獲取到方法上面的引數需要拼接和判斷(這裡只做了GET和POST)。所以用了一個serviceMethod來處理這一系列的問題;
public ServiceMethod bulid() { //處理方法的註解 for (Annotation annotation : methodAnnotation) { //GET paseMethodAnnotation(annotation); } //處理引數的註解 parameterHandlers = new ParameterHandler[parameterAnnotations.length]; //遍歷引數註解 for (int i = 0; i < parameterAnnotations.length; i++) { Annotation[] parameterAnnotation = parameterAnnotations[i]; //遍歷一個引數上的左右註解 for (Annotation annotation : parameterAnnotation) { if (annotation instanceof Query) { Query query = (Query) annotation; String value = query.value(); parameterHandlers[i] = new ParameterHandler.Query(value); } else if (annotation instanceof Field) { Field field = (Field) annotation; String value = field.value(); parameterHandlers[i] = new ParameterHandler.Filed(value); } } } return new ServiceMethod(this); }
這樣的話我們把地址和引數都拼裝好了,而且也獲取到Call物件了,然後執行call.execute()就可以獲取到Response了。
try { Response response = call.execute(); System.out.println(response.body().string()); } catch (IOException e) { e.printStackTrace(); }
總結:對於不理解retrofit的人來說,剛開始對於retrofit的用法和原理都是一頭霧水,還有一些人都是停止在會用retrofit的方法之上就沒有深入研究。其實可以簡單的對retrofit理解為:它就是對okhttp的一個封裝,只不過可擴充套件性更強。內部的根本原理就是OkHttp。因為內部對於請求方法的類使用了代理模式獲取請求地址和想要傳遞的引數,所以請求方法類必須使用介面,並且在Retrofit的內部使用 ConverterFactory工廠類來讓使用者選擇返回資料的格式(可以是Gson,也可以是XML等各種資料格式),更加的方便靈活。甚至還可以通過CallAdapterFactory的介面卡模式來與RxJava組合使用,這就是靈活之處。