当前位置:首页 > 科技数码 > 正文

Retrofit2.0源码解析

摘要: Retrofit2.0源码解析最佳答案53678位专家为你答疑解惑Retrofit2.0源码解析在开发AndroidAPP时,肯...

Retrofit2.0源码解析

最佳答案 53678位专家为你答疑解惑

Retrofit2.0源码解析

在开发Android APP时,肯定会使用到Http请求与服务器通信,上传或下载数据等功能。目前开源的Http请求工具也有很多,比如Google开发的Volley,loopj的Android Async Http,Square开源的OkHttp或者Retrofit等。现在比较流行的网络库是:Retrofit2.0+RxJava2.0+oKhttp3.0。

我觉得Retrofit 无疑是这几个当中最好用的一个,设计这个库的思路很特别而且巧妙。Retrofit的代码很少,花点时间读它的源码肯定会收获很多。

本文的源码分析基于Retrofit 2,和Retrofit 1.0的Api有较大的不同, 本文主要分为几部分:

0、Retrofit 是什么1、Retrofit怎么用2、Retrofit的原理是什么3、我的心得与看法
Retrofit是什么
来自Retrofit官网的介绍:A type-safe HTTP client for Android and Java

简单的说它是一个基于OkHttp的RESTFUL Api请求工具,从功能上来说和Google的Volley功能上很相似,但是使用上很不相似。

Volley使用上更加原始而且符合使用者的直觉,当App要发送一个Http请求时,你需要先创建一个Request对象,指定这个Request用的是GET、POST或其他方法,一个api 地址,一个处理response的回调,如果是一个POST请求,那么你还需要给这个Request对象设置一个body,有时候你还需要自定义添加Header什么的,然后将这个Request对象添加到RequestQueue中,接下去检查Cache以及发送Http请求的事情,Volley会帮你处理。如果一个App中api不同的api请求很多,这样代码就会很难看。

而Retrofit可以让你简单到调用一个Java方法的方式去请求一个api,这样App中的代码就会很简洁方便阅读

Retrofit怎么用

虽然Retrofit官网已经说明了,我还是要按照我的思路说一下它的使用方法比如你要请求这么一个api,查看知乎专栏的某个作者信息:https://zhuanlan.zhihu.com/api/columns/{user}

首先,你需要创建一个Retrofit对象,并且指定api的域名:

public static final String API_URL="https://zhuanlan.zhihu.com";Create a very simple REST adapter which points the Zhuanlan API.Retrofit retrofit=new Retrofit.Builder()    .baseUrl(API_URL)    .addConverterFactory(GsonConverterFactory.create())    .build();

其次,你要根据api新建一个Java接口,用Java注解来描述这个api

public interface ZhuanLanApi {    @GET("/api/columns/{user} ")    Call<ZhuanLanAuthor> getAuthor(@Path("user") String user)}

再用这个retrofit对象创建一个ZhuanLanApi对象:

ZhuanLanApi api=retrofit.create(ZhuanLanApi.class);Call<ZhuanLanAuthor> call=api.getAuthor("qinchao");

这样就表示你要请求的api是https://zhuanlan.zhihu.com/api/columns/qinchao

最后你就可以用这个call对象获得数据了,enqueue方法是异步发送http请求的,如果你想用同步的方式发送可以使用execute()方法,call对象还提供cancel()、isCancel()等方法获取这个Http请求的状态

// 请求数据,并且处理responsecall.enqueue(new Callback<ZhuanLanAuthor>() {    @Override    public void onResponse(Response<ZhuanLanAuthor> author) {        System.out.println("name: " + author.getName());    }    @Override    public void onFailure(Throwable t) {    }});

看到没,Retrofit只要创建一个接口来描述Http请求,然后可以让我们可以像调用Java方法一样请求一个Api,是不是觉得很神奇,很不可思议!!

Retrofit的原理

从上面Retrofit的使用来看,Retrofit就是充当了一个适配器(Adapter)的角色:将一个Java接口翻译成一个Http请求,然后用OkHttp去发送这个请求

Volley描述一个HTTP请求是需要创建一个Request对象,而执行这个请求呢,就是把这个请求对象放到一个队列中,在网络线程中用HttpUrlConnection去请求

问题来了:Retrofit是怎么做的呢?

答案很简单,就是:Java的动态代理

动态代理我刚开始看Retrofit的代码,我对下面这句代码感到很困惑:

ZhuanLanApi api=retrofit.create(ZhuanLanApi.class);

我给Retrofit对象传了一个ZhuanLanApi接口的Class对象,怎么又返回一个ZhuanLanApi对象呢?进入create方法一看,没几行代码,但是我觉得这几行代码就是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);      }    });}

create方法就是返回了一个Proxy.newProxyInstance动态代理对象。那么问题来了...

动态代理是个什么东西?看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在Spring框架里大量的用到,但是它有什么用呢?

Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码

比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。

为什么要使用动态代理你看上面代码,获取数据的代码就是这句:

Call<ZhuanLanAuthor> call=api.getAuthor("qinchao");

上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象,它的invoke方法会传入3个参数:

Object proxy: 代理对象,不关心这个Method method:调用的方法,就是getAuthor方法Object... args:方法的参数,就是"qinchao"

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

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

使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送

Retrofit的源码分析想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:

一个retrofit2.http包,里面全部是定义HTTP请求的Java注解,比如GET、POST、PUT、DELETE、Headers、Path、Query等等余下的retrofit2包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了

Retrofit接口Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:

Callback<T> 这个接口就是retrofit请求数据返回的接口,只有两个方法
void onResponse(Response<T> response);void onFailure(Throwable t);

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

Call<T>这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>,你可以根据实际情况实现你自己的Call类,这个设计和Volley的HttpStack接口设计的思想非常相似,子类可以实现基于HttpClient或HttpUrlConnetction的HTTP请求工具,这种设计非常的插件化,而且灵活

CallAdapter<T>上面说到过,CallAdapter中属性只有responseType一个,还有一个<R> T adapt(Call<R> call)方法,这个接口的实现类也只有一个,DefaultCallAdapter。这个方法的主要作用就是将Call对象转换成另一个对象,可能是为了支持RxJava才设计这个类的吧

Retrofit的运行过程上面讲到ZhuanLanApi api=retrofit.create(ZhuanLanApi.class);代码返回了一个动态代理对象,而执行Call<ZhuanLanAuthor> call=api.getAuthor("qinchao");代码时返回了一个OkHttpCall对象,拿到这个Call对象才能执行HTTP请求。上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象, 创建一个ServiceMethod对象

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

创建ServiceMethod

刚才说到,ServiceMethod就像是一个中央处理器,具体来看一下创建这个ServiceMethod的过程是怎么样的

第一步,获取到上面说到的3个接口对象:

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

第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了@Multipart,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨

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

第三步,比如上面api中带有一个参数{user},这是一个占位符,而真实的参数值在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中使用RxJava

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

Retrofit retrofit=new Retrofit.Builder()  .baseUrl("https://api.github.com")  .addConverterFactory(ProtoConverterFactory.create())  .addConverterFactory(GsonConverterFactory.create())  .addCallAdapterFactory(RxJavaCallAdapterFactory.create())  .build();

上面代码创建了一个Retrofit对象,支持Proto和Gson两种数据格式,并且还支持RxJava

最后

Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)Retrofit中接口设计的恰到好处,在你创建Retrofit对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能.

我也慢慢看了Picasso和Retrofit的代码了,收获还是很多的,也更加深入的理解面向接口的编程方法,这个写代码就是好的代码就是依赖接口而不是实现最好的例子

好感谢开源的世界,让我能读到大牛的代码。我一直觉得一个人如果没有读过好的代码是不太可能写出好代码的。什么是好的代码?像Picasso和Retrofit这样的就是好的代码,扩展性强、低耦合、插件化.

Retrofit2.0入门教程

前面我已经把现在最为流行三个网络框架的其中两个volley和Okhttp3撸了一遍,接下来我就把最后的一个Retrofit2也撸一下。最近也搞了一下Android SDK开发的事,发现网上对于Android SDK开发的资料真的很少。到时我会把这个框架的功能集成到一个Jar包上,做一个简单的SDK开发来让大家也了解一下。

我们先来了解一下Retrofit2这个网络框架,准确来说Retrofit是基于Okhttp3的官方封装一个网络框架。所以这个框架的使用跟Okhttp3非常的相似,如果你在此之前有看过我前面的OKhttp3的教程或者其他人的教程的话,接下来的内容你就会更加的容易了解。

由于最近更新了Androidstudio4.0,便用这个编译环境来试水啦,不过使用原理都是一样的。首先我们先创建一个名为RetrofitDemo的项目,然后在app的gradle下添加Retrofit的依赖包。如下:

  implementation 'com.squareup.retrofit2:retrofit:2.4.0'

然后我们尝试做这样一个功能,当我们点击一个GET按钮时就向服务端发送一个GET请求,然后我们把返回来的结果通过吐司展示出来。当我们点击一个POST按钮就向服务端发送一个POST请求,也把返回来的结果吐司展现。XML布局如下:

<Button      android:layout_margin="10dp"    android:id="@+id/btn_login_get"   android:text="GET登录"      android:layout_width="match_parent"      android:layout_height="wrap_content" />     <Button      android:layout_margin="10dp"   android:id="@+id/btn_login_post"  android:text="POST登录"      android:layout_width="match_parent"   android:layout_height="wrap_content" />

接下来我们创建一个名为Api的接口,这是根据Retrofit的官方文档的格式来定义。接口代码如下:

public interface Api {   @GET("写上你要访问的地址")//GET请求注释    Call<ResponseBody> getUserInfo();       @POST("写上你要访问的地址")//POST请求注释     Call<User> postSubmit (@Body User user); }

主函数里,因为我们的POST请求是以JSON的格式来返回数据的,所以我们要先在app.gradle里添加GSON依赖,代码如下:

 implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

接下来我们分别为两个按钮添加点击事件,并调用刚才定义接口的GET和POST方法来验证是否能够正常访问我们需要访问的地址,并进行吐司,最后还要在AndroidManifest里面注册网络权限。主函数代码如下:

  private Button btn_login_get,btn_login_post;   private  Api api;    @Override   protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);       btn_login_get=findViewById(R.id.btn_login_get);     btn_login_post=findViewById(R.id.btn_login_post);       Retrofit retrofit=new Retrofit.Builder()           .baseUrl("写上你要访问的基地址如:http://127.0.0.1:8080/")              .addConverterFactory(GsonConverterFactory.create())//通过Gson的格式来解析数据        .build();      api=retrofit.create(Api.class);       btn_login_post.setOnClickListener(new View.OnClickListener() {                                         @Override                                               public void onClick(View v) {               User user=new User();                   user.setMoyo_sdk_name("admin");            user.setMoyo_sdk_pwd("123456");               api.postSubmit(user).enqueue(new Callback<User>() {//调用了我们在接口上定义的POST方法postSubmit(请求类型) Callback(返回类型)                                            @Override                                                    public void onResponse(Call<User> call, Response<User> response) {         String name=response.body().getMoyo_sdk_name();            String password=response.body().getMoyo_sdk_pwd();           int code=response.body().getCode();                   Toast.makeText(MainActivity.this,"name:"+name+"password:"+password+"code:"+code,Toast.LENGTH_LONG).show();             }                      @Override                public void onFailure(Call<User> call, Throwable t) {         }          });         }      });      btn_login_get.setOnClickListener(new View.OnClickListener() {                                     @Override                                        public void onClick(View v) {        //此部分为不简写,适合新手查看原理      Call<ResponseBody> call=api.getUserInfo();           call.enqueue(new Callback<ResponseBody>() {                       @Override                                  public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {////             try {                              String msg=response.body().string();                     Toast.makeText(MainActivity.this,msg,Toast.LENGTH_LONG).show();//                } catch (IOException e) {              e.printStackTrace();                 }             }                 @Override      public void onFailure(Call<ResponseBody> call, Throwable t) {     }        });          api.getUserInfo().enqueue(new Callback<ResponseBody>() {//调用了接口上的GET方法getUserInfo()                              @Override                                         public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {        try {                           String result=response.body().string();            Toast.makeText(MainActivity.this,result,Toast.LENGTH_LONG).show();           } catch (IOException e) {       e.printStackTrace();            }                 }                   @Override        public void onFailure(Call<ResponseBody> call, Throwable t) {    }         });          }      }); }

在我们处理数据的时候我们可以选择创建一些Bean类,因为每个人请求的后台所得到的数据都不一样我就不把我的Bean类的源码粘贴出来了。在官方文档中还有很多的提交形式,有兴趣的可以去了解一下。我这里算了抛砖引玉,如果有什么错误的地方欢迎各位提出来一个探讨。

发表评论