美文网首页
我了解的Retrofit2

我了解的Retrofit2

作者: 五香鱼cc | 来源:发表于2017-05-04 20:46 被阅读274次

    一.概述

    我想作为一个Android开发如果没有听过Retrofit网络请求框架,那么他是与时代脱节的,你随便在gayhub上找一个开源的Android项目都能发现Retrofit的身影。在介绍Retrofit之前,大家还是需要知道okhttp的基本使用方式的。
    okhttp使用很简单,主要分为同步请求和异步请求:

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    
    • 同步请求,即等待请求结果返回,然后对结果进行解析,主要流程:
    Response response = client.newCall(request).execute();
    

    然后对返回的结果Request进行解析。

    • 异步请求,即回调式请求,结果返回后进行回调,不会阻塞当前线程的执行。主要流程:
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onResponse(Response response) throws IOException {
            //get result from response.
        }
        @Override
        public void onFailure(Request arg0, IOException arg1) {
        
        } 
    });
    

    可以看到,两种操作方式,都是对返回的Response进行包括io操作、结果转换等解析操作。你可以把okhttp就当成类似HttpURLConnection一样的Api,他们的功能是一样的都是负责网络请求。如果想对Okhttp进一步了解的可以参考:Android OkHttp完全解析 是时候来了解OkHttp了

    二.Retrofit的使用

    既然都会使用okhttp了,那么使用Retrofit就很容易了。网上教程一大堆,这边主要罗列基本的3个操作:

    • 1.创建接口类,其中每个方法都是一个对应的网络请求。
    public interface API {
        @POST("/load")
        Call<Rep> load();
        
        @Post("/test")
        Call<Rep> down();
    }
    
    • 2.创建Retrofit对象,方式和Okhttp一样是通过Builder模式进行配置生成对象,其中baseUrl表示表示完整的url前半段,和API中的注解合并成最终的url。
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://com.seu.test/")
        .build();
    
    • 3.创建异步请求
    Call<Rep> call = retrofit.create(API.class).load();
    call.enquque(new Callback<Rep> {
        @Override
        public void onResponse(Call<Rep> call, Response<Rep> response) {
            //get result from response
        }
     
        @Override
        public void onFailure(Call<Rep> call, Throwable t) {
     
        }
    });
    

    是不是看去和Okhttp的异步请求一模一样。你猜对了,Retrofit就是对Okhttp的网络请求的封装。如果你现在还不了解Retrofit的用途,那我还能怎么办呢。Retrofit2.0使用详解,我也只能帮到这了。

    三.Retrofit源码分析

    注解Annotation

    通过查看源码可以知道在retrofit2.http目录下面都是Java注解类,其中包括熟悉的GETPOST等。本文结合GET进行入门式介绍。
    Java中包括4种元注解(用于自定义注解的注解):

    • Documented,如果定义了该注解,则通过javadoc后,该注解的信息可以在docs中显示(对于开发来说,没啥用,不行就在自定义注解中直接加上就行)。
    • Inherited 定义该注解是否可以在子类中被继承。一般该注解的使用也不会很多,至今没用过继承的注解。
    • Retention 定义了该注解的存活周期。这个是我见过用处比较大的注解了,但是在我解释了他的用处以后,你也会觉得so easy。Retention包括三部分:
    Rentention 的三个enum属性
    SOURCE: 说明该自定义注解,只存在与XX.java中,在编译后的XX.class都是找不到的。比如@Override,可以通过jd-gui来查看XX.class文件,确实找不到该注解,该注解是在虚拟机层面进行解析的,实际自定义注解中大概也不会用到该类别。
    CLASS : 存在于XX.java和XX.class文件中,但是在运行时Java虚拟机会忽略该注释,只有在编译阶段会读取到该注解,在运行时通过反射是获取不到该注解的,可以查看Butter Knife,他就是编译时注解。
    RUNTIME: 和class属性一样,都存在于源文件以及编译生成的class文件中,不同的是在运行时可以得到该注解,即可以运行时可以通过反射得到。
    • Target表示该注解可以用在哪个地方,比如:用于属性字段、用于方法声明、用于包声明、用于接口声明等等,详细的可以参考ElementType。

    在了解了Annotation的元注解以后,我们来解释GET

    //还不是小儿科,直接忽略
    @Documented
    //说明该注解是使用在方法上的,且只能用在方法上
    @Target(METHOD)
    //说明该注解是运行时注解,可以通过反射得到
    @Retention(RUNTIME)
    public @interface GET {
      //需要有一个属性
      String value() default "";
    }
    
    

    怎么样,一个自定义的注解就可以完成了,妈妈再也不用担心我看不懂注解咯。其实所有的自定义注解都是照猫画虎,没啥困难的。还想深入全名学习注解的可以参考:Java注解(Annotation),保证药到病除。到现在为止,retrofit2.http下面所有的类基本都可以知道它们的真实含义了吧。

    动态代理

    在将ServiceMethod之前还是需要稍微介绍下Java的动态代理技术:动态代理是jdk封装好的比较好用的类代理生成方式,Proxy.newProxyInstance有三个参数:

    • ClassLoader,这个比较通用,直接用类本身的Classloader就可以。
    • Class<?>[] interfaces,表示该生成代理类要实现的接口功能。
    • InvocationHandler h,接口功能的真正实现是h。

    Proxy.newProxyInstance是通过反射的方式生成代理类,通过源码分析可以看到,Class对象是通过ProxyGenerator生成的字节码,不信的话你可以通过ProxyGenerator生成的byte[]保存到xx.class文件中,看看是不是把所有的功能都让h来完成的,可以参考:JDK动态代理的实现及原理,保证让你对动态代理有比较深刻的理解。
    看到了没有,说话算话,真的很简单的介绍了动态代理。

    开刀Retrofit.java

    Retrofit是通过Builder模式生成的对象,所以直接对Retrofit.Builder类进行解析,这里只罗列来了属性和build(),属性设置方法略(没啥特别的,都是一堆类似set方法):

    public static final class Builder {
        //Retrofit运行的平台:具体实现类比较简单,其中包括了Android平台、IOS平台、还有java8平台。这里你就直接理解为Android平台吧。
        private Platform platform;
        //该接口直接实现类为OkHttpClient,相信我。
        private okhttp3.Call.Factory callFactory;
        private HttpUrl baseUrl;
        private List<Converter.Factory> converterFactories = new ArrayList<>();
        private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
        //网络请求返回后是不在主线程的,那么是不是需要有一个主线程的executor来执行ui操作,猜对了就是他。
        private Executor callbackExecutor;
        //我暂时理解为懒加载的意思,如果为true则把所有的methond都进行反射,如果为false则用到哪个method再去反射。
        private boolean validateEagerly;
    }
    public Retrofit build() {
        //以后这种判断我就直接忽略了,谁看不懂?
        if (baseUrl == null) {
            throw new IllegalStateException("Base URL required.");
        }
        okhttp3.Call.Factory callFactory = this.callFactory;
        if (callFactory == null) {
            callFactory = new OkHttpClient();
        }
        
        Executor callbackExecutor = this.callbackExecutor;
        if (callbackExecutor == null) {
            callbackExecutor = platform.defaultCallbackExecutor();
        }
    
        // 这里会默认添加一个适配器,这才能让API中的load方法返回Call<Rep>
        List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
        //即加入ExecutorCallAdapterFactory
        adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    
        // 没有给默认解析器,所以要自己定义一个解析器
        List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
    
        return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
              callbackExecutor, validateEagerly);
        }
    

    adapterFactories:这个东西举个栗子吧,由于adapterFactories中会默认添加ExecutorCallAdapterFactory,所以我们看到API中的方法返回Call是默认可以解析的。如果接触过RxJava的同学应该知道,为了让API中的方法能够返回Observable,则需要添加一个自定义Retrofit Adapters,而RxJavaCallAdapterFactory就是能够让API返回Observable的具体实现。所以你想自定义API中函数的返回类型,就需要添加自定义Adapter,而常见的Adapter在GayHub上都给有给出。
    converterFactories:还是很好理解的,一般的网络请求后返回的body都是String形式的,那么怎么把String形式转换成具体的entity对象呢?就是他来实现的。你可以用Gson、jackson来解析返回的数据,只要你配置了真确的converter就可以啦。retrofit-converters,而默认中converterFactories为空的没有具体的实现,所以在初始化Retrofit的时候需要添加例如:addConverterFactory(GsonConverterFactory.create())的converter。
    这种可插拔式的框架,可以灵活的适配项目之前已经使用的一些框架。如果retrofit只支持fastjson解析,而之前项目都用Gson解析的,那你会不会把之前的Gson都用fastjson替换(不然有洁癖的我是无法容忍的)?这就是retrofit可插拔的优点。

    现在创建API的实例,通过Retrofit.create():

    public <T> T create(final Class<T> service) {
        //确保API是一个接口,且该接口没有继承其他接口
        Utils.validateServiceInterface(service);
        //看看是不是懒加载,如果true则会把API里面的所有method都进行一次性缓存。
        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);
                }
                //android平台返回的是false,所以不管它
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                //API中每个方法都会生成对应的一个ServiceMethod,解析该方法对应的Annotation信息。
                ServiceMethod serviceMethod = loadServiceMethod(method);
                //他就是okhttp3.Call的一个包装类,真正的网络请求在这里面进行,且是通过okhttp3.Call进行的。
                OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                //返回该API中method的返回类型,例如Call<Rep>
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    

    adapt()默认返回的是ExecutorCallbackCall,他就是Call子类,所以你也可以直接把它理解为API函数的返回类型,例如load的返回值Call<Rep>,没有任何问题。
    ServiceMethod咋一眼看去不知道什么鬼东西,其实他的存在就是:把API中的每个method方法解析成一个ServiceMethod对象,解析的内容就是该method对应的注解信息,把这些信息存储在对应的ServiceMethod对象中进行缓存等待网络请求使用。

    ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result;
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {
            result = new ServiceMethod.Builder(this, method).build();
            //缓存,不多说,总不可能在多次调用API方法的时候,每次都使用反射进行解析吧。
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
    

    下面就要开始解析ServiceMethod.Builder()方法,按照惯例只写属性和builder方法。这样,一个method中所有信息都被保存在ServiceMethod当中,后续的网络请求只需要获取ServiceMethod当中的数据即可。

    static final class Builder<T> {
        //需要介绍吗,就是上面loadServiceMethod传入的this
        final Retrofit retrofit;
        //动过动态代理得到的method,后续要详细的解析该method
        final Method method;
        //作用于该method的所有注解,比如API方法中的GET
        final Annotation[] methodAnnotations;
        //method方法中参数对应的注解,一个参数可以对应多个注解
        final Annotation[][] parameterAnnotationsArray;
        //method中参数对应的真实类型列表
        final Type[] parameterTypes;
        //method返回类型,就是你看到的Call<Rep>。
        Type responseType;
        //这一堆boolean自行脑补吧
        boolean gotField;
        boolean gotPart;
        boolean gotBody;
        boolean gotPath;
        boolean gotQuery;
        boolean gotUrl;
        String httpMethod;
        boolean hasBody;
        boolean isFormEncoded;
        boolean isMultipart;
        String relativeUrl;
        Headers headers;
        MediaType contentType;
        Set<String> relativeUrlParamNames;
        ParameterHandler<?>[] parameterHandlers;
        Converter<ResponseBody, T> responseConverter;
        CallAdapter<?> callAdapter;
        
        public ServiceMethod build() {
          //获取CallAdapter,可以通过该函数知道,最终会调用retrofit.callAdapter(returnType, annotations);
          callAdapter = createCallAdapter();
          responseType = callAdapter.responseType();
          //最终调用retrofit.responseBodyConverter(responseType, annotations);
          responseConverter = createResponseConverter();
          //猜都不用猜,肯定是解析retrofit2.http下面的注解,解析完之后上面的一堆boolean值差不多都赋值好咯。
          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }
    
          int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0; p < parameterCount; p++) {
            Type parameterType = parameterTypes[p];
            if (Utils.hasUnresolvableType(parameterType)) {
              throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                  parameterType);
            }
            //解析对应参数和该参数的注解列表。
            Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
            
            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }
          return new ServiceMethod<>(this);
        }
    }
    

    通过对上述对Method的解析,得到了一一对应的ServiceMethod对象,最后通过serviceMethod.callAdapter.adapt(okHttpCall)返回一个API中函数的返回类型。由于系统默认了使用ExecutorCallAdapterFactory,可以对比DefaultCallAdapterFactory,他们的作用是一样的,只是网络请求返回的一个回调在主线程,一个回调在子线程:

    final class DefaultCallAdapterFactory extends CallAdapter.Factory {
      static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
    
      @Override
      public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if (getRawType(returnType) != Call.class) {
          return null;
        }
        //这里的returnType是API中函数的返回类型,比如Call<Rep>
        //responseType则是Rep,就是returnType对应的泛型类型。
        final Type responseType = Utils.getCallResponseType(returnType);
        return new CallAdapter<Call<?>>() {
          @Override public Type responseType() {
            return responseType;
          }
    
          @Override public <R> Call<R> adapt(Call<R> call) {
            //看看,直接把call返回了,这就是为什么可以处理API中函数是Call返回类型的原因。
            return call;
          }
        };
      }
    }
    

    好了,到这里Call<Rep> call = retrofit.create(API.class).load()我们就得到了Call,接下去就是enqueue了,这个过程有没有很像Okhttp的enqueue,知识点有没有!前面我们已经知道了这里对应的Call就是OkhttpCall(不清楚的直接在动态代理里面找),所以要enqueue就去找OkhttpCall:

    @Override public void enqueue(final Callback<T> callback) {
        //这是啥东西,哈哈,就是要去真真网络请求的Call,不是retrofit的call。
        //如果还是混淆了这两个call,那么只能再多看看Okhttp是怎么网络请求的。
        okhttp3.Call call;
        Throwable failure;
    
        //禁止同一个call入列两次
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already executed.");
          executed = true;
    
          call = rawCall;
          failure = creationFailure;
          if (call == null && failure == null) {
            try {
              //从该函数的命名就可以看到他的意思,创建真实的Call,即Okhttp的Call
              call = rawCall = createRawCall();
            } catch (Throwable t) {
              failure = creationFailure = t;
            }
          }
        }
    
        if (failure != null) {
          callback.onFailure(this, failure);
          return;
        }
    
        if (canceled) {
          call.cancel();
        }
        //真真的网络请求来了,这一块网络请求就是Okhttp真真的网络请求,看到这你要还不懂的话,怪我咯。
        call.enqueue(new okhttp3.Callback() {
          @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
              throws IOException {
            Response<T> response;
            try {
            //通过真实返回的rawResponse,解析得到retrofit的response
              response = parseResponse(rawResponse);
            } catch (Throwable e) {
              callFailure(e);
              return;
            }
            callSuccess(response);
          }
    
          @Override public void onFailure(okhttp3.Call call, IOException e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
    
          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
          //把成功的结果传递给回调,其中response中包好了解析好的对象。    
          private void callSuccess(Response<T> response) {
            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
        });
      }
      //把okhttp的返回rawResponse转换成retrofit的Response,并把解析内容放入到Response当中。
      Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();
    
        //该rawResponse只解析返回内容的头部,可以了解NoContentResponseBody读取body抛异常了
        rawResponse = rawResponse.newBuilder()
            .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
            .build();
    
        int code = rawResponse.code();
        if (code < 200 || code >= 300) {
          try {
            // Buffer the entire body to avoid future I/O.
            ResponseBody bufferedBody = Utils.buffer(rawBody);
            return Response.error(bufferedBody, rawResponse);
          } finally {
            rawBody.close();
          }
        }
    
        if (code == 204 || code == 205) {
          return Response.success(null, rawResponse);
        }
        //异常捕获response?不了解具体的用处。。。
        ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
        try {
          //这个地方厉害了,就是通过convert来转换成具体的对象,比如用GsonConverterFactory.create()来把String转换成具体对象。自行看代码,其实就一句话。
          T body = serviceMethod.toResponse(catchingBody);
          return Response.success(body, rawResponse);
        } catch (RuntimeException e) {
          catchingBody.throwIfCaught();
          throw e;
        }
      }
    

    到这里,我们就把解析好的T body返回给回调接口Callback里面去了,现在我们就可以在onResponse里面通过Response.body()获取到解析以后的真正对象咯。
    好了基本上能说的都说了,应该能够差不多了解了Retrofit的使用流程了。

    Utils

    Retrofit中有一个非常重要的工具类Utils,里面包含了所有的关于Type的反射方法,比如通过Call<Rep>得到Rep的Type等等。
    Type是和泛型相关的接口,具体分为4种类型:

    • ParameterizedType:具体的泛型类型,比如ArrayList<String>中,具体泛型就是String。
    • TypeVariable:泛型变量,在泛型类里面使用该泛型变量,这时候该变量就是TypeVariable。
    • GenericArrayType:泛型数组,String[],懂?
    • WildcardType:通配符泛型,比如ArrayList<? extends Number>。

    不想讲了,自己参考:Java中的Type详解

    四.总结

    本文通过Okhttp的网络请求为主线,然后把Retrofit是如何包装Okhttp的过程进行了分析,到这里你应该有能力去修改Retrofit去适应你自己app里面的业务功能了吧。
    最后再把参考的几篇文章进行罗列下,欢迎提出问题,我说鸡蛋你说要:

    相关文章

      网友评论

          本文标题:我了解的Retrofit2

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