Android Retrofit 使用
1.定义
A type-safe HTTP client for Android and Java
2.如何使用
这个的意思是采用的请求方式是GET.public interface GitHubService { //获取用户列表 @GET("GetUserListByResource") Call<List<GsonBean>> getUserInfo(@Query("application") String application, @Query("orgGuid") String orgGuid, @Query("resourceName") String resourceName, @Query("isRootOrg") String isRootOrg); }
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://地址/api/UserData/") .addConverterFactory(GsonConverterFactory.create()) .build(); GitHubService service = retrofit.create(GitHubService.class); Call<List<GsonBean>> call = service.getUserInfo("parmeara1","pamera2","pamera3","pamera4"); call.enqueue(new Callback<List<GsonBean>>() { @Override public void onResponse(Call<List<GsonBean>> call, Response<List<GsonBean>> response) { try{ if(response != null){
//这里返回的就是你定义的数据的格式是一个集合列表,我这里只是 if(response.body() != null ){ for(GsonBean entity : response.body()){ if(entity != null){ LogUtil.i("Account==",entity.getAccount()); LogUtil.i("Name==",entity.getName()); LogUtil.i("手机==",entity.get手机()); LogUtil.i("创建时间==",entity.get创建时间()); } } } } }catch (Exception e){ e.printStackTrace(); } } @Override public void onFailure(Call<List<GsonBean>> call, Throwable t) { LogUtil.d("数据===", call.toString() ); } });
这个用法看上去和其他的有关网络请求的操作是完全不一样的,比如Volley,其他的网络请求要设置请求方式,Url,参数,以及成功失败的回调操作,还有网络请求等,要写一系列的代码,而采用Retrofit的话我们只需要定义一个接口,设置请求方式,以及参数就行了。非常的便捷。
但是你从本质上而言,其实Retrofit和其他网络请求的方式本质上是一样的,只是这个框架 描述HTTP请求的方式不一样而已 。因此, 你可以发现上面的 GitHubService 接口其实就是 Retrofit
对一个HTTP请求的描述。
3.Retrofit实现原理
Retrofit
class generates an implementation of the
GitHubService interface.
GitHubService service = retrofit.create(GitHubService.class);
/** * Create an implementation of the API endpoints defined by the {@code service} interface. * <p> * The relative path for a given method is obtained from an annotation on the method describing * the request type. The built-in methods are {@link retrofit2.http.GET GET}, * {@link retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH * PATCH}, {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE} and * {@link retrofit2.http.OPTIONS OPTIONS}. You can use a custom HTTP method with * {@link HTTP @HTTP}. For a dynamic URL, omit the path on the annotation and annotate the first * parameter with {@link Url @Url}. * <p> * Method parameters can be used to replace parts of the URL by annotating them with * {@link retrofit2.http.Path @Path}. Replacement sections are denoted by an identifier * surrounded by curly braces (e.g., "{foo}"). To add items to the query string of a URL use * {@link retrofit2.http.Query @Query}. * <p> * The body of a request is denoted by the {@link retrofit2.http.Body @Body} annotation. The * object will be converted to request representation by one of the {@link Converter.Factory} * instances. A {@link RequestBody} can also be used for a raw representation. * <p> * Alternative request body formats are supported by method annotations and corresponding * parameter annotations: * <ul> * <li>{@link retrofit2.http.FormUrlEncoded @FormUrlEncoded} - Form-encoded data with key-value * pairs specified by the {@link retrofit2.http.Field @Field} parameter annotation. * <li>{@link retrofit2.http.Multipart @Multipart} - RFC 2388-compliant multipart data with * parts specified by the {@link retrofit2.http.Part @Part} parameter annotation. * </ul> * <p> * Additional static headers can be added for an endpoint using the * {@link retrofit2.http.Headers @Headers} method annotation. For per-request control over a * header annotate a parameter with {@link Header @Header}. * <p> * By default, methods return a {@link Call} which represents the HTTP request. The generic * parameter of the call is the response body type and will be converted by one of the * {@link Converter.Factory} instances. {@link ResponseBody} can also be used for a raw * representation. {@link Void} can be used if you do not care about the body contents. * <p> * For example: * <pre> * public interface CategoryService { * @POST("category/{cat}/") * Call<List<Item>> categoryList(@Path("cat") String a, @Query("page") int b); * } * </pre> */ @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety. public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }看到注释了吗?我看到注释也是懵逼了,这么长的注释看完只知道这个是一个接口,采用的是代理的方式来执行,结 果返回了一个动态代理的调用。那么你对java的动态代理熟悉不?如果不熟悉的话,问题就来了,因为你不熟悉动态代理的话你根本就看不懂代码。那建议看看这篇文章java动态代理详解。
接下来这行代码
Each Call
from the created GitHubService
can make a synchronous or asynchronous HTTP request to the remote webserver.
Call<List<GsonBean>> call = service.getUserInfo("parmeara1","pamera2","pamera3","pamera4");
//意思是通过创建的接口来得到Call对象,call对象是用来获取数据采用同步或者异步的方式从服务器端。这个执行的结果是通过这行代码生成了一个Http的请求,包括header和Url,Params等这些参数。然后通过call.enqueue(CallBack<List<GsonBean>>)
执行异步或者同步的网络操作。相当于发送一个请求到服务器端。这样就玩成了整个操作。
4.Retrofit源码分析
Callback<T>
这个接口中定义了二个方法一个请求成功后用来回调接收http响应数据的。另外一个是请求失败后的通知操作。
public interface Callback<T> { /** * Invoked for a received HTTP response. * <p> * Note: An HTTP response may still indicate an application-level failure such as a 404 or 500. * Call {@link Response#isSuccessful()} to determine if the response indicates success. */ void onResponse(Call<T> call, Response<T> response); /** * Invoked when a network exception occurred talking to the server or when an unexpected * exception occurred creating the request or processing the response. */ void onFailure(Call<T> call, Throwable t); }
第二个接口Convert<F,T>,这个接口是用来转换数据的。源码结构如下
/** * Convert objects to and from their representation in HTTP. Instances are created by {@linkplain * Factory a factory} which is {@linkplain Retrofit.Builder#addConverterFactory(Factory) installed} * into the {@link Retrofit} instance. */ public interface Converter<F, T> { T convert(F value) throws IOException; /** Creates {@link Converter} instances based on a type and target usage. */ abstract class Factory { /** * Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if * {@code type} cannot be handled by this factory. This is used to create converters for * response types such as {@code SimpleResponse} from a {@code Call<SimpleResponse>} * declaration. */ public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } /** * Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if * {@code type} cannot be handled by this factory. This is used to create converters for types * specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap} * values. */ public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } /** * Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if * {@code type} cannot be handled by this factory. This is used to create converters for types * specified by {@link Field @Field}, {@link FieldMap @FieldMap} values, * {@link Header @Header}, {@link Path @Path}, {@link Query @Query}, and * {@link QueryMap @QueryMap} values. */ public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } } }
这个设计还是蛮诡异的,接口里面创建一个类。我平时在代码中很少这样写,这个接口是何GsonConverterFactory这个类一起使用的。核心的代码都在这里了
public final class GsonConverterFactory extends Converter.Factory { /** * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will use UTF-8. */ public static GsonConverterFactory create() { return create(new Gson()); } /** * Create an instance using {@code gson} for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will use UTF-8. */ public static GsonConverterFactory create(Gson gson) { return new GsonConverterFactory(gson); } private final Gson gson; private GsonConverterFactory(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); this.gson = gson; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } }看到最后二个方法了吗?这才是这个类的核心所在。
/** * An invocation of a Retrofit method that sends a request to a webserver and returns a response. * Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple * calls with the same parameters to the same webserver; this may be used to implement polling or * to retry a failed call. * * <p>Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link * #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that * is busy writing its request or reading its response may receive a {@link IOException}; this is * working as designed. * * @param <T> Successful response body type. */ public interface Call<T> extends Cloneable { /** * Synchronously send the request and return its response. * * @throws IOException if a problem occurred talking to the server. * @throws RuntimeException (and subclasses) if an unexpected error occurs creating the request * or decoding the response. */ Response<T> execute() throws IOException; /** * Asynchronously send the request and notify {@code callback} of its response or if an error * occurred talking to the server, creating the request, or processing the response. */ void enqueue(Callback<T> callback); /** * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain * #enqueue(Callback) enqueued}. It is an error to execute or enqueue a call more than once. */ boolean isExecuted(); /** * Cancel this call. An attempt will be made to cancel in-flight calls, and if the call has not * yet been executed it never will be. */ void cancel(); /** True if {@link #cancel()} was called. */ boolean isCanceled(); /** * Create a new, identical call to this one which can be enqueued or executed even if this call * has already been. */ Call<T> clone(); /** The original HTTP request. */ Request request(); }
看到没有你们熟悉的方法,其实这个Call主要使用的是okHttp中的Call。只是在这里做了一个转换,你看Retrofit源码中的create方法。
enqueue(CallBack<T>); 异步的请求
Response<T> execute() ;同步请求
CallAdapter<T>这个接口中有一个属性responeType。还有一个<R> T adapt(Call<R> call)的方法,这个接口的实现只有一个DefaultCallAdapter。这个方法的主要作用格式将Call对象转换成另一个对象,可能是为了支持RxJava。这个在项目中目前还没有用。后续估计会加入。
CallAdapter的代码我贴上来。
/** * Adapts a {@link Call} into the type of {@code T}. Instances are created by {@linkplain Factory a * factory} which is {@linkplain Retrofit.Builder#addCallAdapterFactory(Factory) installed} into * the {@link Retrofit} instance. */ public interface CallAdapter<T> { /** * Returns the value type that this adapter uses when converting the HTTP response body to a Java * object. For example, the response type for {@code Call<Repo>} is {@code Repo}. This type * is used to prepare the {@code call} passed to {@code #adapt}. * <p> * Note: This is typically not the same type as the {@code returnType} provided to this call * adapter's factory. */ Type responseType(); /** * Returns an instance of {@code T} which delegates to {@code call}. * <p> * For example, given an instance for a hypothetical utility, {@code Async}, this instance would * return a new {@code Async<R>} which invoked {@code call} when run. * <pre><code> * @Override * public <R> Async<R> adapt(final Call<R> call) { * return Async.create(new Callable<Response<R>>() { * @Override * public Response<R> call() throws Exception { * return call.execute(); * } * }); * } * </code></pre> */ <R> T adapt(Call<R> call); /** * Creates {@link CallAdapter} instances based on the return type of {@linkplain * Retrofit#create(Class) the service interface} methods. */ abstract class Factory { /** * Returns a call adapter for interface methods that return {@code returnType}, or null if it * cannot be handled by this factory. */ public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit); /** * Extract the upper bound of the generic parameter at {@code index} from {@code type}. For * example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}. */ protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } /** * Extract the raw class type from {@code type}. For example, the type representing * {@code List<? extends Runnable>} returns {@code List.class}. */ protected static Class<?> getRawType(Type type) { return Utils.getRawType(type); } } }在Retrofit中使用的时候采用这种方式来实现;
public Builder addCallAdapterFactory(CallAdapter.Factory factory) { adapterFactories.add(checkNotNull(factory, "factory == null")); return this; }
5.总结
Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求
Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)
Retrofit中接口设计的恰到好处,在你创建Retrofit对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的 、使用不同的ConverAdapter ,这也就提供了你使用RxJava来调用Retrofit的可能
我也慢慢看了Picasso 和 Retrofit 的代码了,收获还是很多的,也更加深入的理解面向接口的编程方法,这个写代码就是 好的代码就是依赖接口而不是实现 最好的例子.这样的就是好的代码,扩展性强、低耦合、插件化。可以说对面向对象的设计是出神入化。