您的位置:首页 > 理论基础 > 计算机网络

快速Android开发系列网络篇之Retrofit

2016-04-14 16:18 645 查看

快速Android开发系列网络篇之Retrofit

Retrofit是一个不错的网络请求库,用官方自己的介绍就是:


A type-safe REST client for Android and Java


看官网的介绍用起来很省事,不过如果不了解它是怎么实现的也不太敢用,不然出问题了就不知道怎么办了。这几天比较闲就下下来看了一下,了解一下大概实现方法,细节就不追究了。先来看一个官网的例子,详细说明去网官

简单示例

首先定义请求接口,即程序中都需要什么请求操作

public interface GitHubService {
@GET("/users/{user}/repos")
List<Repo> listRepos(@Path("user") String user);
}


然后通过
RestAdapter
生成一个刚才定义的接口的实现类,使用的是动态代理。

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的使用就是以下几步:

定义接口,参数声明,Url都通过Annotation指定

通过
RestAdapter
生成一个接口的实现类(动态代理)

调用接口请求数据

接口的定义要用用Rtrofit定义的一些Annotation,所以先看一下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 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


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;
}
};
}


Platform.Base

@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();
}
}


MainThreadExecutor

public final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());

@Override public void execute(Runnable r) {
handler.post(r);
}
}


如果是Android,通过Handler将回调发送到主线程执行,如果非Android,直接同步执行。
Platform
看完了,RestAdapter的成员初始化完成,就要看怎么通过
RestAdapter.create
生成我们定义的接口的实现类了

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点

如果调用的是Object的方法,不做处理直接调用。

通过
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信息的处理就都在这里了

RestMethodInfo

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
的执行步骤

RestAdapter.invokeRequest

第一步是调用
methodInfo.init()
解析调用的方法,方法里有做判断,只在第一次调用时解析,因为处一次解析后这个对象就被缓存起来了,下次调同一个方法时可以直接使用

synchronized void init() {
if (loaded) return;

parseMethodAnnotations();
parseParameters();

loaded = true;
}


RestMethodInfo.init
中分别调用

parseMethodAnnotations()
:解析所有方法的Annotation

parseParameters()
:解析所有参数的Annotation

for (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并遍历:


对于每一个Annotation,也会获取它的Annotation,看它是否是被RestMethod注解的Annotation,如果是,说明是@GET,@POST类型的注解,就调用
parsePath
解析请求的Url,requestParam(URL中问号后的内容)及Url中需要替换的参数名(Url中大括号括起来的部分)

寻找Headers Annotation解析Header参数

解析RequestType:SIMPLE,MULTIPART,FORM_URL_ENCODED

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


CallbackRunnable

请求类型有同步请求,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()


Mime

执行网络请求,需要向服务端发送请求参数,如表单数据,上传的文件等,同样需要解析服务端返回的数据,在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会改,接口应该不怎么变
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: