Retrofit2深入浅出

作者: My_Hubery | 来源:发表于2018-05-04 15:42 被阅读4248次

    Retrofit2简单的说就是一个网络请求的适配器,它将一个基本的Java接口通过动态代理的方式翻译成一个HTTP请求,并通过OkHttp去发送请求。此外它还具有强大的可扩展性,支持各种格式转换以及RxJava。本文基于Retrofit2解析。

    Retrofit2基本使用

    先定义一个PersonalProtocol的java接口

    public interface PersonalProtocol {
        /**
         * 用户信息
         * @param page
         * @return
         */
        @FormUrlEncoded
        @POST("user/personal_list_info")
        Call<Response<PersonalInfo>> getPersonalListInfo(@Field("cur_page") int page);
    }
    

    @FormUrlEncoded注解表示from表单,另外还有@Multipart等注解,如果接口不需要传递参数,那么@FormUrlEncoded以及@Multipart需要去掉,具体原因后面章节会讲到。
    @POST表示post请求,此外还可以使用@GET请求

    现在看看Retrofit的使用

    private void requestRetrofit(){
            Retrofit retrofit = new Retrofit.Builder().baseUrl("www.xxxx.com/").build();
            PersonalProtocol personalProtocol = retrofit.create(PersonalProtocol.class);
            Call<Response<PersonalInfo>> call = personalProtocol.getPersonalListInfo(12);
            call.enqueue(new Callback<Response<PersonalInfo>>() {
                @Override
                public void onResponse(Call<Response<PersonalInfo>> call, Response<Response<PersonalInfo>> response) {
                    //数据请求成功
                }
    
                @Override
                public void onFailure(Call<Response<PersonalInfo>> call, Throwable t) {
                    //数据请求失败
                }
            });
        }
    

    首先将域名传入构造一个Retrofit,然后通过retrofit中的create方法传入一个Java接口并得到一个PersonalProtocol(当然PersonalProtocol这个对象是经过处理了的,这个后面会讲到)调用getPersonalListInfo(12)然后返回一个Call,最后这个Call调用了enqueue方法去异步请求http,这就是一个基本的Retrofit的网络请求。Retrofit2Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client

    其实Retrofit还有很多方法,我们现在选两个一起来看看:

     OkHttpClient okHttpClient = new OkHttpClient();
            Retrofit retrofit = new Retrofit.Builder().baseUrl("www.xxxx.com")
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create(buildGson()))
                    .build();
    

    addConverterFactory方法中使用了gson去解析json,如果接口返回的jsonint为异常数据,我们做了一个简单的转换,给它一个默认值为0:

    public static Gson buildGson() {
            Gson gson = new GsonBuilder()
                    .serializeNulls()
                    .registerTypeAdapter(int.class, new GsonIntegerDefaultAdapter())
                    .create();
            return gson;
        }
    
    public class GsonIntegerDefaultAdapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
        @Override
        public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            try {
                if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为int类型,如果后台返回""或者null,则返回0
                    return 0;
                }
            } catch (Exception ignore) {
            }
            try {
                return json.getAsInt();
            } catch (NumberFormatException e) {
                throw new JsonSyntaxException(e);
            }
        }
    
        @Override
        public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src);
        }
    }
    

    此外,我们再来看看RxJava的使用:

     OkHttpClient okHttpClient = new OkHttpClient();
            Retrofit retrofit = new Retrofit.Builder().baseUrl("www.xxxx.com")
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create(buildGson()))
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
    

    addCallAdapterFactory方法中使用了RxJavaCallAdapterFactory,网络请求也需要修改:

    PersonalProtocol personalProtocol = retrofit.create(PersonalProtocol.class);
    rx.Observable<PersonalInfo> observable  = personalProtocol.getPersonalListInfo(12);
          observable.subscribeOn(Schedulers.io()) 
                    .observeOn(AndroidSchedulers.mainThread())//最后在主线程中执行
                    .subscribe(new Subscriber<PersonalInfo>() {
                        @Override
                        public void onCompleted() {
    
                        }
    
                        @Override
                        public void onError(Throwable e) {
                            //请求失败
                        }
    
                        @Override
                        public void onNext(PersonalInfo personalInfo) {
                            //请求成功
                        }
                    });
    

    同时PersonalProtocol接口也需要改变:

    public interface PersonalProtocol {
        /**
         * 用户信息
         * @param page
         * @return
         */
        @FormUrlEncoded
        @POST("user/personal_list_info")
        Observable<PersonalInfo> getPersonalListInfo(@Field("cur_page") int page);
    //    Call<Response<PersonalInfo>> getPersonalListInfo(@Field("cur_page") int page);
    }
    

    基本的用法大概了解了一下,现在来看看具体实现

    Retrofit2实现原理

    Retrofit2build()方法

    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();
          }
    
          // Make a defensive copy of the adapters and add the default Call adapter.
          List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
          adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    
          // Make a defensive copy of the converters.
          List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
    
          return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
              callbackExecutor, validateEagerly);
        }
    

    文章开头的时候说过,Retrofit会和OkHttp一起使用,而我们创建的时候没有传递OkHttp进去,build()这个方法里面会创建一个OkHttpClient(),当然你可以在外面传递进入,比如像下面代码一样:

    OkHttpClient okHttpClient = new OkHttpClient();
    Retrofit retrofit = new Retrofit.Builder().baseUrl("www.xxxx.com").client(okHttpClient).build();
    

    接下来我们来看看create方法的实现:

      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()方法会返回一个范型T,我们这里是PersonalProtocol对象,当PersonalProtocol调用自身方法的时候,因为它运用了动态代理,所以会调用invoke方法。invoke方法内先将method转换为ServiceMethod对象,再将serviceMethodargs对象传入OkHttpCall构造方法中,返回一个OkHttpCall,最后将OkHttpCall传入adapt方法,最后返回一个Call对象。

    private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
     ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result;
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {
            result = new ServiceMethod.Builder(this, method).build();
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
    

    loadServiceMethod方法通过method去serviceMethodCache集合对象中获取缓存。那ServiceMethod究竟是一个什么东东?先看看ServiceMethod中方法的实现。

    ServiceMethod.Builder

      ServiceMethod(Builder<T> builder) {
        this.callFactory = builder.retrofit.callFactory();
        this.callAdapter = builder.callAdapter;
        this.baseUrl = builder.retrofit.baseUrl();
        this.responseConverter = builder.responseConverter;
        this.httpMethod = builder.httpMethod;
        this.relativeUrl = builder.relativeUrl;
        this.headers = builder.headers;
        this.contentType = builder.contentType;
        this.hasBody = builder.hasBody;
        this.isFormEncoded = builder.isFormEncoded;
        this.isMultipart = builder.isMultipart;
        this.parameterHandlers = builder.parameterHandlers;
      }
    

    在ServiceMethod的构造参数里面发现,包含了一些请求的信息,如baseUrl,httpMethod,hasBody ,isFormEncoded 等信息。

     public ServiceMethod build() {
          callAdapter = createCallAdapter();
          responseType = callAdapter.responseType();
          if (responseType == Response.class || responseType == okhttp3.Response.class) {
            throw methodError("'"
                + Utils.getRawType(responseType).getName()
                + "' is not a valid response body type. Did you mean ResponseBody?");
          }
          responseConverter = createResponseConverter();
    
          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }
    
          if (httpMethod == null) {
            throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
          }
    
          if (!hasBody) {
            if (isMultipart) {
              throw methodError(
                  "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
            }
            if (isFormEncoded) {
              throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
                  + "request body (e.g., @POST).");
            }
          }
    
          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];
            if (parameterAnnotations == null) {
              throw parameterError(p, "No Retrofit annotation found.");
            }
    
            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }
    
         //省略部分代码。。。
    
          return new ServiceMethod<>(this);
        }
    

    build()方法中,会创建callAdapter,也会循环调用parseMethodAnnotation(annotation),此外如果hasBody==false并且isMultipart或者isFormEncodedtrue,那么将会抛出methodError的错误,这也验证了之前的结论:如果定义的java接口参数为空,那么@FormUrlEncoded以及@Multipart需要去掉。最后build()方法会返回一个ServiceMethod

    先看看createCallAdapter

     private CallAdapter<?> createCallAdapter() {
          Type returnType = method.getGenericReturnType();
          if (Utils.hasUnresolvableType(returnType)) {
            throw methodError(
                "Method return type must not include a type variable or wildcard: %s", returnType);
          }
          if (returnType == void.class) {
            throw methodError("Service methods cannot return void.");
          }
          Annotation[] annotations = method.getAnnotations();
          try {
            return retrofit.callAdapter(returnType, annotations);
          } catch (RuntimeException e) { // Wide exception range because factories are user code.
            throw methodError(e, "Unable to create call adapter for %s", returnType);
          }
        }
    

    createCallAdapter方法中主要就是获取method类型和注解,最后再调用callAdapter(returnTYpe,annotations),现在进入retrofitcallAdapter(returnType, annotations)方法:

     public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
        return nextCallAdapter(null, returnType, annotations);
     }
    
      public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
          Annotation[] annotations) {
        checkNotNull(returnType, "returnType == null");
        checkNotNull(annotations, "annotations == null");
    
        int start = adapterFactories.indexOf(skipPast) + 1;
        for (int i = start, count = adapterFactories.size(); i < count; i++) {
          CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
          if (adapter != null) {
            return adapter;
          }
        }
    
      //移除处理代码省略。。。
    
      }
    

    callAdapter方法实际调用的是nextCallAdapter,通过传入的参数去循环在adapterFactories集合里面取出不为nullCallAdapter并返回。

    parseMethodAnnotation(annotation):

       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) {
    
        //省略部分代码。。。
    
        }
    
     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;
    
          // 省略部分代码。。。
    
          this.relativeUrl = value;
          this.relativeUrlParamNames = parsePathParameters(value);
        }
    

    parseMethodAnnotation(annotation)方法里面主要是判断注解的类型,然后分别调用方法对ServiceMethod的成员变量进行赋值。

    最后我们来看看ServiceMethodtoRequest方法:

    /** Builds an HTTP request from method arguments. */
      Request toRequest(Object... args) throws IOException {
        RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
            contentType, hasBody, isFormEncoded, isMultipart);
    
        @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
        ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    
        //省略部分代码。。。
     
        for (int p = 0; p < argumentCount; p++) {
          handlers[p].apply(requestBuilder, args[p]);
        }
        return requestBuilder.build();
      }
    

    toRequest方法中,将处理好的变量通过RequestBuilder封装,最后返回一个HTTP request。看到这里我们大概明白ServiceMethod是拿来做什么的了。大概总结一下:
    ServiceMethod接收到传入Retrofit对象和Method对象之后,ServiceMethod通过获得method里面的参数,然后调用各种解析器,最后将处理好的变量赋值给ServiceMethod成员变量,最后在toRequest方法中将这些参数给封装成OkHttp3的一个Request对象。

    现在回到Retrofit的create方法中:

    OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    

    获取serviceMethod之后,会将其作为参数继续封装为OkHttpCall对象;createRawCall会从serviceMethod中拿到Request,并转换为Call

    private okhttp3.Call createRawCall() throws IOException {
        Request request = serviceMethod.toRequest(args);
        okhttp3.Call call = serviceMethod.callFactory.newCall(request);
        if (call == null) {
          throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
      }
    

    parseResponse方法中会处理接口返回的结果,并调用serviceMethod中的toResponse方法去转换接口请求回来的数据,前面的例子,我们是给Retrofit设置的gson转换,因此这里是使用gson转换成java对象。

      Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();
    
        // Remove the body's source (the only stateful object) so we can pass the response along.
        rawResponse = rawResponse.newBuilder()
            .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
            .build();
    
       //处理返回码,代码省略。。。
    
        ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
        try {
          T body = serviceMethod.toResponse(catchingBody);
          return Response.success(body, rawResponse);
        } catch (RuntimeException e) {
          // If the underlying source threw an exception, propagate that rather than indicating it was
          // a runtime exception.
          catchingBody.throwIfCaught();
          throw e;
        }
      }
    
      T toResponse(ResponseBody body) throws IOException {
        return responseConverter.convert(body);
      }
    

    通过前面的讲解,我们发现ServiceMethod中封装了接口请求的数据,而OkHttpCall则从ServiceMethod中获得一个Request对象,最后返回一个Call对象,在接口回调之后,通过ServiceMethod中的Converter将接口中的ResponseBody 转换成Java对象。

    拿到okHttpCall之后,将它传入adapt方法:我们在来看看Retrofitcreate方法。

     public <T> T create(final Class<T> 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 {
                // //省略部分代码。。。。
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    

    重点看方法里面的两行有return的代码,调用create方法的时候返回一个泛型T,我们这里是PersonalProtocol,当我们调用PersonalProtocolgetPersonalListInfo的时候会通过动态代理,并返回serviceMethod.callAdapter.adapt(okHttpCall)也就是一个Call对象:

     @Override public <R> Call<R> adapt(Call<R> call) {
            return call;
     }
    

    于是就有了之前的代码:

     Call<Response<PersonalInfo>> call = personalProtocol.getPersonalListInfo(12);
     call.enqueue(new Callback<Response<PersonalInfo>>() {
                @Override
                public void onResponse(Call<Response<PersonalInfo>> call, Response<Response<PersonalInfo>> response) {
                    //数据请求成功
                }
    
                @Override
                public void onFailure(Call<Response<PersonalInfo>> call, Throwable t) {
                    //数据请求失败
                }
            });
    

    分析到这里,基本的原理就分析完了。

    ·

    相关文章

      网友评论

      本文标题:Retrofit2深入浅出

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