添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

基础使用

以下就是实现一个登录Login接口的小功能 ,先了解一下Retrofit的基本用法:

private  void getLogin() {
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("//localhost:8080/")
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build();
ApiManager apiService = retrofit.create(ApiManager.class);
Call<LoginResult> call = apiService.getData("lyk", "1234");
call.enqueue(new Callback<LoginResult>() {
   @Override
   public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
       if (response.isSuccess()) {
           // 请求成功
       } else {
          //直接操作UI 或弹框提示请求失败
   @Override
   public void onFailure(Call<LoginResult> call, Throwable t) {
       //错误处理代码

ApiManager接口:

public interface ApiManager {
 @GET("login/")
 Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);

Retrofit支持异步和同步

call.enqueue(new Callback )采用异步请求;
call.execute() 采用同步方式。

call.cancel() 取消请求

CallAdapterFactory

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())这个是用来决定你的返回值是Observable还是Call。

// 使用call的情况
Call<String> login();  
// 使用Observable的情况
Observable<String> login();  

如果返回为Call那么可以不添加这个配置。如果使用Observable那就必须添加这个配置。否则就会请求的时候就会报错!

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

ConverterFactory

addConverterFactory 制定数据解析器 ,上面添加依赖的gson就是用在这里做默认数据返回的, 之后通过build()创建出来。

Retrofit内部自带如下格式:

Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

网络请求参数

@Path:所有在网址中的参数(URL的问号前面),如://192.168.1.1/api/Accounts/{accountId}

@Query:URL问号后面的参数,如://192.168.1.1/api/Comments?access_token={access_token}

@QueryMap:相当于多个@Query

@Field:用于POST请求,提交单个数据

@FieldMap:以map形式提交多个Field(Retrofit2.0之后添加)

@Body:相当于多个@Field,以对象的形式提交

  • 使用@Field时记得添加@FormUrlEncoded

  • 若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。

  • @Path 和@Query的区别
    相同点:都是请求头中的带有的数据
    不同点:前者是请求头中问号之前用于替换URL中变量的字段,后者是请求头问号之后用于查询数据的字段,作用和应用场景都不同

    进阶功能

    开启Log

    用拦截器实现, retrofit已经提供了 HttpLoggingInterceptor 里面有四种级别,输出的格式,可以看下面介绍:

    public enum Level {
    /** No logs. */
    NONE,
     * Logs request and response lines.
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting 
     * /1.1 (3-byte body)
     * <-- 200 OK (22ms, 6-byte body)
     * }</pre>
    BASIC,
     * Logs request and response lines and their respective headers.
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1
     * Host: example.com
     * Content-Type: plain/text
     * Content-Length: 3
     * --> END POST
     * <-- 200 OK (22ms)
     * Content-Type: plain/text
     * Content-Length: 6
     * <-- END HTTP
     * }</pre>
    HEADERS,
     * Logs request and response lines and their respective headers and bodies (if present).
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1
     * Host: example.com
     * Content-Type: plain/text
     * Content-Length: 3
     * Hi?
     * --> END GET
     * <-- 200 OK (22ms)
     * Content-Type: plain/text
     * Content-Length: 6
     * Hello!
     * <-- END HTTP
     * }</pre>
    

    例如,开启请求头添加拦截器:

    Retrofit retrofit = new Retrofit.Builder().client(new OkHttpClient.Builder()
                             .addNetworkInterceptor(new  HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))       
                             .build())
    

    增加头部信息

    new Retrofit.Builder()
           .addConverterFactory(GsonConverterFactory.create())
           .client(new OkHttpClient.Builder()
                   .addInterceptor(new Interceptor() {
                       @Override
                       public Response intercept(Chain chain) throws IOException {
                           Request request = chain.request()
                                   .newBuilder()
                                   .addHeader("mac", "f8:00:ea:10:45")
                                   .addHeader("uuid", "gdeflatfgfg5454545e")
                                   .addHeader("userId", "Fea2405144")
                                   .addHeader("netWork", "wifi")
                                   .build();
                           return chain.proceed(request);
                   .build()
    

    特殊API接口单独加入,方法上注释@Headers:

    @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-your-App"})
    @get("users/{username}")
    Call<User>   getUser(@Path("username") String username);
    

    添加证书Pinning

    证书可以在自定义的OkHttpClient加入certificatePinner 实现:

    OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(new CertificatePinner.Builder()
            .add("YOU API.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
            .add("YOU API..com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
            .add("YOU API..com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
            .add("YOU API..com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
            .build())
    

    支持https

    加密和普通http客户端请求支持https一样,证书同样可以设置到okhttpclient中.详细可以参考我之前的文章: android中使用https

    常见问题

    url被转义

    https://api.myapi.com/http%3A%2F%2Fapi.mysite.com%2Fuser%2Flist
    

    请将@path改成@url

    public interface APIService { 
    @GET Call<Users> getUsers(@Url String url);}
    
    public interface APIService {
        @GET("{fullUrl}")
        Call<Users> getUsers(@Path(value = "fullUrl", encoded = true) String fullUrl);
    

    Method方法找不到

    java.lang.IllegalArgumentException: Method must not be null
    

    请指定具体请求类型@get @post等

    public interface APIService { 
       @GET Call<Users> getUsers(@Url String url);
    

    Url编码不对,@fieldMap parameters must be use FormUrlEncoded

    如果用fieldMap加上FormUrlEncoded编码

    @POST()
    @FormUrlEncoded
    Observable<ResponseBody> executePost(@FieldMap Map<String, Object> maps);
    

    上层需要转换将自己的map转换为FieldMap

    @FieldMap(encoded = true) Map<String, Object> parameters,
    

    path和url一起使用

    Using @Path and @Url paramers together with retrofit2 
    java.lang.IllegalArgumentException: @Path parameters may not be used with @Url. (parameter #4
    

    如果你是这样的:

    Call<DataResponse> getOrder(@Url String url, @Path("id") int id);

    请在你的url指定占位符.url:

    www.myAPi.com/{Id}
    

    原理

    Retrofit就像一个适配器(Adapter)的角色,将一个Java接口转换成一个Http请求并返回一个Call对象,简单的调用接口方法就可以发送API请求,Retrofit完全隐藏了Request 的请求体,并使用okhttp执行请求。

    Retrofit 是怎么实现的呢?答案就是:Java的动态代理。 Java动态代理,是一种结构性设计模式,可以在要调用的Class方法前或后,插入想要执行的代码进行改造。

    案例中关键两行代码:

    ApiManager apiService = retrofit.create(ApiManager.class); //2、retrofit对象创建一个API接口对象
    Call<LoginResult> call = apiService.getData("lyk", "1234"); //返回响应接口回调
    

    这简短的两行代码,隐藏了Request请求体并拿到Response返回Call对象。看下源码,这几行代码才是 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);
    

    源码分析:
    当 apiService 对象调用 getData方法时,就会被这个动态代理拦截并在内部做些小动作,它会调用 Proxy.newProxyInstance方法 中的 InvocationHandler 对象,它的 invoke方法 会传入3个参数:

    Object proxy :代理对象 ,即APIManner.class
    Method method :调用方法,即getData方法
    Object… args : 参数对象,即 “lyk”,”1234”

    Retrofit 得到了 method 和 参数args 。接下去 Retrofit 就会用 Java反射 获取到 getData方法 的注解信息,配合args参数,创建一个ServiceMethod对象。

    ServiceMethod 是服务于请求方法的,服务于传入Retrofit的proxy对象的method方法,即getData方法。如何服务呢?它可以将method通过各种内部接口解析器进行组装拼凑,最终生成一个Request请求体。这个Request 包含 api域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个Call对象,Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client。一句话就是: Retrofit 使用Java动态代理就是要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送Http请求。

    想要弄清楚Retrofit的细节,先来简单了解一下Retrofit源码组成结构:


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

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

    Retrofit接口

    Retrofit的设计使用插件化而且轻量级,高内聚而且低耦合,这都和它的接口设计有关。Retrofit中定义了四个接口:

  • Callback
  • Converter<F, T>
  • CallAdapter
  • 1、Callback
    这个接口就是retrofit请求数据返回的接口,只有两个方法:

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

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

    3、Call
    这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall ,你可以根据实际情况实现你自己的Call类。这个设计和Volley的HttpStack接口设计的思想非常相似,子类可以实现基于HttpClient或HttpUrlConnetction的HTTP请求工具。

    4、CallAdapter
    这个借口的属性只有responseType一个;这个接口的实现类也只有DefaultCallAdapter一个。这个方法的主要作用就是将Call对象转换成另一个对象,为了支持RxJava才设计这个类的吧。

    Retrofit的运行过程

    上面讲的案例代码,返回了一个动态代理对象。而执行这段代码时,返回了一个OkHttpCall对象,拿到这个 Call 对象才能真正执行 HTTP 请求。

    ApiManager apiService = retrofit.create(ApiManager.class); //2、retrofit对象创建一个API接口对象
    Call<LoginResult> call = apiService.getData("lyk", "1234"); //返回响应接口回调
    

    上面代码中 apiService 对象其实是一个动态代理对象。当 apiService 对象调用 getData方法 时会被动态代理拦截,然后调用 Proxy.newProxyInstance 方法中的 InvocationHandler 对象, 创建一个 ServiceMethod对象:

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

    创建ServiceMethod

    刚才说到 ServiceMethod 是服务于方法的,具体来看一下创建这个ServiceMethod的过程是怎么样的:
    首先,获取到上面说到的 Retrofit的接口:

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

    然后,解析Method方法的注解,其实就是想获取Http请求的方法。比如请求方法是GET还是POST形式,如果没有程序就会报错。还会做一系列的检查,比如在方法上注解了@Multipart,但是Http请求方法是GET,同样也会报错。

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

    其次,比如上面 apiService 接口的方法中带有参数{name,password},这都占位符,而参数值是在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对象。

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

    总结

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

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

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

    参考资料

    Android Retrofit 2.0
    https://blog.csdn.net/jiankeufo/article/details/73186929
    https://www.jianshu.com/p/2e8b400909b7