源码解析Retrofit

Retrofit是针对Android网络请求的框架,底层是基于OkHttp实现的,更多使用运行时注解的方式提供功能。

Retrofit基本用法

首先需要配置build.gradle

    implementation 'com.squareup.retrofit2:retrofit:2.1.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
Retrofit的分类

主要分为三类,请求方法注解,标记类注解,参数类注解。

  • 请求方法注解: GET,POST,PUT,DELETE,HEAD,PATCH,OPTIONS和HTTP。
  • 标记类注解有三种:FormUrlEncode,Multipart,Streaming。
  • 参数类注解有Header,Body,Path,Field等
GET请求访问网络

首先编写请求网络接口:

public interface IpService {
    @Headers({
            "Accept-Encoding: application/json",
            "User-Agent: RetrofitTest"
    })
    @GET("getIpInfo.php?ip=59.108.54.37")
    Call<IpModel> getIpMsg();
}

注意要在请求头中设置Accept-Encoding: application/json,在接口中定义了访问地址,getIpMsg方法,这个方法返回的Call<IpModel>类型的参数。
然后创建Retrofit,并创建接口:

        String url = "http://ip.taobao.com/service/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class);
        Call<IpModel>call = ipService.getIpMsg();

用创建者模式创建Retrofit,然后url是通过拼接baseUrl和接口中的url得到请求URL,接下来用Retrofit动态代理的方式获得接口,然后调用接口的getIpMsg方法得到call对象,然后处理回调。

    call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d(TAG, "onResponse: country get");
                Toast.makeText(getApplicationContext(),country,Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {
                Log.d(TAG, "onFailure: "+t);
            }
    });
动态配置URL地址

@GET注解中有包含了{path},对应@Path注解中String path,如下:

public interface IpServiceForPath {
    @Headers({
            "Accept-Encoding: application/json",
            "User-Agent: RetrofitTest"
    })
    @GET("{path}/getIpInfo.php?ip=59.108.54.37")
    Call<IpModel> getIpMsg(@Path("path") String path);
}

这样就可以在getIpMsg方法中动态指定路径了,比如:

Call<IpModel>call = ipServiceForPath.getIpMsg("service");
动态指定查询条件@Query

使用@Query来动态指定ip的值,接口如下:

public interface IpServiceForQuery{
    @GET("getIpInfo.php")
    Call<IpModel> getIpMsg(@Query("ip")String ip);
}

也可以将参数集成为一个Map统一传递,如图:

public interface IpServiceForQueryMap {
    @GET("getIpInfo.php")
    Call<IpModel> getIpMsg(@QueryMap Map<String, String> options);
}
POST请求参数
public interface IpServiceForPost {
    @FormUrlEncoded
    @POST("getIpInfo.php")
    Call<IpModel> getIpMsg(@Field("ip") String first);
}

首先使用@FormUrlEncode注解来表明这是一个表单请求,然后在getIpMsg方法中使用@Field注解来标示所对应的String类型数据的键,从而组成键值对来进行传递。

也可以用POST方式将JSON字符串作为请求体发送到服务器,使用@Body即可,Retroit会将Ip对象转换为字符串。

Retrofit的创建过程

Retrofit是根据建造者模式构建出来的,Buider方法调用了Platform.get(),根据不同的平台调用不同的线程池,接下来看build方法,baseUrl是必须指定的,callFactory默认是this.callFactory。this.callFactory就是我们在构建Retrofit时调用callFactory方法所传进来的。

接下来创建Retrofit实例并调用如下代码生成接口的动态代理对象:

IpService ipService = retrofit.create(IpService.class);

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

返回了一个动态代理对象,当我们调用IpService的getIpMsg方法时,最终会调用InvocationHandler的invoke方法,有三个参数,第一个参数是代理对象,第二个参数是调用的方法,第三个是方法的参数。然后看loadServiceMethod做了什么。

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

这里会判断serviceMethodCache缓存中是否有方法,如果有方法,那么得到该方法。如果没有方法,那么就创建一个,并加入serviceMethodCache缓存起来。然后看ServiceMethod是如何创建的,片段如下:

callAdapter = createCallAdapter();

首先调用了createCallAdapter方法,最终会得到构建Retrofit调用build方法时adapterFactories添加的对象的get方法。build方法中adapterFactories列表会默认添加defaultCallAdapterFactory,defaultCallAdapterFactory指的是ExecutorCallAdapterFactory,ExecutorCallAdapterFactory的get方法,如下:

 public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public <R> Call<R> adapt(Call<R> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

CallAdapter的responseType得到的是返回数据的真实类型,比如传入的是Call<IpModel>,返回的类型就是IpModel,adapt方法会创建ExecutorCallbackCall,它会将call的回调转发至UI线程。因此在:

responseType = callAdapter.responseType();

返回真实的数据类型。然后调用

responseConverter = createResponseConverter();

方法来遍历convertFactories列表中存储的Convert.Factory.并返回一个合适的Convert来转换对象。此前我们调用了
addConverterFactory将GsonConverterFactory添加到convertFactories列表中,表示返回的数据支持JSON对象。然后

  for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

遍历parseMethodAnnotation方法来对请求方式和请求地址进行解析。在

Annotation[] parameterAnnotations = parameterAnnotationsArray[p];

对方法中的参数注解进行解析。

然后看Retrofit的create方法,在调用了loadServiceMethod方法后会创建OkHttpCall,在构造方法中进行了赋值操作,紧接着调用serviceMethod.callAdapter.adapt(okHttpCall),adapt方法会创建ExecutorCallbackCall并传入OkHttpCall,ExecutorCallbackCall是对Call的封装,通过callbackExecutor将请求回调到UI线程,我们在Call中调用的enqueue方法最终调用的是delegate,delegate是传入的OkHttp。

call的enqueue方法
 call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }

首先调用了call的enqueue方法,然后调用parseResponse方法

response = parseResponse(rawResponse);

然后跟踪parseResponse方法:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();

    // Remove the body's source (the only stateful object) so we can pass the response along.
    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

    int code = rawResponse.code();
    if (code < 200 || code >= 300) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
    }

    if (code == 204 || code == 205) {
      return Response.success(null, rawResponse);
    }

    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(catchingBody);//1
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throw e;
    }
  }

主要就是根据不同的状态码来做不同的操作。如果请求成功那么会调用注释1中的代码。调用toResponse方法,如下:

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

这里的responseConverter就是createResponseConverter方法中返回的converter,之前传入的是GsonConvertFactory,跟踪GsonConvertFactory的代码:

  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }

可以看见,最终会返回一个GsonResponseBodyConverter,跟踪GsonResponseBodyConverter

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }

在这里利用jsonReader将回调数据转换为JSON格式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值