android 网络操作新框架Retrofit

来源:互联网 发布:airplayiphone连mac 编辑:程序博客网 时间:2024/05/19 00:10

Android Retrofit 使用

1.定义

    最近这一年内Retrofit在Android开发中使用是非常的火,使用这个框架后网络请求代码边的非常的简洁,提高了代码的维护性和可读性。
   官网地址:Retrofit官网
   官网上面对他的定义是:

A type-safe HTTP client for Android and Java


根据定义来看是一种Http的请求工具,目前为止这种http请求的工具框架是很对的,比如google的Volley,以及OkHttp。读他们的源码你会发现他们其实都是对原始的Http代码加以封装,实现代码的重用和耦合。简化了代码的调用。能够为我们更好的使用。

2.如何使用


  1.定义你返回的数据和请求的方式
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);}
   这个的意思是采用的请求方式是GET.
返回的数据是经过Gson解析成一个集合,传递的参数有四个,
2.创建Retrofit对象实现获取数据
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() );   }});

这是最简单的使用方法,服务器端采用的webApi的方式来搞的。因为他集成了Gson的解析过程
所以我们在定义服务的时候就就将解析给加上了。这样一个网络请求加数据解析就完成了。

这个用法看上去和其他的有关网络请求的操作是完全不一样的,比如Volley,其他的网络请求要设置请求方式,Url,参数,以及成功失败的回调操作,还有网络请求等,要写一系列的代码,而采用Retrofit的话我们只需要定义一个接口,设置请求方式,以及参数就行了。非常的便捷。

但是你从本质上而言,其实Retrofit和其他网络请求的方式本质上是一样的,只是这个框架 描述HTTP请求的方式不一样而已 。因此, 你可以发现上面的 GitHubService 接口其实就是 Retrofit 对一个HTTP请求的描述。

3.Retrofit实现原理

对于接口服务使用,官网上面给出的这句解释我完全没有看懂。
The Retrofit class generates an implementation of the GitHubService interface.
实例化Retrofit对象采用的是建造者的模式来创建的,创建的同时已经设置好了请求的地址,和JSON的自动转换。
但是下面这句代码完全没有搞懂,读源码才发现。这个采用的(java的动态代理)
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 { *   &#64;POST("category/{cat}/") *   Call&lt;List&lt;Item&gt;&gt; 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源码分析

要彻底的理解Retrofit的原理的话,还是要从源码入手看看。官网上面的版本目前是2.0.2.
Retrofit的源码分为二部分,结构图如下所示。



1.Http包,这个是一些http请求的基本使用方法的注解,包括HTTP,POST,GET,PUSH,HEAD的注解。结构如下图:



2.其他结构的包,包括对GSON的解析转换,真正的网络请求是交给OkHttp来执行的,所以代码是比较少的。

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);  }}
看到最后二个方法了吗?这才是这个类的核心所在。

Call<T> 
这个接口才是开始发送请求的回调,代码如下
/** * 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>   * &#64;Override   * public &lt;R&gt; Async&lt;R&gt; adapt(final Call&lt;R&gt; call) {   *   return Async.create(new Callable&lt;Response&lt;R&gt;&gt;() {   *     &#64;Override   *     public Response&lt;R&gt; 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 的代码了,收获还是很多的,也更加深入的理解面向接口的编程方法,这个写代码就是 好的代码就是依赖接口而不是实现 最好的例子.这样的就是好的代码,扩展性强、低耦合、插件化。可以说对面向对象的设计是出神入化。    

1 0
原创粉丝点击