Bootstrap

Retrofit使用与解析

Retrofit使用与解析


Retrofit是一个基于OkHttp的网络请求工具,其功能与Volley很相似,但是使用起来就很不一样。
Retrofit不用你去创建Request对象,每次指定Get还是Post等,它请求一个api只需你去调用一个Java方法。

使用

1.首先需要创建一个Retrofit对象,并指定域名:

//使用Builder模式构建对象
private static final String BASE_URL = "http://xxx";//域名
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

Retrofit中Bulider类的build方法

 public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();//默认使用OkHttp
      }

      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);
    }
  }

2.之后,你只要根据api新建一个Java接口,并使用注解进行相关描述:

//Get请求
public interface ElectricAPI {
        @GET("/api/v1/get/power/{lou}/{hao}")
        Call<Electric> electricData(@Path("lou") String lou,@Path("hao") String hao);
}
//Post请求
public interface FeedBackAPI {
    @FormUrlEncoded
    @POST("/home/msg/0")
    Call<ResponseBody> feed(@Field("email") String email,
                                      @Field("content") String content);
}
//文件上传
public interface FileUploadAPI {
    @Multipart
    @POST("api/v1/stuff/upload")
    Call<HttpResult<String>> uploadImage(@Part MultipartBody.Part file);
}

3.最后只要通过retrofit对象创建Api对象了,并使用call对象进行请求和处理:

ElectricAPI api = retrofit.create(ElectricAPI.class);
Call<Electric> call = api.electricData("12","122");
// 请求数据,并且处理response
call.enqueue(new Callback<Electric>() {
    @Override
    public void onResponse(Response<Electric> r) {
        System.out.println("请求成功");
    }
    @Override
    public void onFailure(Throwable t) {
    }
});

原理

从使用上可以看出,Retrofit其实是把一个Java接口对象翻译成一个Http请求,然后使用OkHttp请求。Retrofit使用的技术就是动态代理。
先看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)) {//默认返回false
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);//处理注解
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

接着是create方法的使用:

ElectricAPI api = retrofit.create(ElectricAPI.class);
Call<Electric> call = api.electricData("12","122");

这里的api对象其实是一个动态代理,并不是实现了相关接口的对象。当api对象调用eletricData方法时,会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象的invoke方法。这里invoke方法传入三个对象,依次为代理对象(不用管)、method(这里是electricData方法),方法的参数(这里是”12”,”122”)。

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

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

接着看下上面的loadServiceMethod(Method method)方法

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;
  }


 private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();

这里对解析请求进行了缓存,用于避免解析过慢。这里同样使用Builder模式构建ServiceMethod方法:

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).");
        }
      }
      //处理api中的参数{ }占位符,真实参数在Java方法中传入,这里Retrofit会使用一个ParameterHandler来进行替换
      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);
      }

      if (relativeUrl == null && !gotUrl) {
        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError("Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError("Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError("Multipart method must contain at least one @Part.");
      }

      return new ServiceMethod<>(this);
    }

执行Http请求

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

 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;
    //参数替换
    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.build();
  }

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

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

;