Retrofit 原理以及快速使用教程

初步理解

一.引用
首先在build.gradle中引用

    implementation 'com.squareup.okhttp3:okhttp:3.1.2'
    // Retrofit库
    implementation 'com.squareup.retrofit2:retrofit:2.0.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

retrofit而且支持多种数据的解析方式

数据解析器Gradle依赖
Gsoncom.squareup.retrofit2:converter-gson:2.0.2
Jacksoncom.squareup.retrofit2:converter-jackson:2.0.2
Simple XMLcom.squareup.retrofit2:converter-simplexml:2.0.2
Protobufcom.squareup.retrofit2:converter-protobuf:2.0.2
Moshicom.squareup.retrofit2:converter-wire:2.0.2
Wirecom.squareup.retrofit2:converter-gson:2.0.2
Scalarscom.squareup.retrofit2:converter-scalars:2.0.2

二.api接口创建
请求方法注解最常用的:@post,@Get
请求参数最常用的一些:@headers,@Path,@Field,@Query,@QueryMap Map<String, String> map,@Body Book book;

    @GET("blog/{id}")
    Call<Book> getBookData(@Path("id") int id);

@GET:
1最简单的样式:

http://102.10.10.132/api/News
@GET("News")
Call<NewsBean> getItem();

2.@Path:

http://102.10.10.132/api/News/1
 @GET("News/{newsId}")
 Call<NewsBean> getItem(@Path("newsId") String newsId);

3@Query

http://102.10.10.132/api/News?newsId=1
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId);

4.@QueryMap

http://102.10.10.132/api/News?newsId=1&type=类型1...
@GET("News")
Call<NewsBean> getItem(@QueryMap Map<String, String> map);

@POST
1.@Path,@Query 同上
2.@Field:需要加上FormUrlEncoded 注解

http://102.10.10.132/api/Comments/1
@FormUrlEncoded
@POST("Comments/{newsId}")
Call<Comment> reportComment(@Path("newsId") String commentId,@Field("reason") String reason);

3.@Body:添加一个对象

http://102.10.10.132/api/Comments/1?access_token=1234123
@POST("Comments/{newsId}")
 Call<Comment> reportComment( @Path("newsId") String commentId, @Query("access_token") String,access_token, @Body CommentBean bean);

三.获取对象

    final Retrofit retrofit=new Retrofit.Builder()
                .baseUrl("http://192.168.0.42:4567/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        Call<Book> mApi = retrofit.create(ApiService.class).getBookData(1);

1.首先获取一个retrofit对象retrofit
2.获取接口的代理对象 retrofit.create(ApiService.class)
3.通过调用接口函数,获得一个可执行的网络访问对象mApi

四.访问网络数据

  mApi.enqueue(new Callback<Book>() {
            @Override
            public void onResponse(Call<Book> call, Response<Book> response) {
                Message message=new Message();
                message.obj=response.body();
                handler.sendMessage(message);
                Toast.makeText(RetrofitDemo.this,response.body().getData().toString(),Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onFailure(Call<Book> call, Throwable t) {
                Toast.makeText(RetrofitDemo.this,"访问出错",Toast.LENGTH_SHORT).show();
            }
        });
    }

原理

一.理解
Retrofit其实并不做网络访问的请求,他的底层还是对OkHttp的封装,通过OKHttp对网络数据进行访问,外层有灵活提供能直接融入业务逻辑的Call网络访问对象

public interface INetApiService {
    @GET("/demobiz/api.php")
    Call<BizEntity> getBizInfo(@Query("id") String id);
}

这个接口很不简单,是用来获取其中的网络工作对象也就是 Call 对象,然后在进行相应的网络访问请求,

二.功能扩展
1.OkHttpClient
通过扩展拦截器添加头数据,进行对网络数据的请求

OkHttpClient mClient = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        try {
                            Request.Builder builder = chain.request().newBuilder();
                            builder.addHeader("Accept-Charset", "UTF-8");
                            builder.addHeader("Accept", " application/json");
                            builder.addHeader("Content-type", "application/json");
                            Request request = builder.build();
                            return chain.proceed(request);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                }).build();

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Config.DOMAIN)
                .addConverterFactory(GsonConverterFactory.create())
                .client(mClient)
                .build();
}

2.addConverterFactory
扩展的是对接口获取到的数据进行格式转换,由一种对象转换为另一种对象
GsonConverterFactory就是吧获取到的json 字符串转换为Book对象
如果现有的扩展包不能满足需要,可以继承Retrofit的接口。retrofit2.Converter<F,T>,自己实现Converter和ConverterFactory。

/retrofit对象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(YourConverterFactory.create())//添加自定义Converter
.build();

3.addCallAdapterFactory
Retrofit本身用一个OkHttpCall的类负责处理网络请求,而我们在接口中定义需要定义很多种Call,例如Call,或者Flowable等,接口里的Call和Retrofit里的OkHttpCall并不一致,所以我们需要用一个CallAdapter去做一个适配转换。
(Retrofit底层虽然使用了OkHttpClient去处理网络请求,但她并没有使用okhttp3.call这个Call接口,而是自己又建了一个retrofit2.Call接口,OkHttpCall继承的是retrofit2.Call,与okhttp3.call只是引用关系。
这样的设计符合依赖倒置原则,可以尽可能的与OkHttpClient解耦。)

就是通过retrofit2.Call这个接口转换为你在接口中定义的那个Call

那么我们只需要为Retrofit添加对应的扩展

//retrofit对象,这里用使用的是Rxjava里面的Observable
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

返回的Call可以理解成源生的了,默认就这么写。但像很多很多项目都是结合着RXJava来使用这个Retrofit的,那么这个接口返回就会被定义为(伪代码):

Observable<News> news = mApi.getNews("1", "10").subscribeOn(...).observeOn(...);

它返回的是一个Observable类型(观察者模式)。从上面可以看到,Retrofit接口的返回值可以分为两部分,第一部分是返回值类型:Call或者Observable,另一部分是泛型:News
addCallAdapterFactory()影响的就是第一部分:Call或者Observable。Call类型是Retrofit默认支持的(Retrofit内部有一个DefaultCallAdapterFactory),所以你如果不用RXJava + Retrofit结合使用,那就自动忽略掉这个方法,而如果你想要支持RXJava(就是想把返回值定义为Observable对象),就需要我们自己用addCallAdapterFactory()添加:

addCallAdapterFactory(RxJavaCallAdapterFactory.create())  

多种网络请求适配器:Guava、Java8、RXJava ,使用时也需要在Gradle添加依赖:

网络请求适配器Gradle依赖
Guavacom.squareup.retrofit2:adapter-guava:2.0.2
Java8com.squareup.retrofit2:adapter-java8:2.0.2
RXJavacom.squareup.retrofit2:adapter-rxjava:2.0.2

三.retrofit 实现原理
1.Call网络访问对象如何产生

到Retrofit源码里看create函数,是一个动态代理

public <T> T create(final Class<T> service) {
    ...
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            ...
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

这个Call网络工作对象是在InvocationHandler中实现的,也就是在Retrofit.create函数中,由InvocationHandler实现的。

retrofit是如何知道我们调用的接口:

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

ServiceMethod能让我们准确解析到INetApiService中定义的函数,为最后的适配转换提供转换目标

2.适配转换Call对象
我们在初始化Retrofit对象时,好像不添加CallAdapterFactory也能实现适配转换。

我们知道Retrofit使用了建造者模式,建造者模式的特定就是实现了建造和使用的分离,所以建造者模式的建造函数里,一般会有很复杂的对象创建和初始化过程,所以我们要看一下Retrofit的build函数。

public Retrofit build() {
      ...
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();//使用OkHttpClient处理网络请求
      }
      ...
      //根据当前运行平台,设置默认的callAdapterFactory
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
      ...
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

这段代码里,我们看到Retrofit使用OkHttpClient处理网络请求,并且会添加默认的callAdapterFactory,这个platform是一个简单工厂,能根据当前系统平台去生成对应的callAdapterFactory

private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();//根据当前系统平台返回相应的对象
      }
    ...
  }
  ...
  static class Android extends Platform {
    ...
    @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
      if (callbackExecutor == null) throw new AssertionError();
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }
    ...
  }

我们看ExecutorCallAdapterFactory的代码,这是一个工厂类,可以返回CallAdapter对象:

 @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    return new CallAdapter<Object, Call<?>>() {
      ...
      //               转换后              转换前,也就是OkHttpCall
      @Override public Call<Object> adapt(Call<Object> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

RxJava2CallAdapterFactory来看看:

 //RxJava2CallAdapterFactory中
  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
        isSingle, isMaybe, false);
}
  //RxJava2CallAdapter中
  //               转换后        转换前,也就是OkHttpCall
  @Override public Object adapt(Call<R> call) {
   ...
   Observable<?> observable;
   ...
   return observable;
  }

这个CallAdapter的转换就比较明显了,把retrofit2.Call对象通过适配器转换为了一个实为Observable<?>的Object对象。

**

至此,我们可以理解Retrofit根据接口定义动态生产Call网络请求工作对象的原理了,其实就是通过适配器把retrofit2.Call对象转换为目标对象。

**

Retrofit具体是如何知道了INetApiService中定义的Call网络请求对象?

具体过程如下;
首先,根据INetApiService中定义的函数,解析函数,得到函数的具体定义,并生成对应的ServiceMethod。
然后,根据这个ServiceMethod,实现一个OkHttpCall的Call对象,负责在Retrofit底层实现网络访问。
其中,在网络访问返回了网络数据时,根据ServiceMethod实现数据转换。
最后,利用上一小节中匹配的适配器,把OkHttpCall对象转换为INetApiService要求的Call网络请求对象。

  1. 函数解析
    在接口函数里,用注解描述了输入参数,用Java对象定义了返回值类型,所以对输入参数和返回值,ServiceMethod采取了不同的方式去处理。
    输入参数
    输入参数是用来描述url的,它的处理相对简单,ServiceMethod会根据反射得到的Method,取得Annotation注解信息,这些注解是Retrofit自己预定义好的(retrofit2.http.*),ServiceMethod根据预先的定义,直接判断注解所属的逻辑分支,在有网络请求时分情况进行处理,就能得到目标url,http请求头等数据。
    返回值
    返回值是需要用CallAdapter去适配的,所以核心在于生成对应的CallAdapter。
    在Retrofit生成Call网络工作对象时,她通过动态代理获取到了接口函数的Method定义,从这个Method中可以获取函数定义的返回对象类型,由于这个转换是需要CallAdapterFactory生产CallAdapter对象去实现,而Retrofit事先并不知道要使用哪个Factory,所以她是遍历所有的CallAdapterFactory,根据目标函数的返回值类型,让每个Factory都去尝试生产一个CallAdapter,哪个成功就用哪个。

  2. 网络请求
    OkHttpCall继承的retrofit2.Call接口是为了依赖倒置解耦的,真正的网络请求是由OkHttpCall内部引用的okhttp3.call处理的,这个okhttp3.call是
    借道ServiceMethod获取的Retrofit中的callFactory,也就是Retrofit中的OkHttpClient。

整个引用链条是这样的:
OkHttpCall–okhttp3.call
–>
ServiceMethod–callFactory
–>
Retrofit.build()–callFactory//(如未扩展赋值)new OkHttpClient();
–>
Retrofit.Builder().client(mClient)//(可能有扩展赋值)扩展过的OkHttpClient

最终的网络请求是由OkHttpCall调用OkHttpClient发出的,调用和回调等过程,也就是在OkHttpCall中处理的。

网络请求的生成过程中,为了使用接口函数中定义的参数,OkHttpCall会调用ServiceMethod来生成Request请求对象,再交给OkHttpCall去处理。

  1. 数据转换
    因为回调是在OkHttpCall中处理的,所以对回调数据的转换也在OkHttpCall中触发,为了符合接口函数中定义的返回数据类型,OkHttpCall会调用ServiceMethod来转换Response返回数据对象。

OkHttpCall对返回的网络数据,会调用一个serviceMethod.toResponse(ResponseBody body)函数,函数中执行的是:

R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

这个函数可以把原始的okhttp3. ResponseBody数据转换为INetApiService接口中要求的数据类型(如BizEntity类型)。
从代码可以看出,实现数据转换的核心对象其实是responseConverter,这个Converter实际上要依次经过Retrofit的建造和ServiceMethod的建造后,才能确定下来的。

Retrofit建造时添加数据转换工厂
Retrofit里有converterFactries列表,这是在我们初始化Retrofit实例时添加的

//retrofit对象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(Config.DOMAIN)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(YourConverterFactory.create())//添加自定义Converter
.build();

ServiceMethod建造时设定数据转换器
ServiceMethod在建造时,就已经确定了对应的是INetApiService中的哪个函数,所以需要明确设定自己的Converter<R,T>转换对象

public ServiceMethod build() {
      ...
      responseConverter = createResponseConverter();
      ...
  }

这需要调用Retrofit

private Converter<ResponseBody, T> createResponseConverter() {
      ...
      retrofit.responseBodyConverter(responseType, annotations);
    }

Retrofit会在自己的转换器工厂列表中遍历每个ConverterFactory,尝试根据ServiceMethod所对应的目标数据类型,找到Converter数据转换类

for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter<ResponseBody, ?> converter =
          converterFactories.get(i).responseBodyConverter(type, annotations, this);
      if (converter != null) {
        //noinspection unchecked
        return (Converter<ResponseBody, T>) converter;
      }
    }

以Gson转换为例,GsonConverterFactory会通过getAdapter来尝试匹配目标数据类型:

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {...}

如果可以匹配,那么前面调用serviceMethod.toResponse(ResponseBody body)函数时,会调用

  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

在调用这段代码时,其实就是调用了Gson中最终执行数据转换的代码:

@Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
总结来说,Retrofit在类的单一职责方面分隔的很好,OkHttpCall类只负责网络交互,凡是需要知道函数定义的,都交给ServiceMethod类去处理,而ServiceMethod类对使用者不公开,因为Retrofit是个外观模式,而所有需要扩展的都在Retrofit的建造者中实现

在这里插入图片描述

参考链接:
从架构角度看Retrofit的作用、原理和启示
你真的会用Retrofit2吗?Retrofit2完全教程
Retrofit分析-漂亮的解耦套路
Retrofit2源码分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值