前言
估计很多人和我一样,在接触retrofit这个库的时候就被它强大的功能所吸引住了。它不同于传统的网络请求方式的是,retrofit巧妙的采用接口方式进行网络请求,每次调用接口方法,就是对应一次网络请求,这对于长期和丑陋接口做斗争的程序员来说这简直是莫大的福利啊。然而光是用肯定是不行,我们还得搞清其中的原理,知其why。一番周折之后,我发现自己在阅读源码并实现的过程中已经能作一文,于是写出来分享,算是学习中的心得体会。
示例
在开始之前,我们先看下一段简单的示例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://10.21.59.21:8080/")
.client(new OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
m_weatherApi = retrofit.create(WeatherApi.class);
findViewById(R.id.id_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Observable<Weather> list = m_weatherApi.getWeather("json");
list.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.newThread())
.subscribe(new Subscriber<Weather>() {
...
}
});
可以看到,在我们的例子中,我们首先创建了一个retrofit对象,这里使用的是Builder模式,创建过程中我们指定了网络请求时的uri, client, converterFactory (这也是这个库的核心之处),callAdapterFactory。
之后通过create方法,创建WeatherApi示例, 之后每次调用WeatherApi的方法都是对应于一次请求。
我们看下WeatherApi:
public interface WeatherApi {
@GET("/{path}")//使用get方式进行Http请求
//将上述的path替换成指定的path
//在我们的例子里面 这里会被替换成 "json" 这个字符串
Observable<Weather> getWeather(@Path("path") String path);
}
这里都是GET注解 指定请求方式是get,GET中的值会添加在baseUrl中, Path注解指定了具体的请求path,它将替换本文中的{path}字符串为具体的值,在我们的例子中,最后的请求uri会变成:
http://10.21.59.21:8080/json
好了,这里只是简单的看下示例,也是为我们整个全文做个铺垫,读者在阅读过程中要时常记得返回到此,才能加深理解。当然如果您还不了解该库具体的使用方式,可以参考:[retrofit官网]
(http://square.github.io/retrofit/)
我在编写博文的过程中就已经思考过了,要想懂这个库为什么如此设计,光看源码肯定不行。首先要做的就是理解它的Description information, 如果你点击了上面的链接,就可以看到,在它官网首页,就标注了retrofit是一个类型安全的Http客户端(A type-safe HTTP client for Android and Java)。什么叫类型安全呢?为什么要类型安全?这的确有点难懂啊
Type Safe
从上面的示例代码我们就看到了,在进行网络请求的时候,我们制定了它的客户端——OkHttpClient。也就是说,真正的网络请求都是通过okhttp实现的。然而,我们都使用过okhttp,所以我们都应该知道,在获得服务器请求之后,我们都是通过Response对象来获得服务器返回的数据的,比如这样:
Request request = ...;
OkHttpClient okHttpClient = new OkHttpClient();
com.squareup.okhttp.Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
/**
* 这里获得服务器的数据
* @param response
* @throws IOException
*/
@Override
public void onResponse(Response response) throws IOException {
ResponseBody responseBody = response.body();
//二进制
responseBody.bytes();
//字符串
responseBody.string();
}
});
获得的数据无非就是二进制类型或者字符串类型的。如果服务器返回的是json字符串,我们还得通过gson把它转换成实体类对象。这显然是不够友好的,所以我们要改变。用户无需知道服务器返回的数据具体格式,我们只要知道它最终的类型就好了Book? Person?etc。屏蔽这些细节,专心于业务实现岂不是更好。
而我们的retrofit如何做到的呢?那就是通过converterFactory 来实现了,它主要是负责将服务器返回的数据转换成具体的实例类对象。比如在我们这个例子里面,它的作用就是将服务器返回的json字符串转换成Book实体类对象。
分析实现方式
那么我们现在就根据上面的示例实现一个自己的retrofit,不过在开始之前还是需要分析一下实现方式
1:通过Retrofit.Builder对象new 一个Retrofit对象,期间需要配置的有:
(1):client,因为有时候我们可能需要在请求头加一个token头,用来作为访问服务器时验证其身份有效性的凭证。
(2):baseUrl,所有的请求Uri都是基于它的
(3):ConverterFactory, 一种工厂对象,用于根据用户指定的返回值类型,确定最终将服务器返回数据转换成对应实体类对象的Converter类型。在我们的例子里面GsonConverterFactory将选用GsonConverter来转换
(4):CallAdapterFactory,网络请求之后返回的是对应的Call< T >类型(这里的T对应于本文的Weather),然而如果我们结合RxJava使用的话,需要把它再做一次修饰,转换成Observable< T >类型。
2:注解:http请求是采用Get,post,还是delete,都需要根据描述接口方法的注解来确定,如果是Get的话,我们就需要生成get方式的Request对象,同理于Post Delete方式。所以我们需要一个RequestFactory,它根据方法的注解描述生成对于的Request对象。
3:通过动态代理,生成接口对应的对象,之后的每次方法Invoke都是上述组建之间互相配合
效果
我们先看下最终要到达的效果:
public class MainActivity extends AppCompatActivity {