Retrofit学习笔记

注:以下内容为本人自己做(chao)的学习笔记,可能含有少儿不宜的误导性内容,学习Retrofit请看参考文章。。。
参考文章:http://www.jianshu.com/p/0d919e54eef0
OkHttp使用:

//创建client对象
OkHttpClient client = new OkHttpClient();

//创建Request
Request request = new Request.Builder()
  .url(url)
  .build();

//执行请求,获取结果
client.newCall(request).enqueue(new Callback() {
  @override
  public void onFailure(Call call, IOException e) {
  }

  @override
  public void onResponse(Call call, Response response) throws IOException {
    content.setText(response.body().string());
  }
});

以上既是OkHttp的使用方法,使用OkHttp时需要自己处理多线程,开启子线程发送请求,收到数据后需要使用Handler来更新UI。
放屁,OkHttp支持同步和异步两种方式发送请求,execute及enqueue。

Retrofit使用:

//定义API接口
public interface APIService {
  @GET("/test.json")
  Call<Model> getResult();

  @GET("/search.json")
  Call<SearchResult> getResult();
}

//创建Retrofit
Retrofit retrofit = new Retrofit.Builder()
  .baseUrl(BASE_URL)
  .addConverterFactory(GsonConverterFactory.create())
  .client(new OkHttpClient())
  .build();

//获取接口Service的代理对象
APIService apiService = retrofit.create(APIService.class);

//调用代理对象的方法获取到Call对象(Call对象是对Request的一层封装)
Call<Model> result = apiService.getResult();

//异步执行请求
result.enqueue(new Callback<Model>() {
  @override
  public void onResponse(Response<Model> response, Retrofit retrofit) {
    content.setText(response.body().toString());
  }

  @override
  public void onFailure(Throwable t) {
    content.setText("error");
  }
});

单从代码量来看,Retrofit并不比OkHttp需要写的代码少。之所以要对OkHttp做这样的封装,我觉得原因在于:
1.不需要知道什么是Request,什么是Client以及Call对象,闭着眼用就行。
2.把网络接口统一封装到一个接口类中,看上去更规整。
3.使用Annotation标识请求类型和路径,虽然我并不知道这么干有什么卵用,大家都说好才是真的好。

既然要分享框架,那就必须得看源码。
创建API接口类和用Builder创建Retrofit不提也罢。直接看重点:
APIService apiService = retrofit.create(APIService.class);
传一个接口类进去,返回一个实例给我。
厉害了。
其实也并没那么神奇,我猜测这个方法是这样的:传一个接口进去,里面用代码创建一个类,实现我传进去的这个接口,把那个类的实例返回给我。
咦?用代码创建类?对我这种白菜来说也是很新鲜的,用代码写代码看上去也是屌屌的。
喽一眼源码

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<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

关键当然是在return那一句。咦,Proxy是个啥...

Java动态代理
先说说我是怎么理解代理的。作为年轻程序猿,大都非常老实本分加勤奋,不愿意卷入世俗纷争,假装与Boss谈笑风生。所以这个时候,就要给每一个程序猿配送一个解说员,由解说员去解决谈笑风生的事,程序猿专心写好程序就行了。这个解说员就可以看做是一个代理,事实上,这个代理的目的就是把程序猿和Boss进行解耦,让Boss专心谈笑风生,程序猿专心写程序。
然而事与愿违,小公司请不起那么多人当解说员,所以就有了动态代理,也就是Leader。Leader在不出事儿的时候(编译时)是喝茶的,一旦一个团队的某个程序猿出了问题(运行时),Leader就会跳出来变身成该程序猿的解说员去与Boss谈笑风生。

扯完了蛋,来看看动态代理的代码:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    //权限判断等,忽略
    //生成代理类
    Class<?> cl = getProxyClass0(loader, intfs);
    //调用构造器,生成实例
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        //各种异常处理
    }
}

看方法名就能看出来,此方法是用来new代理实例的。
有三个参数:
1.ClassLoader,类加载器,用来加载这个类...算了其实我不懂,反正就是要这个参数拿去创建类的。
2.Class<?>[] interfaces,要继承的接口数组,这个我懂。
3.InvocationHandler,祈祷处理器......这个是代理类要完成的任务的具体实现。
Class<?> cl = getProxyClass0(loader, intfs);
这行代码创建了一个代理类,实现了传进去的接口。
return cons.newInstance(new Object[]{h});
这行代码返回上面创建的Class的新的实例,顺便把接收到的InvocationHandler传了进去。
跟Retrofit结合来看一下。还记得这句代码吧:
RestApi restApi = retrofit.create(RestApi.class);
我们传一个接口类进去,再看一遍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)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

它拿到了我们传进去的接口类的ClassLoader作为创建动态代理方法的第一个参数,新建一个Class数组承载我们传进去的接口,最后它自己写了一个InvocationHandler传进去。
最终,动态代理的newProxyInstance返回一个包含InvocationHandler,实现了Retrofit传进去接口的APIService接口类的代理类实例给Retrofit(自己看着都累)。Retrofit再把这个代理类实例返给我们。
OK,上面回顾的是传入一个接口类给Retrofit,Retrofit返回代理实例给我们的实现流程。这时候还剩一个磨人的小妖精,就是那个InvocationHandler究竟是干啥的。
要探究这个问题就得看看生成的动态代理类里面是什么样子的了。
Class<?> cl = getProxyClass0(loader, intfs);
这句代码就是用来生成代理类的,那么来看看getProxyClass这个方法里头干了啥。

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
     //一个代理类最多能够实现65535个接口
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 这里Proxy做了一次缓存,如果之前生成过这个Classloader和interfaces的代理类,那么这里直接返回

  //否则新生成类的字节码文件
   byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
            try {
                    //将字节码加载到JVM
                    proxyClass = defineClass0(loader, proxyName,
                    proxyClassFile, 0, proxyClassFile.length);
              }
        retrun proxyClass;
}

可以看到,最终是调用ProxyGenerator的generateProxyClass方法生成类的字节码并加载到JVM,再返回。
至于这个方法是怎么生成的,咱就不追下去了(因为参考的文章里没追...)。不过可以通过它生成的代理类反推它干了什么。
把生成的代理类的Class文件导出并反编译,可以看到,这个代理类继承自Proxy,实现了传进去的所有接口。它除了实现了Object基础的equals,toString和hashCode这几个方法外,还实现了我们接口定义的方法。

//以下代码纯手打,不一定准确
public final Call<Model> getResult() throws {
  try {
    return (Call)super.h.invoke(this, m3, new Object[]{"retrofit"});
    } catch (RuntimeException | Error var4) {
        throw var4;
    } catch (Throwable var5) {
        throw new UndeclaredThrowableException(var5);
    }
}

其中,m3是通过反射拿到的Method对象。

private static Method m3;
m3 = Class.forName("proxy.GenerateClass$APIService").getMethod("getResult", new Class[] {});

我们拿到接口的动态代理实例后,直接调用动态代理中实现的我们的接口定义的getResult方法,此方法会把调用转发给父类(Proxy)的InvocationHandler的invoke方法。
Call<Model> result = apiService.getResult();
此时Retrofit的create方法中的InvocationHandler的invoke回调被调用。

@Override 
public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
  // 如果method是Object的基础方法,就正常执行方法
  if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
  }
  //据说是Java8兼容,忽略
  if (platform.isDefaultMethod(method)) {
    return platform.invokeDefaultMethod(method, service, proxy, args);
  }
  ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
  OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
  return serviceMethod.callAdapter.adapt(okHttpCall);
  }
}

现在来看invoke回调。重点在最后三行代码。
进到ServiceMethod类中,看见注释:
/** Adapts an invocation of an interface method into an HTTP call. */
翻译一下就是说:把一个接口方法的invocation翻译(适配)成一个HTTP 请求。可以直接理解为:ServiceMethod把我们定义在APIService接口类中的方法翻译成一个HTTP请求。具体就是翻译接口类方法的注释、参数,然后自动加上什么Header啦,body啦的什么鬼。

ServiceMethod<?, ?> loadServiceMethod(Method method) {
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;

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

从这个方法可以看出来,每一个method对应一个serviceMethod。
所以第一行代码就是获取到当前method对应的serviceMethod。
第二行代码
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
这里是Retrofit封装Call对象的OkHttpCall类。它的构造函数可以接受一个serviceMethod和参数。
第三行代码
return serviceMethod.callAdapter.adapt(okHttpCall);
不用说,肯定是把okHttpCall变成Call对象返回给我们。这里Retrofit留了拓展可能性。这里的callAdapter由CallAdapter.Factory生产,默认使用的是DefaultCallAdapterFactory。我们可以对其进行定制,自定义一个CallAdapter.Factory的子类,然后返回其他的类型。

到现在为止,终于把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)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

其实也没那么复杂...
再往回套一层,回到使用上面来,咱们已经调用了动态代理实例的方法,拿到了Call对象。往下就是enqueue执行了,这个是OkHttp的东西,在这里就不深究了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容