关闭

android 网络操作新框架Retrofit

标签: androidjavajava设计模式
315人阅读 评论(0) 收藏 举报
分类:

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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15892次
    • 积分:270
    • 等级:
    • 排名:千里之外
    • 原创:10篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条