深入解析开源项目之Retrofit(二)框架篇

珍惜作者劳动成果,如需转载,请注明出处。
http://blog.csdn.net/zhengzechuan91/article/details/50349934

Retrofit(Github地址)是square公司一套开源的http框架,简单易用,并且支持okhttp和RxJava,如果你不想为配置繁杂的http请求而写一套自己的网络请求框架,那么这套优雅的框架你不妨试试。

上篇博客我们介绍了retrofit在项目中的使用,今天我们就来说说这个框架实现的原理。

Retrofit原理

我们先来说说Retrofit运行原理:

  1. 生成RestAdapter实例,然后RestAdapter的内部类RestHandler动态代理我们实际的操作接口RealService;

  2. 当我们调用RealService的方法时,就会由RestHandler.invoke()代理;

  3. 生成RequestBuilder实例,并通过setArguments(Object[])将参数解析到RequestBuilder实例中,如果参数为POJO类型,通过Converter的toBody(Object)将POJO转换为TypedOutput;

  4. 利用我们设置的RequestInterceptor,在发送http请求前拦截RequestBuilder并向其中添加参数,最后转换为Request;

  5. 执行Client.execute(Request),返回Response;

  6. 如果返回Response的状态码为2**,继续处理,否则抛出各种RetrofitError异常;

  7. 需要的返回类型为Response,同步直接返回,异步时包装为ResponseWrapper;

  8. 需要的返回类型如果不为Response,通过设定的Converter的fromBody(TypedInput, Type)将TypedInput转化为POJO,同步直接返回,异步包装为ResponseWrapper。

通过上面我们看到,这个框架将不同的平台抽象为Platform,将不同的请求方式抽象为Request,将不同的返回结果抽象为Response,而Converter提供了Request与POJO的转换和Response与POJO的转换,思路还是非常清晰。

既然我们对框架有了整体的理解,我们看看一些细节,以加深我们对框架的理解。

框架

我们根据上面的思路从平台、请求、拦截、返回四个角度对框架做深入的分析

平台

在平台的选择上是通过抽象类Platform来切换的。

Platform对平台相关的变量都通过方法进行了抽象:

/* Platform.java */

  //默认Converter
  abstract Converter defaultConverter();
  //默认Client
  abstract Client.Provider defaultClient();
  //默认同步http线程池
  abstract Executor defaultHttpExecutor();
  //默认异步http线程池
  abstract Executor defaultCallbackExecutor();
  //默认log
  abstract RestAdapter.Log defaultLog();

而三个内部类Android、AppEngine和Base都实现了以上的抽象方法,分别代表三个不同的平台。而进行选择时,我们也会从前往后以此选取,当前面两个平台都不满足时,则会使用默认的Base。

/* Platform$Android.java */

  private static class Android extends Platform {
    @Override Converter defaultConverter() {
      //默认为Gson解析
      return new GsonConverter(new Gson());
    }

    @Override Client.Provider defaultClient() {
      final Client client;
      if (hasOkHttpOnClasspath()) {
        //使用了okhttp框架,使用OkClient
        client = OkClientInstantiator.instantiate();
      } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
        //android2.3以下,使用ApacheClient
        client = new AndroidApacheClient();
      } else {
        //android2.3及以上,使用HttpURLConnection
        client = new UrlConnectionClient();
      }
      return new Client.Provider() {
        @Override public Client get() {
          return client;
        }
      };
    }

    @Override Executor defaultHttpExecutor() {
      //同步任务添加到线程池
      return Executors.newCachedThreadPool(new ThreadFactory() {
        @Override public Thread newThread(final Runnable r) {
          return new Thread(new Runnable() {
            @Override public void run() {
              Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
              r.run();
            }
          }, RestAdapter.IDLE_THREAD_NAME);
        }
      });
    }

    @Override Executor defaultCallbackExecutor() {
      //异步任务添加到主线程消息队列
      return new MainThreadExecutor();
    }

    @Override RestAdapter.Log defaultLog() {
      return new AndroidLog("Retrofit");
    }
  }

ApacheClient与UrlConnectionClient都直接实现了Client接口,而OkClient继承了UrlConnectionClient。

ApacheClient是对apache的HttpClient的封装,UrlConnectionClient是对java的UrlConnectionClient的封装,而OkClient则是对okhttp框架的OkHttpClient的封装。

我们再看看ApacheClient和UrlConnectionClient请求的代码:

/* ApacheClient.java */

  @Override 
  public Response execute(Request request) throws IOException {
    //向HttpUriRequest中添加Request的uri和head,如果是
    //Request的body不为空,则向HttpUriRequest的Entry中设置
    //Request的body
    HttpUriRequest apacheRequest = createRequest(request);
    //执行http请求
    HttpResponse apacheResponse = execute(client, apacheRequest);
    //解析HttpResponse的head和entry,包装成Response实例
    //如果entry不为空,则Response的body为TypedByteArray
    return parseResponse(request.getUrl(), apacheResponse);
  }
/* UrlConnectionClient.java */

    @Override
    public Response execute(Request request) throws IOException {
        //获取Client实例
        HttpURLConnection connection = openConnection(request);
        //向connection中添加Request的head,如果Request的
        //body不为空,则将body写入connection中
        //如果body的长度不为-1,则使用固定长度流输出
        //如果长度为-1,则等固定大小缓存满后输出一次
        //而默认是在数组中全部缓存输出流,一次输出,可能会oom
        prepareRequest(connection, request);
        //通过connection返回的值构建Response,而Response的
        //body为TypedInputStream
        return readResponse(connection);
    }

看完了上面平台相关的代码,我们一定会有一个疑问:Request的body值什么时候不为空呢?

让我们先来看看RequestBuilder这个类,这个类是对要调用的方法的解析,里面有三个地方对TypedOutput类型的body进行了赋值,分别是方法注解@FormUrlEncoded、@Multipart和参数注解@Body。

请求

RestAdapter adapter =   new RestAdapter.Builder()
        .setClient()
        .setEndpoint()
        .setRequestInterceptor()
        .setConverter()
        .setLogLevel(LogLevel.FULL)
        .build();

RealService real = adapter.create(RealService.class);

当我们在RestAdapter.Builder().build()时,系统会帮我们配置我们没实现的配置,而这些参数都是根据Platform生成的默认值。

而RestAdapter#create(),首先会判断需要代理的必须是接口,并且不能和其他类或接口有继承或实现的关系,否则会抛出IllegalArgumentException异常。然后通过RestAdapter的内部类RestHandler动态代理接口RealService。

接下来的所有操作都是在RestHandler#invoke()中完成。

serviceMethodInfoCache中缓存了每个RealService接口对应的RestMethodInfo队列,每一个Method对应一个RestMethodInfo。

RestAdapter#create()时,默认会创建一个RealService接口对应的队列加入到serviceMethodInfoCache中。

RealService调用getList()方法获取数据时,就会执行到RestHandler#invoke()

        @Override
        public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
            // 不是这个接口定义的方法不代理
            if (method.getDeclaringClass() 
                    == Object.class) {
                return method.invoke(this, args);
            }

            // 这个时候RealService对应的队列
            // methodDetailsCache里面是空的,
            //通过Method新建一个对应的RestMethodInfo
            //里面会有一些Method相关的赋值
            final RestMethodInfo methodInfo = getMethodInfo(methodDetailsCache, method);

            //如果方法返回的是object类型
            if (methodInfo.isSynchronous) {
                try {
                    return invokeRequest(requestInterceptor, methodInfo, args);
                } catch (RetrofitError error) {
                    throw newError;
                }
            }


            // 新建RequestInterceptorTape实例
            final RequestInterceptorTape interceptorTape = new RequestInterceptorTape();

            //方法最后一个参数为Callback,异步
            Callback<?> callback = (Callback<?>) args[args.length - 1];

            //线程池执行CallbackRunnable
            // 1.后台线程通过obtainResponse()获取
            // ResponseWrapper
            // 2.主线程Callback回调返回Response
            httpExecutor.execute(new 
            CallbackRunnable(callback, 
                callbackExecutor) {
                @Override
                public ResponseWrapper obtainResponse() {
                    return (ResponseWrapper) 
                    invokeRequest(interceptorTape, 
                    methodInfo, args);
                }
            });
            return null; 
        }

我们看到,最后都是调用到了RestAdapter#invokeRequest()方法,这个方法封装了拦截、http请求细节,返回了Response。

那我们就重点看看这个方法里的逻辑。

    //新建RestMethodInfo实例时只是通过构造方法设置了几个参数,
    //而这里则是解析这个方法的方法注解和参数参数注解
    methodInfo.init(); 

来看看RestMethodInfo#init()方法的代码:

    synchronized void init() {
        if(!this.loaded) {
            this.parseMethodAnnotations();
            this.parseParameters();
            this.loaded = true;
        }
    }

而RestMethodInfo#parseMethodAnnotations()主要解析了方法里的注解,其中对@POST、@GET注解相对路径的处理我们看看:

    private void parsePath(String path) {
        //如果这个相对路径不是以/开头的,会抛出异常
        if (path == null || path.length() == 0 || path.charAt(0) != '/') {
            throw methodError("URL path \"%s\" must start with '/'.", path);
        }

        //url为相对路径
        String url = path;
        String query = null;
        int question = path.indexOf('?');

        //如果相对路径中带有参数,则检查参数中是否有占位符
        if (question != -1 && question < path.length() - 1) {
            url = path.substring(0, question);
            query = path.substring(question + 1);

            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(query);

            //如果相对路径带的参数中有占位符,抛出异常
            if (queryParamMatcher.find()) {
                throw methodError("URL query string \"%s\" must not have replace block.", query);
            }
        }

        //记录相对路径中的占位符列表
        Set<String> urlParams = parsePathParameters(path);

        //requestUrl保存不带参数的相对路径
        requestUrl = url;
        //requestUrlParamNames保存相对路径中的占位符列表
        requestUrlParamNames = urlParams;
        //requestQuery保存相对路径的参数字符串:
        //' id=1&name="android" '
        requestQuery = query;
    }

而负责解析参数注解的方法RestMethodInfo#parseParameters()则是将该方法的每个参数的名称放在requestParamNames数组中,将每个参数注解的类型放在requestParamUsage数组中。

这个时候这个方法的注解已经解析完毕,然后根据这个RestMethodInfo创建一个RequestBuilder:

//向RequestBuilder拷贝RestMethodInfo的值
//queryParams:' ?id=1&name="android" '
RequestBuilder requestBuilder = new 
RequestBuilder(serverUrl, methodInfo, converter);

//向head、url等注入args参数,args参数中不能有null值,会抛异常
requestBuilder.setArguments(args);

//更新相对路径url
methodInfo.setRelativeUrl(requestBuilder.getRelativeUrl());
methodInfo.setArgs(args);

这个时候,我们已经在RequestBuilder中准备好了request时需要的参数。

拦截

我们可能想在http请求前,再往head中添加一些公共参数,框架给我们提供了RequestInterceptor接口,通过RequestInterceptor#intercept(),我们就可以对参数进行修改。

public interface RequestInterceptor {

    void intercept(RequestInterceptor.RequestFacade var1);

    public interface RequestFacade {
        void addHeader(String var1, String var2);

        void addPathParam(String var1, String var2);

        void addEncodedPathParam(String var1, 
           String var2);

        void addQueryParam(String var1, String var2);

        void addEncodedQueryParam(String var1, 
           String var2);

        RestMethodInfo getMethodInfo();
    }
}

我们再来看看实际代码中是怎么拦截的:

//异步
if (requestInterceptor instanceof
        RequestInterceptorTape) {

    RequestInterceptorTape interceptorTape = 
      (RequestInterceptorTape) requestInterceptor;

    interceptorTape.setMethodInfo(methodInfo);

    //从自定义拦截器向interceptorTape中添加参数
    //实际调用RequestInterceptorTape#add*()向tape中添加
    //不同Command实例
    RestAdapter.this.requestInterceptor
        .intercept(interceptorTape);

    //从interceptorTape向requestBuilder中添加参数
    //实际调用RequestInterceptorTape#intercept()
    //遍历tape根据不同Command挨个向requestBuilder添加参数
    interceptorTape.intercept(requestBuilder);
} else { //同步

    //从自定义拦截器向requestBuilder中添加参数
    requestInterceptor.intercept(requestBuilder);
}

添加完了参数,那我们拦截的任务也就完成了。

返回

拦截完了后,通过RequestBuilder#build()就将url的根路径和相对路径拼装在了一起,并将RequestBuilder转化成了Request。

Request request = requestBuilder.build();

根据现在的平台去执行请求,然后返回Response。

Response response = clientProvider.get().execute(request);

类型为方法的返回类型,这个是API返回数据将TypedInput转换为POJO时Converter#fromBody(TypedInput, Type)的第二个参数,表示我们方法返回的类型。

Type type = methodInfo.responseObjectType;

如果返回值的状态码不为2XX,则会抛出RetrofitError异常。

如果返回的状态码为2XX,则说明请求API成功。

如果返回的是Response,则直接返回

if (type.equals(Response.class)) {
    response = Utils.readBodyToBytesIfNecessary(response);

    if (methodInfo.isSynchronous) {
        return response;
    }
    return new ResponseWrapper(response, response);
}

而如果返回的类型不是Response,则需要我们去转换

TypedInput body = response.getBody();
if (body == null) {
    return new ResponseWrapper(response, null);
}

ExceptionCatchingTypedInput wrapped = new ExceptionCatchingTypedInput(body);
try {
    //通过转换器将输入流wrapped转换成返回类型type
    Object convert = converter.fromBody(wrapped, type);
    if (methodInfo.isSynchronous) {
        return convert;
    }
    return new ResponseWrapper(response, convert);
} catch (ConversionException e) {
}

返回Response后,同步的话,会返回异步线程,通知UI线程数据更新;而如果是异步,则会通知到Callback,直接在UI线程中更新数据即可。

我们再总结下使用时需要注意的地方:
1. 异步最后一个参数为Callback,返回类型为void;同步参数中没有Callback,返回类型为Object;
2. 只有被方法注解@FormUrlEncoded、@Multipart和参数注解@Body注解时,参数中才能有POJO参数;
3. request的POJO是通过Converter#toBody(Object)转换为TypedOutput的;
4. response是通过Converter#fromBody(TypedInput, Type)将TypedInput转化为POJO的;
5. @GET或@POST的相对路径必须是以/开头的,否则会抛异常;
6. 我们定义的请求API的接口的参数不能有null值,否则会抛异常。

好了,Retrofit框架相关的分析到此结束。

你可能感兴趣的:(github,开源项目,Square,retrofit,Api框架)