Retrofit是一個不錯的網絡請求庫,用官方本身的介紹就是:java
A type-safe REST client for Android and Javaandroid
看官網的介紹用起來很省事,不過若是不瞭解它是怎麼實現的也不太敢用,否則出問題了就不知道怎麼辦了。這幾天比較閒就下下來看了一下,瞭解一下大概實現方法,細節就不追究了。先來看一個官網的例子,詳細說明去網官看git
首先定義請求接口,即程序中都須要什麼請求操做github
public interface GitHubService { @GET("/users/{user}/repos") List<Repo> listRepos(@Path("user") String user); }
而後經過RestAdapter
生成一個剛纔定義的接口的實現類,使用的是動態代理。api
RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.github.com") .build(); GitHubService service = restAdapter.create(GitHubService.class);
如今就能夠調用接口進行請求了數組
List<Repo> repos = service.listRepos("octocat");
使用就是這麼簡單,請求時直接調用接口就好了,甚至不用封裝參數,由於參數的信息已經在定義接口時經過Annotation定義好了。緩存
從上面的例子能夠看到接口直接返回了須要的Java類型,而不是byte[]或String,解析數據的地方就是Converter
,這個是能夠自定義的,默認是用Gson
解析,也就是說默認認爲服務器返回的是Json數據,能夠經過指定不一樣的Convert
使用不一樣的解析方法,如用Jackson
解析Json,或自定義XmlConvert解析xml數據。服務器
Retrofit的使用就是如下幾步:網絡
RestAdapter
生成一個接口的實現類(動態代理)接口的定義要用用Rtrofit定義的一些Annotation,因此先看一下Annotation的。數據結構
以上面的示例中的接口來看
@GET("/group/{id}/users") List<User> groupList(@Path("id") int groupId);
先看@GET
/** Make a GET request to a REST path relative to base URL. */ @Documented @Target(METHOD) @Retention(RUNTIME) @RestMethod("GET") public @interface GET { String value(); }
@GET自己也被幾個Anotation註解,@Target表示@GET註解是用於方法的,value方法就返回這個註解的value值,在上例中就是/group/{id}/users,而後就是@RestMethod
@Documented @Target(ANNOTATION_TYPE) @Retention(RUNTIME) public @interface RestMethod { String value(); boolean hasBody() default false; }
RestMethod
是一個用於Annotation的Annotation,好比上面的例子中用來註解的@GET,value方法就返回GET,hasBody表示是否有Body,對於POST這個方法就返回true
@Documented @Target(METHOD) @Retention(RUNTIME) @RestMethod(value = "POST", hasBody = true) public @interface POST { String value(); }
Retrofit的Annotation包含請求方法相關的@GET、@POST、@HEAD、@PUT、@DELETA、@PATCH,和參數相關的@Path、@Field、@Multipart等。
定義了Annotation要就有解析它的方法,在Retrofit中解析的位置就是RestMethodInfo
,但在這以前須要先看哪裏使用了RestMethodInfo
,前面說了Retrofit使用了動態代理生成了咱們定義的接口的實現類,而這個實現類是經過RestAdapter.create
返回的,因此使用動態代理的位置就是RestAdapter
,接下來就看一下RestAdapter
。
RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.github.com") .build(); GitHubService service = restAdapter.create(GitHubService.class); public RestAdapter build() { if (endpoint == null) { throw new IllegalArgumentException("Endpoint may not be null."); } ensureSaneDefaults(); return new RestAdapter(endpoint, clientProvider, httpExecutor, callbackExecutor, requestInterceptor, converter, profiler, errorHandler, log, logLevel); }
setEndPoint
就不說了,接口中定義的都是相對Url,EndPoint就是域名,build
方法調用ensureSaneDefaults()
方法,而後就構造了一個RestAdapter對象,構造函數的參數中傳入了EndPoint外的幾個對象,這幾個對象就是在ensureSaneDefaults()
中初始化的。
private void ensureSaneDefaults() { if (converter == null) { converter = Platform.get().defaultConverter(); } if (clientProvider == null) { clientProvider = Platform.get().defaultClient(); } if (httpExecutor == null) { httpExecutor = Platform.get().defaultHttpExecutor(); } if (callbackExecutor == null) { callbackExecutor = Platform.get().defaultCallbackExecutor(); } if (errorHandler == null) { errorHandler = ErrorHandler.DEFAULT; } if (log == null) { log = Platform.get().defaultLog(); } if (requestInterceptor == null) { requestInterceptor = RequestInterceptor.NONE; } }
ensureSaneDefaults()
中初始化了不少成員,errorHandler、log就不看了,其餘的除了requestInterceptor
都是經過Platform
對象得到的,因此要先看下Platform
private static final Platform PLATFORM = findPlatform(); static final boolean HAS_RX_JAVA = hasRxJavaOnClasspath(); 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) { } if (System.getProperty("com.google.appengine.runtime.version") != null) { return new AppEngine(); } return new Base(); }
使用了單例的PLATFORM
,經過findPlatform()
初始化實例,若是是Android平臺就使用Platform.Android
,若是是Google AppEngine就使用Platform.AppEngine
,不然使用Platform.Base
,這些都是Platform
的子類,其中AppEngine
又是Base
的子類。
Platform
是一個抽象類,定義瞭如下幾個抽象方法,這幾個方法的做用就是返回一些RestAdapter
中須要要用到成員的默認實現
abstract Converter defaultConverter(); // 默認的Converter,用於將請求結果轉化成須要的數據,如GsonConverter將JSON請求結果用Gson解析成Java對象 abstract Client.Provider defaultClient(); // Http請求類,若是是AppEngine就使用`UrlFetchClient`,不然若是有OKHttp就使用OKHttp,若是是Android,2.3之後使用HttpURLConnection,2.3之前使用HttpClient abstract Executor defaultHttpExecutor(); // 用於執行Http請求的Executor abstract Executor defaultCallbackExecutor(); // Callback調用中用於執行Callback的Executor(多是同步的) abstract RestAdapter.Log defaultLog(); // Log接口,用於輸出Log
Platform
的接口再看
ensureSaneDefaults
就清楚了,初始化轉化數據的Converter、執行請求的Client、執行請求的Executor、執行Callback的Executor、Log輸出類、錯誤處理類和用於在請求前添加額外處理的攔截請求的Interceptor。
Converter
默認都是用的GsonConverter
,就不看了,defaultClient
返回執行網絡請求的Client
Platform.Android
@Override Client.Provider defaultClient() { final Client client; if (hasOkHttpOnClasspath()) { client = OkClientInstantiator.instantiate(); } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { client = new AndroidApacheClient(); } else { client = new UrlConnectionClient(); } return new Client.Provider() { @Override public Client get() { return client; } }; }
@Override Client.Provider defaultClient() { final Client client; if (hasOkHttpOnClasspath()) { client = OkClientInstantiator.instantiate(); } else { client = new UrlConnectionClient(); } return new Client.Provider() { @Override public Client get() { return client; } }; }
Platform.AppEngine
@Override Client.Provider defaultClient() { final UrlFetchClient client = new UrlFetchClient(); return new Client.Provider() { @Override public Client get() { return client; } }; }
對於Android,優先使用OKHttp,不然2.3之後使用HttpUrlConnection,2.3之前使用HttpClient
defaultHttpExecutor
就是返回一個Executor,執行請求的線程在這個Executor中執行,就作了一件事,把線程設置爲後臺線程
defaultCallbackExecutor
用於執行Callback類型的請求時,提供一個Executor執行Callback的Runnable
Platform.Base
@Override Executor defaultCallbackExecutor() { return new Utils.SynchronousExecutor(); }
Platform.Android
@Override Executor defaultCallbackExecutor() { return new MainThreadExecutor(); }
SynchronousExecutor
static class SynchronousExecutor implements Executor { @Override public void execute(Runnable runnable) { runnable.run(); } }
public final class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); } }
Platform
看完了,RestAdapter的成員初始化完成,就要看怎麼經過RestAdapter.create
生成咱們定義的接口的實現類了
public <T> T create(Class<T> service) { Utils.validateServiceClass(service); return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new RestHandler(getMethodInfoCache(service))); } Map<Method, RestMethodInfo> getMethodInfoCache(Class<?> service) { synchronized (serviceMethodInfoCache) { Map<Method, RestMethodInfo> methodInfoCache = serviceMethodInfoCache.get(service); if (methodInfoCache == null) { methodInfoCache = new LinkedHashMap<Method, RestMethodInfo>(); serviceMethodInfoCache.put(service, methodInfoCache); } return methodInfoCache; } }
使用了動態代理,InvocationHandler
是RestHandler
,RestHandler
有一個參數,是Method
->RestMethodInfo
的映射,初始化時這個映射是空的。重點就是這兩個了:RestHandler
,RestMethodInfo
,
@Override public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { // 1 return method.invoke(this, args); } // Load or create the details cache for the current method. final RestMethodInfo methodInfo = getMethodInfo(methodDetailsCache, method); // 2 if (methodInfo.isSynchronous) { // 3 try { return invokeRequest(requestInterceptor, methodInfo, args); } catch (RetrofitError error) { Throwable newError = errorHandler.handleError(error); if (newError == null) { throw new IllegalStateException("Error handler returned null for wrapped exception.", error); } throw newError; } } if (httpExecutor == null || callbackExecutor == null) { throw new IllegalStateException("Asynchronous invocation requires calling setExecutors."); } // Apply the interceptor synchronously, recording the interception so we can replay it later. // This way we still defer argument serialization to the background thread. final RequestInterceptorTape interceptorTape = new RequestInterceptorTape(); requestInterceptor.intercept(interceptorTape); // 4 if (methodInfo.isObservable) { // 5 if (rxSupport == null) { if (Platform.HAS_RX_JAVA) { rxSupport = new RxSupport(httpExecutor, errorHandler); } else { throw new IllegalStateException("Observable method found but no RxJava on classpath"); } } return rxSupport.createRequestObservable(new Callable<ResponseWrapper>() { @Override public ResponseWrapper call() throws Exception { return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args); } }); } Callback<?> callback = (Callback<?>) args[args.length - 1]; // 6 httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor, errorHandler) { @Override public ResponseWrapper obtainResponse() { return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args); } }); return null; // Asynchronous methods should have return type of void. }
執行請求時會調用RestHandler
的invoke
方法,如上所示,主要是上面代碼中標註有6點
getMethodInfo
獲取調用的Method
對應的RestMethodInfo
,前面說了,構造RestHandler
對象時傳進來了一個Method
->RestMethodInfo
的映射,初始時是空的。static RestMethodInfo getMethodInfo(Map<Method, RestMethodInfo> cache, Method method) { synchronized (cache) { RestMethodInfo methodInfo = cache.get(method); if (methodInfo == null) { methodInfo = new RestMethodInfo(method); cache.put(method, methodInfo); } return methodInfo; }
在getMethodInfo
中判斷若是相應的映射不存在,就創建這個映射,並如名字所示緩存起來
3. 若是是同步調用(接口中直接返回數據,不經過Callback或Observe),直接調用invokeRequest
4. 若是是非同步調用,先經過RequestInterceptorTape
記錄攔截請求,記錄後在後臺線程作實際攔截,後面會提到。
5. 若是是Observe請求(RxJava),執行第5步,對RxJava不瞭解,略過
6. 若是是Callback形式,交由線程池執行
接口中的每個Method有一個對應的RestMethodInfo,關於接口中Annotation信息的處理就都在這裏了
private enum ResponseType { VOID, OBSERVABLE, OBJECT } RestMethodInfo(Method method) { this.method = method; responseType = parseResponseType(); isSynchronous = (responseType == ResponseType.OBJECT); isObservable = (responseType == ResponseType.OBSERVABLE); }
在構造函數中調用了parseResponseType
,parseResponseType
解析了方法簽名,根據方法的返回值類型及最後一個參數的類型判斷方法的類型是哪一種ResponseType
不管是哪一種ResponseType,最終都是調用invokeRequest
執行實際的請求,接下來依次看下invokeRequest
的執行步驟
第一步是調用methodInfo.init()
解析調用的方法,方法裏有作判斷,只在第一次調用時解析,由於處一次解析後這個對象就被緩存起來了,下次調同一個方法時能夠直接使用
synchronized void init() { if (loaded) return; parseMethodAnnotations(); parseParameters(); loaded = true; }
在RestMethodInfo.init
中分別調用
parseMethodAnnotations()
:解析全部方法的AnnotationparseParameters()
:解析全部參數的Annotationfor (Annotation methodAnnotation : method.getAnnotations()) { Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); RestMethod methodInfo = null; // Look for a @RestMethod annotation on the parameter annotation indicating request method. for (Annotation innerAnnotation : annotationType.getAnnotations()) { if (RestMethod.class == innerAnnotation.annotationType()) { methodInfo = (RestMethod) innerAnnotation; break; } } ... }
在parseMethodAnnotations
中,會獲取方法全部的Annotation並遍歷:
parsePath
解析請求的Url,requestParam(URL中問號後的內容)及Url中須要替換的參數名(Url中大括號括起來的部分)parseParameters
解析請求參數,即參數的Annotation,@PATH
、@HEADER
、@FIELD
等
第二步是RequestBuilder和Interceptor,這兩個是有關聯的,因此一塊兒看。
RequestBuilder requestBuilder = new RequestBuilder(serverUrl, methodInfo, converter); requestBuilder.setArguments(args); requestInterceptor.intercept(requestBuilder); Request request = requestBuilder.build();
先說RequestInterceptor,做用很明顯,當執行請求時攔截請求以作一些特殊處理,好比添加一些額外的請求參數。
/** Intercept every request before it is executed in order to add additional data. */ public interface RequestInterceptor { /** Called for every request. Add data using methods on the supplied {@link RequestFacade}. */ void intercept(RequestFacade request); interface RequestFacade { void addHeader(String name, String value); void addPathParam(String name, String value); void addEncodedPathParam(String name, String value); void addQueryParam(String name, String value); void addEncodedQueryParam(String name, String value); } /** A {@link RequestInterceptor} which does no modification of requests. */ RequestInterceptor NONE = new RequestInterceptor() { @Override public void intercept(RequestFacade request) { // Do nothing. } }; }
RequestInterceptor
只有一個方法intercept
,接收一個RequestFacade
參數,RequestFacade
是RequestInterceptor
內部的一個接口,這個接口的方法就是添加請求參數,Query、Header什麼的。大概能夠看出RequestInterceptor
的做用了,若是RequestFacade
表示一個請求相關的數據,RequestInteceptor.intercept
的做用就是向這個RequestFacade
中添加額外Header,Param等參數。
RequestFacade
的一個子類叫RequestBuilder
,用來處理Request
請求參數,在invokeRequest
中會對RequestBuilder
調用intercept
方法向RequestBuilder
添加額外的參數。
有一個叫RequestInterceptorTape
的類,同時實現了RequestFacade
與RequestInterceptor
,它的做用是:
RequestFacade
使用時做爲參數傳給一個RequestInteceptor
,這個RequestInterceptor
調用它的addHeader
等方法時,它把這些調用及參數記錄下來RequestInterceptor
使用時,將以前記錄的方法調用及參數從新應用到它的intercept
參數RequestFacade
中在RestHandler.invoke
中,若是判斷方法的調用不是同步調用,就經過下面的兩行代碼將用戶設置的interceptor須要添加的參數記錄到RequestInterceptorTape
,而後在invokeRequest
中再實際執行參數的添加。
// Apply the interceptor synchronously, recording the interception so we can replay it later. // This way we still defer argument serialization to the background thread. final RequestInterceptorTape interceptorTape = new RequestInterceptorTape(); requestInterceptor.intercept(interceptorTape);
RequestBuilder.setArguments()
解析調用接口時的實際參數。而後經過build()
方法生成一個Request
對象
第三步執行請求,Response response = clientProvider.get().execute(request);
第四步就是解析並分發請求結果了,成功請求時返回結果,解析失敗調用ErrorHandler
給用戶一個自定義異常的機會,但最終都是經過異常拋出到invoke()
中的,若是是同步調用,直接拋異常,若是是Callback調用,會回調Callback.failure
請求類型有同步請求,Callback請求,Observable請求,來看下Callback請求:
Callback<?> callback = (Callback<?>) args[args.length - 1]; httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor, errorHandler) { @Override public ResponseWrapper obtainResponse() { return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args); } });
Callback請求中函數最後一個參數是一個Callback的實例,httpExecutor是一個Executor,用於執行Runnable請求,咱們看到,這裏new了一個CallbackRunnable執行,並實現了它的obtainResponse方法,看實現:
abstract class CallbackRunnable<T> implements Runnable { private final Callback<T> callback; private final Executor callbackExecutor; private final ErrorHandler errorHandler; CallbackRunnable(Callback<T> callback, Executor callbackExecutor, ErrorHandler errorHandler) { this.callback = callback; this.callbackExecutor = callbackExecutor; this.errorHandler = errorHandler; } @SuppressWarnings("unchecked") @Override public final void run() { try { final ResponseWrapper wrapper = obtainResponse(); callbackExecutor.execute(new Runnable() { @Override public void run() { callback.success((T) wrapper.responseBody, wrapper.response); } }); } catch (RetrofitError e) { Throwable cause = errorHandler.handleError(e); final RetrofitError handled = cause == e ? e : unexpectedError(e.getUrl(), cause); callbackExecutor.execute(new Runnable() { @Override public void run() { callback.failure(handled); } }); } } public abstract ResponseWrapper obtainResponse(); }
就是一個普通的Runnable,在run方法中首先執行obtailResponse,從名字能夠看到是執行請求返回Response,這個從前面能夠看到執行了invokeRequest,和同步調用中同樣執行請求。
緊接着就提交了一個Runnable至callbackExecutor,在看Platform
時看到了callbackExecotor是經過Platform.get().defaultCallbackExecutor()
返回的,Android中是向主線程的一個Handler發消息
值得注意的事,對於同步調用,若是遇到錯誤是直接拋異常,而對於異步調用,是調用Callback.failure()
執行網絡請求,須要向服務端發送請求參數,如表單數據,上傳的文件等,一樣須要解析服務端返回的數據,在Retrofit中對這些作了封裝,位於Mime包中,也只有封裝了,纔好統一由指定的Converter執行數據的轉換
TypedInput
和TypedOutput
表示輸入輸出的數據,都包含mimeType,並分別支持讀入一個InputStream或寫到一個OutputStrem
/** * Binary data with an associated mime type. * * @author Jake Wharton (jw@squareup.com) */ public interface TypedInput { /** Returns the mime type. */ String mimeType(); /** Length in bytes. Returns {@code -1} if length is unknown. */ long length(); /** * Read bytes as stream. Unless otherwise specified, this method may only be called once. It is * the responsibility of the caller to close the stream. */ InputStream in() throws IOException; } /** * Binary data with an associated mime type. * * @author Bob Lee (bob@squareup.com) */ public interface TypedOutput { /** Original filename. * * Used only for multipart requests, may be null. */ String fileName(); /** Returns the mime type. */ String mimeType(); /** Length in bytes or -1 if unknown. */ long length(); /** Writes these bytes to the given output stream. */ void writeTo(OutputStream out) throws IOException; }
TypedByteArray
,內部數據是一個Byte數組
private final byte[] bytes; @Override public long length() { return bytes.length; } @Override public void writeTo(OutputStream out) throws IOException { out.write(bytes); } @Override public InputStream in() throws IOException { return new ByteArrayInputStream(bytes); }
TypedString
,繼承自TypedByteArray
,內部表示是同樣的
public TypedString(String string) { super("text/plain; charset=UTF-8", convertToBytes(string)); } private static byte[] convertToBytes(String string) { try { return string.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } }
其餘的也同樣,從名字很好理解:TypedFile
,MultipartTypedOutput
,FormEncodedTypedOutput
。
Retrofit對輸入和輸出作了封裝,經過TypedOutput
向服務器發送數據,經過TypedInput
讀取服務器返回的數據。
經過MultipartTypedOutput
支持文件上傳,讀取服務器數據時,若是要求直接返回未解析的Response,Restonse會被轉換爲TypedByteArray,因此不能是大文件類的
Retrofit支持不一樣的Log等級,當爲LogLevel.Full時會把Request及Response的Body打印出來,因此若是包含文件就不行了。
Retrofit默認使用GsonConverter,因此要想獲取原始數據不要Retrofit解析,要麼自定義Conveter,要麼直接返回Response了,返回Response也比較麻煩
整體來講Retrofit看起來很好用,不過要求服務端返回數據最好要規範,否則若是請求成功返回一種數據結構,請求失敗返回另外一種數據結構,很差用Converter解析,接口的定義也很差定義,除非都返回Response,或自定義Converter全部接口都返回String
在Twitter上JakeWharton這麼說:
Gearing up towards a Retrofit 1.6.0 release and then branching 1.x so we can push master towards a 2.0 and fix long-standing design issues.
要出2.0了,內部API會改,接口應該不怎麼變