zoukankan      html  css  js  c++  java
  • Retrofit 2.0 使用和原理

    使用教程:

    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1016/3588.html

    retrofit2 与okhttp关系

    http://blog.csdn.net/lmj623565791/article/details/51304204

    DEMO,用api调用天气数据

    http://blog.csdn.net/a553181867/article/details/52093695

    Retrofit2 源码解读

    构造方法

    Retrofit构造方法采用构建者模式构造,源码如下。

    private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();//key为接口中定义方法,value为转换过后的方法
    
      public static final class Builder {
        private Platform platform;//平台:安卓、java等
        private okhttp3.Call.Factory callFactory; //okhttp的Call工厂类,自定义newCall将Request转为Call
        private HttpUrl baseUrl;//okhttp中的类,保存解析过的url
        private List<Converter.Factory> converterFactories = new ArrayList<>();//类型转换工厂列表。
        private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();//CallAdapter工厂列表。
        private Executor callbackExecutor;//回调线程池
        private boolean validateEagerly;//急需验证?作用在于直接将所有方法加入前面的map缓存中。
    
        Builder(Platform platform) {
          this.platform = platform;
          converterFactories.add(new BuiltInConverters());//添加默认的转换器
        }
    
        public Builder() {
          this(Platform.get());//通过Platform.get()获取关于当前平台的实现
        }
    
        public Retrofit build() {
          if (baseUrl == null) {
            throw new IllegalStateException("Base URL required.");
          }
    
          okhttp3.Call.Factory callFactory = this.callFactory;
          if (callFactory == null) {
            callFactory = new OkHttpClient();//默认使用OkHttpClient
          }
    
          Executor callbackExecutor = this.callbackExecutor;
          if (callbackExecutor == null) {//默认使用平台默认回调线程池
            callbackExecutor = platform.defaultCallbackExecutor();
          }
    
          //将平台默认CallAdapter.Factory加入列表中
          List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
          adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    
         //将默认Converter.Factory加入列表中
          List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
    
          return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
              callbackExecutor, validateEagerly);
        }

    可以看出,首选通过Platform.get()来获取平台实现然后添加了默认转换工厂。由于我们是Android平台,毋容置疑,接下来就去看看Andoird类的源码。 

    static class Android extends Platform {
        //回调线程池
        @Override public Executor defaultCallbackExecutor() {
          return new MainThreadExecutor();//主线程
        }
    
        @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
          //ExecutorCallAdapterFactory继承CallAdapter.Factory,内部代理了原来的Call<T>,用于将Callback回调到指定线程中。
          return new ExecutorCallAdapterFactory(callbackExecutor);
        }
    
        static class MainThreadExecutor implements Executor {
          private final Handler handler = new Handler(Looper.getMainLooper());
    
          @Override public void execute(Runnable r) {
            handler.post(r);//采用Hanlder#post回调到主线程
          }
        }
      }

    Andoird类的源码主要实现了两个方法,一个是实现了默认的回调线程池,用于在主线程中执行任务,另一个是实现了CallAdapter工厂,通过代理的方式,将执行结果回调到callbackExecutor中去执行。所以,我们只需将callbackExecutor赋值为MainThreadExecutor即可实现主线程间的回调。

    BuiltInConverters继承于Converter.Factory,Converter.Factory中有三个方法:

    • public Converter<ResponseBody, ?> responseBodyConverter(xx)用于将ResponseBody转换为指定类型,通常用于对响应结果的类型转换。
    • public Converter<?, RequestBody> requestBodyConverter(xx)用于将指定类型转为RequestBody。一般用于将@Body,@Part,@PartMap转为RequestBody
    • public Converter<?, String> stringConverter用于将指定类型转为String,用于将@Field,@FieldMap,@Path,@Query,@Header等注解的参数类型转为String。

    Retrofit提供的用于自定义的方法如下: 

    • client(OkHttpClient) 用于自定义客户端
    • callFactory(okhttp3.Call.Factory factory)用于自定义Call工厂,重写newCall将Request转为Call。OkHttpClient就是实现了这个接口。
    • addConverterFactory添加类型转换工厂(Gson转换等)
    • addCallAdapterFactory添加CallAdapter代理工厂,用来代理原始的Call(RxJavaCallAdapter等)。
    • callbackExecutor自定义回调线程池,默认为主线程
    • validateEagerly是否继续验证,是就提前将所有方法转为ServiceMethod放入缓存中,而不是调用一个缓存一个
    • baseUrl用于定义基本链接,必须以”/”结尾

    假如基本地址为http://example.com/api/,关于baseUrl与注解中路径的拼接问题如下:

    注解中的路径最终Url (baseUrl为http://example.com/api/
    foo/bar/ http://example.com/api/foo/bar/
    /foo/bar/ http://example.com/foo/bar/
    https://github.com/square/retrofit/ https://github.com/square/retrofit/
    //github.com/square/retrofit/ http://github.com/square/retrofit/

    接下来看使用:

    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(httpurl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
    IWeather iWeather = retrofit.create(IWeather.class);
    Call<City> call = iWeather.getCity(API_KEY, "shenzhen");
     call.enqueue(new Callback<City>() {
    @Override
     public void onResponse(Call<City> call, retrofit2.Response<City> response)               {}
    @Override
    public void onFailure(Call<City> call, Throwable t) 
    {}
    });

    以这段代码为例

    我给Retrofit对象传了一个IWeather接口的Class对象,怎么又返回一个IWeather对象呢?进入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动态代理对象。那么问题来了...

    动态代理是个什么东西?

    看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在Spring框架里大量的用到,但是它有什么用呢?

    Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码

    比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。

    为什么要使用动态代理

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

    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发送

    3 Retrofit的源码分析

    想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:

    1. 一个retrofit2.http包,里面全部是定义HTTP请求的Java注解,比如GETPOSTPUTDELETEHeadersPathQuery等等
    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接口设计的思想非常相似,子类可以实现基于HttpClientHttpUrlConnetction的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);

     loadServiceMethod的方法很简陋,直接传入了Method然后build(),那么这个build()又做了什么?

    ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result;
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);//首先从map中取看看是否已经缓存过
          if (result == null) {//否则构造ServiceMethod。
            result = new ServiceMethod.Builder(this, method).build();
            serviceMethodCache.put(method, result);//缓存map中
          }
        }
        return result;
      }
    
     public Builder(Retrofit retrofit, Method method) {
          this.retrofit = retrofit;
          this.method = method;
          this.methodAnnotations = method.getAnnotations();//赋值方法注解数组
          this.parameterTypes = method.getGenericParameterTypes();//赋值参数类型数组
          this.parameterAnnotationsArray = method.getParameterAnnotations();//赋值参数注解数组
    }
    
      public ServiceMethod build() {
          callAdapter = createCallAdapter();//创建CallAdapter,用来代理Call
          responseType = callAdapter.responseType();//获取返回类型
          //...
          responseConverter = createResponseConverter();//创建ResponseConverter,用来转换ResponseBody为指定类型
    
          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);//遍历解析方法注解
          }
          //...
          int parameterCount = parameterAnnotationsArray.length;//parameterAnnotationsArray为参数注解数组
          parameterHandlers = new ParameterHandler<?>[parameterCount];//初始化ParameterHandler,用来处理参数相关
          for (int p = 0; p < parameterCount; p++) {//遍历参数注解数组
            Type parameterType = parameterTypes[p];//获取参数类型
            Annotation[] parameterAnnotations = parameterAnnotationsArray[p];//获取参数注解数组
             //...
            //通过注解和参数类型,解析并赋值到parameterHandlers中
            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }
    
         //...
    
          return new ServiceMethod<>(this);
        }

    创建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.).");
    }
    

    parseMethodAnnotation用于遍历解析方法上的注解,比如请求方法,请求头之类的。

    private void parseMethodAnnotation(Annotation annotation) {
          //请求方法注解
          if (annotation instanceof DELETE) {
            parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
          } else if (annotation instanceof GET) {
            parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
          } else if (annotation instanceof HEAD) {
            parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
            if (!Void.class.equals(responseType)) {
              throw methodError("HEAD method must use Void as response type.");
            }
          } else if (annotation instanceof PATCH) {
            parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
          } else if (annotation instanceof POST) {
            parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
          } else if (annotation instanceof PUT) {
            parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
          } else if (annotation instanceof OPTIONS) {
            parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
          } else if (annotation instanceof HTTP) {
            //自定义HTTP请求注解
            HTTP http = (HTTP) annotation;
            parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
          } else if (annotation instanceof retrofit2.http.Headers) {
            //请求头注解
            String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
            if (headersToParse.length == 0) {
              throw methodError("@Headers annotation is empty.");
            }
            headers = parseHeaders(headersToParse);//解析Header
          } else if (annotation instanceof Multipart) {
            //Multipart
            if (isFormEncoded) {
              throw methodError("Only one encoding annotation is allowed.");
            }
            isMultipart = true;
          } else if (annotation instanceof FormUrlEncoded) {
            //FormUrlEncoded
            if (isMultipart) {
              throw methodError("Only one encoding annotation is allowed.");
            }
            isFormEncoded = true;
          }
        }

    从上面源码可以看出,使用parseHttpMethodAndPath这个方法用于解析请求方法注解和路径参数保存到Set中

    private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
          if (this.httpMethod != null) {
            throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                this.httpMethod, httpMethod);
          }
          this.httpMethod = httpMethod;
          this.hasBody = hasBody;
    
          if (value.isEmpty()) {
            return;
          }
    
    
          int question = value.indexOf('?');//查询参数开始的符号
          if (question != -1 && question < value.length() - 1) {
           //如果在查询参数中使用了{},则抛出异常。
            String queryParams = value.substring(question + 1);
            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
            if (queryParamMatcher.find()) {
              throw methodError("URL query string "%s" must not have replace block. "
                  + "For dynamic query parameters use @Query.", queryParams);
            }
          }
    
          //赋值相对链接
          this.relativeUrl = value;
          //解析{}路径参数保存到Set中
          this.relativeUrlParamNames = parsePathParameters(value);
        }

    从源码可以发现,不允许在查询参数中使用{}进行占位,否则就会抛出异常,然后将请求方法中的注解值赋值给relativeUrl,通过parsePathParameters将{}路径参数保存到Set中。 

    第三步,比如上面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对象就是从ServiceMethodtoRequest返回的

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

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

    你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?

    我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;而且Retrofit会对解析过的请求进行缓存,就在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();这个对象中

    调用服务接口

    以上介绍了retrofit.create(XX.class);用于创建服务方法接口的过程。现在该进行相关调用了。我们知道,创建服务方法会返回一个Call<XX>对象,通过Call<XX>可以进行相关异步同步调用。 

    @Override public void enqueue(final Callback<T> callback) {
        if (callback == null) throw new NullPointerException("callback == null");
    
        okhttp3.Call call;//okhttp中的call对象
        Throwable failure;
    
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already executed.");
          executed = true;
    
          call = rawCall;
          failure = creationFailure;
          if (call == null && failure == null) {
            try {
              call = rawCall = createRawCall();//创建原始Call,即okhttp中的Call对象
            } catch (Throwable t) {
              failure = creationFailure = t;
            }
          }
        }
    
        if (failure != null) {
          callback.onFailure(this, failure);
          return;
        }
    
        if (canceled) {
          call.cancel();
        }
    
        //通过enqueue进行异步调用
        call.enqueue(new okhttp3.Callback() {
          @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
              throws IOException {
            Response<T> response;
            try {
              response = parseResponse(rawResponse);//解析响应内容
            } catch (Throwable e) {
              callFailure(e);
              return;
            }
            callSuccess(response);
          }
      }

    从源码不难看出,首先通过createRawCall()来创建OkHttp中的Call对象,然后通过Call进行异步/同步调用,获取结果后通过parseResponse解析OkHttp中的Response,然后进行相应回调。 
    createRawCall()用于创建OkHttp中的Call对象,源码如下:

    private okhttp3.Call createRawCall() throws IOException {
        Request request = serviceMethod.toRequest(args);//通过 serviceMethod.toRequest进行转换成Request
        okhttp3.Call call = serviceMethod.callFactory.newCall(request);//调用newCall返回Call对象,默认callFactory为OkHttpClient,OkHttpClient也实现了okhttp3.Call.Factory接口。
        if (call == null) {
          throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
      }

    4 最后

    Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求

    Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)

    Retrofit中接口设计的恰到好处,在你创建Retrofit对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能

  • 相关阅读:
    C# 遍历TreeView所有节点
    Oracle创建删除用户、角色、表空间、导入导出、...命令总结
    order by,group by和having的使用
    C/C++ 数据结构与算法笔记
    使用PS随意更换相片底色
    微软SQL Server2012增加对Hadoop的支持
    MOSS2010事件接收器开发以及自定义错误提示页
    将 SharePoint 开发与其他形式的开发进行比较
    ItemAdding事件接收器中无法取到【创建者】的字段的值
    ItemAdding实现数据验证中文字段,properties.AfterProperties值为null的问题
  • 原文地址:https://www.cnblogs.com/qlky/p/7246399.html
Copyright © 2011-2022 走看看