美文网首页
Retrofit2 源码解析

Retrofit2 源码解析

作者: 小帝Ele | 来源:发表于2016-08-31 01:24 被阅读41次

    本文的源码分析基于Retrofit 2,和Retrofit 1.0的Api有较大的不同, 本文主要分为几部分:1、Retrofit 是什么,2、Retrofit怎么用,3、Retrofit的原理是什么,4、Retrofit的源码分析。

    1 Retrofit是什么

    来自Retrofit官网的介绍:

    A type-safe HTTP client for Android and Java
    

    简单的说它是一个基于OkHttp的RESTFUL Api请求工具,从功能上来说和Google的Volley功能上很相似,但是使用上很不相似。Volley使用上更加原始而且符合使用者的直觉,当App要发送一个Http请求时,你需要先创建一个Request对象,指定这个Request用的是GET、POST或其他方法,一个api 地址,一个处理response的回调,如果是一个POST请求,那么你还需要给这个Request对象设置一个body,有时候你还需要自定义添加Header什么的,然后将这个Request对象添加到RequestQueue中,接下去检查Cache以及发送Http请求的事情,Volley会帮你处理。如果一个App中api不同的api请求很多,这样代码就会很难看。而Retrofit可以让你简单到调用一个Java方法的方式去请求一个api,这样App中的代码就会很简洁方便阅读

    2 Retrofit怎么用

    虽然Retrofit官网已经说明了,我还是要按照我的思路说一下它的使用方法

    首先,你需要创建一个Retrofit
    对象,并且指定api的域名:

    public static final String API_URL = "https://zhuanlan.zhihu.com";
    //Create a very simple REST adapter which points the Zhuanlan API.
    Retrofit retrofit = new Retrofit.Builder() 
                                  .baseUrl(API_URL) 
                                  .addConverterFactory(GsonConverterFactory.create()) 
                                  .build();
    

    其次,你要根据api新建一个Java接口,用Java注解来描述这个api

    public interface ZhuanLanApi { 
    
        @GET("/api/columns/{user} ") Call<ZhuanLanAuthor>
        getAuthor(@Path("user") String user)
    
    }
    

    再用这个retrofit对象创建一个ZhuanLanApi对象:

        ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
    
        Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
    

    这样就表示你要请求的api是https://zhuanlan.zhihu.com/api/columns/qinchao

    最后你就可以用这个call对象获得数据了,enqueue方法是异步发送http请求的,如果你想用同步的方式发送可以使用execute()方法,call对象还提供cancel()、isCancel()等方法获取这个Http请求的状态

    // 请求数据,并且处理
    responsecall.enqueue(new Callback<ZhuanLanAuthor>() { 
        @Override 
        public void onResponse(Response<ZhuanLanAuthor> author) {
    
             System.out.println("name: " + author.getName()); 
        } 
        @Override 
        public void onFailure(Throwable t) { 
    
        }
      });
    

    3 Retrofit的原理

    从上面Retrofit的使用来看,Retrofit就是充当了一个适配器(Adapter)的角色:将一个Java接口翻译成一个Http请求,然后用OkHttp去发送这个请求Volley描述一个HTTP请求是需要创建一个Request对象,而执行这个请求呢,就是把这个请求对象放到一个队列中,在网络线程中用HttpUrlConnection去请求
    Retrofit是怎么做的呢?
    就是:
    Java的动态代理**

    动态代理

    ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
    

    我给Retrofit对象传了一个ZhuanLanApi接口的Class对象,怎么又返回一个ZhuanLanApi对象呢?进入create方法一看,没几行代码,但是我觉得这几行代码就是Retrofit的精妙的地方

    /** Create an implementation of the API defined by the {@code service} interface. */
    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); 
             } 
          });
     }
    

    create方法就是返回了一个Proxy.newProxyInstance动态代理对象。

    为什么要使用动态代理

    你看上面代码,获取数据的代码就是这句:

    Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
    

    上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象,它的invoke方法会传入3个参数:

    • Object proxy: 代理对象,不关心这个
    • Method method:调用的方法,就是getAuthor方法
    • Object... args:方法的参数,就是"qinchao"

    而Retrofit关心的就是method和它的参数args,接下去Retrofit就会用Java反射获取到getAuthor方法的注解信息,配合args参数,创建一个ServiceMethod对象

    ServiceMethod就像是一个中央处理器,传入Retrofit对象和Method对象,调用各个接口和解析器,最终生成一个Request,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个Call对象,Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client

    使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送

    4 Retrofit的源码分析

    先来看一下Retrofit源码的组成:

    1. 一个retrofit2.http包,里面全部是定义HTTP请求的Java注解,比如GET、POST、PUT、DELETE、Headers、Path、Query等等。

    2. 余下的retrofit2包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了

    Retrofit接口

    Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:

    Callback<T>
    这个接口就是retrofit请求数据返回的接口,只有两个方法

    • void onResponse(Response<T> response);
    • void onFailure(Throwable t);

    Converter<F, T>
    这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建Retrofit对象时添加你需要使用的Converter实现(看上面创建Retrofit对象的代码)

    Call<T>
    这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>,你可以根据实际情况实现你自己的Call类,这个设计和Volley的HttpStack接口设计的思想非常相似,子类可以实现基于HttpClient或HttpUrlConnetction的HTTP请求工具,这种设计非常的插件化,而且灵活

    CallAdapter<T>
    上面说到过,CallAdapter中属性只有responseType一个,还有一个<R> T adapt(Call<R> call)方法,这个接口的实现类也只有一个,DefaultCallAdapter。这个方法的主要作用就是将Call对象转换成另一个对象,可能是为了支持RxJava才设计这个类的吧

    Retrofit的运行过程

    面讲到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);代码返回了一个动态代理对象,而执行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");代码时返回了一个OkHttpCall对象,拿到这个Call对象才能执行HTTP请求。

    上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象, 创建一个ServiceMethod对象

      ServiceMethod serviceMethod = loadServiceMethod(method);
      OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
      return serviceMethod.callAdapter.adapt(okHttpCall);
    

    创建ServiceMethod

    刚才说到,ServiceMethod就像是一个中央处理器,具体来看一下创建这个ServiceMethod的过程是怎么样的

    第一步,获取到上面说到的3个接口对象:

    callAdapter = createCallAdapter();
    responseType = callAdapter.responseType();
    responseConverter = createResponseConverter();
    

    第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了@Multipart,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨

    for (Annotation annotation : methodAnnotations) { 
        parseMethodAnnotation(annotation);
    }
    if (httpMethod == null) { 
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
    }
    

    第三步,比如上面api中带有一个参数{user},这是一个占位符,而真实的参数值在Java方法中传入,那么Retrofit会使用一个ParameterHandler来进行替换:

    int parameterCount = parameterAnnotationsArray.length;
    parameterHandlers = new ParameterHandler<?>[parameterCount];
    

    最后,ServiceMethod会做其他的检查,比如用了@FormUrlEncoded
    注解,那么方法参数中必须至少有一个@Field或@FieldMap

    执行Http请求

    之前讲到,OkHttpCall是实现了Call接口的,并且是真正调用OkHttp3发送Http请求的类。OkHttp3发送一个Http请求需要一个Request对象,而这个Request对象就是从ServiceMethod的toRequest返回的

    总的来说,OkHttpCall就是调用ServiceMethod获得一个可以执行的Request对象,然后等到Http请求返回后,再将response body传入ServiceMethod中,ServiceMethod就可以调用Converter接口将response body转成一个Java对象

    结合上面说的就可以看出,ServiceMethod中几乎保存了一个api请求所有需要的数据,OkHttpCall需要从ServiceMethod中获得一个Request对象,然后得到response后,还需要传入ServiceMethod用Converter转换成Java对象

    如何在Retrofit中使用RxJava

    由于Retrofit设计的扩展性非常强,你只需要添加一个CallAdapter就可以了

    Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("https://api.github.com")
      .addConverterFactory(ProtoConverterFactory.create())
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
      .build();
    

    上面代码创建了一个Retrofit对象,支持Proto和Gson两种数据格式,并且还支持RxJava。

    相关文章

      网友评论

          本文标题:Retrofit2 源码解析

          本文链接:https://www.haomeiwen.com/subject/jmdqettx.html