死磕Android_Retrofit 原理解析

Retrofit,一個遠近聞名的網絡框架,它是由Square公司開源的.Square公司,是咱們的老熟人了,不少框架都是他開源的,好比OkHttp,picasso,leakcanary等等.他們公司的不少開源庫,幾乎已經成爲如今開發Android APP的標配.java

簡單來講,Retrofit實際上是底層仍是用的OkHttp來進行網絡請求的,只不過他包裝了一下,使得開發者在使用訪問網絡的時候更加方便簡單高效.android

一句話總結:Retrofit將接口動態生成實現類,該接口定義了請求方式+路徑+參數等註解,Retrofit能夠方便得從註解中獲取這些參數,而後拼接起來,經過OkHttp訪問網絡.json

1. 基本使用

很簡單,我就是簡單請求一個GET接口,拿點json數據,解析成對象實例.數組

首先,咱們須要定義一個interface.這個interface是網絡請求的API接口,裏面定義了請求方式,入參,數據,返回值等數據.緩存

我這裏使用的接口是鴻神的wanandroid網站的開放接口,地址: www.wanandroid.com/blog/show/2 . 我使用的是https://wanandroid.com/wxarticle/chapters/json.bash

interface TodoService {

     @GET("wxarticle/list/{id}/{page}/json")
    fun getAirticles(@Path("id") id: String, @Path("page") page: String): Call<BaseData> } 複製代碼

API接口定義好了以後,咱們須要構建一個Retrofit實例.網絡

private val mRetrofit by lazy {
        Retrofit.Builder()
            .baseUrl("https://wanandroid.com/")
            .addConverterFactory(GsonConverterFactory.create())   //數據解析器
            .build()
    }
複製代碼

有了Retrofit實例以後,咱們須要讓Retrofit幫咱們把上面定義的API接口轉換成實例,而後咱們就能夠直接把它當作實例來調用了app

//用接口生成實例
val iArticleApi = mRetrofit.create(IArticleApi::class.java) //調用方法 返回Call對象 val airticlesCall = iArticleApi.getAirticles("408", "1")
複製代碼

調用以後會返回一個Call對象,咱們能夠拿着這個Call對象去訪問網絡了框架

//異步請求方式
airticlesCall.enqueue(object : Callback<BaseData> {
    override fun onFailure(call: Call<BaseData>, t: Throwable) {
        //請求失敗
        t.printStackTrace()
        Log.e("xfhy", "請求失敗")
    }

    override fun onResponse(call: Call<BaseData>, response: Response<BaseData>) {
        //請求成功
        val baseData = response.body()
        Log.e("xfhy", "請求成功 ${baseData?.toString()}")
    }
})
複製代碼

拿着這個Call對象調用enqueue方法便可異步訪問網絡,獲取結果,很是簡單.異步

2. 構建Retrofit

ps:這裏插播一個小技巧,當咱們構建一個對象須要傳入不少不少必要的參數才能構建起來的時候,咱們須要使用Builder模式(構造器模式).若是不太瞭解的同窗,看這裏

Retrofit源碼中使用了不少Builder模式,像好比接下來要講的Retrofit 構建,咱們看一下它的構建

Retrofit.Builder()
        .baseUrl("https://wanandroid.com/")
        .addConverterFactory(GsonConverterFactory.create())   //數據解析器
        .build()
複製代碼

Builder()方法內部就不細看了,裏面就是獲取一下當前是什麼平臺(Android,Java).咱們來看一下baseUrl方法.

Retrofit#Builder#baseUrl
public Builder baseUrl(String baseUrl) {
  return baseUrl(HttpUrl.get(baseUrl));
}
複製代碼

經過HttpUrl的靜態get方法構建了一個HttpUrl,傳入的是一個baseUrl,HttpUrl裏面主要是根據baseUrl獲取scheme,host,port,url等等信息的.

而後是addConverterFactory方法,添加解析器

private final List<Converter.Factory> converterFactories = new ArrayList<>();
public Builder addConverterFactory(Converter.Factory factory) {
  converterFactories.add(checkNotNull(factory, "factory == null"));
  return this;
}
複製代碼

解析器是能夠有多個的.

最後就是Retrofit.Builder的build方法了

public Retrofit build() {
  //就是一些各類判空的邏輯
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  //添加適配器,爲了支持除了Call對象之外的返回類型
  List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
  callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

  // Make a defensive copy of the converters.
  //轉換器,用於序列化 和 反序列化
  List<Converter.Factory> converterFactories = new ArrayList<>(
      1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

  // Add the built-in converter factory first. This prevents overriding its behavior but also
  // ensures correct behavior when using converters that consume all types.
  converterFactories.add(new BuiltInConverters());
  converterFactories.addAll(this.converterFactories);
  converterFactories.addAll(platform.defaultConverterFactories());

  return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
      unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
複製代碼

能夠看到,就是將前面的一些參數(baseUrl,轉換器,適配器等)什麼的都配置到Retrofit對象裏面.

3. 獲取網絡請求參數

接下來的就比較帶感了,Retrofit實際上是經過咱們定義的API interface來獲取網絡請求的入參的.Retrofit爲何能將接口轉換成實現類,讓咱們調用呢?下面來看源碼

3.1 構建interface實例

在上面的示例中mRetrofit.create(IArticleApi::class.java),這一句代碼將接口轉換成了實現類,咱們進去看看

public <T> T create(final Class<T> service) {
    //這裏傳入的必須是接口
    Utils.validateServiceInterface(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, @Nullable Object[] args) throws Throwable {
            ....
            //讀取method中的全部數據 將是網絡請求的全部入參
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            //各類註解啊,數據啊都傳過去 args是方法的參數數據
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }
複製代碼

經過動態代理的方式,獲取其執行時的方法上的註解+形參等數據,並保存於serviceMethod對象中.serviceMethod和args(形參的值)全都存入OkHttpCall中,先在這裏,稍後使用.如今咱們來看一下,如何獲取到method裏面的數據

3.2 ServiceMethod 獲取入參

咱們從上面的loadServiceMethod方法進入

//緩存Method與ServiceMethod,每次根據Method去讀取數據比較麻煩,緩存起來,下次進入直接返回,很是高效
private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();

ServiceMethod<?, ?> loadServiceMethod(Method method) {
    //有緩存用緩存
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        //將method傳入,而後去讀取它的數據
        result = new ServiceMethod.Builder<>(this, method).build();
        //將serviceMethod存入緩存
        serviceMethodCache.put(method, result);
      }
    }
    return result;
}
複製代碼

loadServiceMethod主要是能夠看到緩存Method與ServiceMethod,每次根據Method去讀取數據比較麻煩,緩存起來,下次進入直接返回,很是高效.咱們去看看它內部讀取數據的部分

ServiceMethod#Builder()
//又來了,這裏又是Builder模式
Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit;
  this.method = method;
  //接口方法的註解  好比GET,PUT,POST等
  this.methodAnnotations = method.getAnnotations();
  //參數類型
  this.parameterTypes = method.getGenericParameterTypes();
  //參數註解數組  好比Query
  this.parameterAnnotationsArray = method.getParameterAnnotations();
}
複製代碼

ServiceMethod#Builder()裏面將Retrofit實例+method+接口方法的註解+參數類型+參數的註解存入ServiceMethod中,待會兒會用到.接下來就是ServiceMethod#Builder的build方法了

public ServiceMethod build() {
      //1. 獲取傳入的適配器 若是沒有傳入則使用默認的ExecutorCallAdapterFactory
      callAdapter = createCallAdapter();
      //2. 獲取接口的返回值類型
      responseType = callAdapter.responseType();
      //3. 獲取轉換器 我傳入的是GsonResponseBodyConverter
      responseConverter = createResponseConverter();
    
      //循環接口的方法上的註解 好比我上面示例使用的是GET
      for (Annotation annotation : methodAnnotations) {
        //解析這個方法上的註解是啥 
        parseMethodAnnotation(annotation);
      }

      //參數上的註解
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        //參數類型
        Type parameterType = parameterTypes[p];
        
        //參數的註解
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      return new ServiceMethod<>(this);
    }



複製代碼

這個方法搞的操做比較多,1,2,3點都是和結果有關的,暫時先不看.而後就是解析接口方法上面的註解,經過parseMethodAnnotation方法.

//解析這個方法上的註解是表示的哪一種HTTP請求 
private void parseMethodAnnotation(Annotation annotation) {
  if (annotation instanceof DELETE) {
    parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
  } else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  } else if (annotation instanceof HEAD) {
    parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
    if (!Void.class.equals(responseType)) {
      throw methodError("HEAD method must use Void as response type.");
    }
  } else if (annotation instanceof PATCH) {
    parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
  } else if (annotation instanceof POST) {
    parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
  }
  //後面也是這個邏輯
  .......
 
}
複製代碼

parseMethodAnnotation方法首先是判斷是哪一種HTTP請求的註解,而後經過parseHttpMethodAndPath方法去分析

//獲取註解上面的值
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
  
  this.httpMethod = httpMethod;
  this.hasBody = hasBody;

  //獲取方法註解裏面的值 好比上面的示例是wxarticle/list/{id}/{page}/json
  this.relativeUrl = value;
  //把那種須要替換值的地方找出來 上面的示例獲取出來的結果是id和page
  this.relativeUrlParamNames = parsePathParameters(value);
}
複製代碼

parseHttpMethodAndPath方法分析獲取的是 http請求方式+省略域名的url+須要替換路徑中值的地方.

咱們繼續看ServiceMethod的Builder的build方法.解析好了方法的註解以後,就開始解析參數的註解了.參數上的註解多是一個數組,由於可能不止一個註解.

而後遍歷參數的類型

Type parameterType = parameterTypes[p];
複製代碼

獲取參數的註解+參數類型,一塊兒傳入parseParameter方法進行解析

//參數的註解
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
複製代碼
private ParameterHandler<?> parseParameter(
    int p, Type parameterType, Annotation[] annotations) {
    
  ParameterHandler<?> result = null;
  for (Annotation annotation : annotations) {
    ParameterHandler<?> annotationAction = parseParameterAnnotation(
        p, parameterType, annotations, annotation);
    result = annotationAction;
  }

  return result;
}
複製代碼

parseParameter裏面主要就是調用parseParameterAnnotation生成ParameterHandler

private ParameterHandler<?> parseParameterAnnotation(
        int p, Type type, Annotation[] annotations, Annotation annotation) {
      if (annotation instanceof Url) {

        gotUrl = true;

        if (type == HttpUrl.class
            || type == String.class
            || type == URI.class
            || (type instanceof Class && "android.net.Uri".equals(((Class<?>) type).getName()))) {
          return new ParameterHandler.RelativeUrl();
        } else {
          throw parameterError(p,
              "@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
        }

      } else if (annotation instanceof Path) {
        gotPath = true;

        Path path = (Path) annotation;
        String name = path.value();
        validatePathName(p, name);

        Converter<?, String> converter = retrofit.stringConverter(type, annotations);
        return new ParameterHandler.Path<>(name, converter, path.encoded());

      } else if (annotation instanceof Query) {
        Query query = (Query) annotation;
        String name = query.value();
        boolean encoded = query.encoded();

        Class<?> rawParameterType = Utils.getRawType(type);
        gotQuery = true;
        if (Iterable.class.isAssignableFrom(rawParameterType)) {
          if (!(type instanceof ParameterizedType)) {
            throw parameterError(p, rawParameterType.getSimpleName()
                + " must include generic type (e.g., "
                + rawParameterType.getSimpleName()
                + "<String>)");
          }
          ParameterizedType parameterizedType = (ParameterizedType) type;
          Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
          Converter<?, String> converter =
              retrofit.stringConverter(iterableType, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded).iterable();
        } 
      
      ......
      //後面的邏輯都差很少的,感興趣能夠閱讀源碼進行查看

      return null; // Not a Retrofit annotation.
    }

複製代碼

解析參數上的註解,這個註解可能的類型比較多,好比Path或者Query等等.因此parseParameterAnnotation方法裏面有不少if..else..,我只列舉了其中幾種,其餘的邏輯都差很少的,感興趣能夠閱讀源碼進行查看.

好比,咱們就只分析一下Path的

gotPath = true;
Path path = (Path) annotation;
//獲取註解裏面value的值
String name = path.value();

Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Path<>(name, converter, path.encoded());
複製代碼

若是是Path則將獲取到的數據放到了ParameterHandler.Path中,若是是Query則將數據放到ParameterHandler.Query中.每一個註解都有一個屬於本身的類型.

而後ServiceMethod剩下的build方法就是new ServiceMethod<>(this)了,就是將上面這些獲取到的全部數據所有存進去.

而後咱們回到動態代碼的那個方法,我已經放到下面來了.

ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
複製代碼

OkHttpCall是Retrofit中的一個類,最後咱們將ServiceMethod和args(形參數據)都放進了OkHttpCall對象中.

serviceMethod.adapt最終返回的是將serviceMethod和okHttpCall綁在了一塊兒,

T adapt(Call<R> call) {
    return callAdapter.adapt(call);
}
複製代碼

我初始化Retrofit時沒有傳addCallAdapterFactory(CallAdapterFactory),因此這裏是默認的ExecutorCallAdapterFactory,而後ExecutorCallAdapterFactory的adapt方法是就是返回了一個ExecutorCallbackCall對象

@Override public Call<Object> adapt(Call<Object> call) {
    return new ExecutorCallbackCall<>(callbackExecutor, call);
  }
複製代碼

到這裏,網絡請求的入參已經基本解析完了,其實還差一點點,下面會說到.把這些獲取到的入參所有封裝了起來

4. 請求網絡

咱們的示例是從下面這段代碼進行網絡請求的

airticlesCall.enqueue(object : Callback<BaseData> {
    override fun onFailure(call: Call<BaseData>, t: Throwable) {
        t.printStackTrace()
        Log.e("xfhy", "請求失敗")
    }

    override fun onResponse(call: Call<BaseData>, response: Response<BaseData>) {
        val body = response.body()
        Log.e("xfhy", "請求成功 ${body?.toString()}")
    }
})
複製代碼

進行Call的enqueue方法,這裏的Call實際上是ExecutorCallbackCall對象,由於在上面的動態代理中返回了這個對象的實例,因此就是調用的ExecutorCallbackCall的enqueue方法

ExecutorCallbackCall#enqueue
@Override public void enqueue(final Callback<T> callback) {

  //這裏的delegate是以前傳入的OkHttpCall對象
  delegate.enqueue(new Callback<T>() {
    @Override public void onResponse(Call<T> call, final Response<T> response) {
      callbackExecutor.execute(new Runnable() {
        @Override public void run() {
          if (delegate.isCanceled()) {
            // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
          } else {
            callback.onResponse(ExecutorCallbackCall.this, response);
          }
        }
      });
    }

    @Override public void onFailure(Call<T> call, final Throwable t) {
      callbackExecutor.execute(new Runnable() {
        @Override public void run() {
          callback.onFailure(ExecutorCallbackCall.this, t);
        }
      });
    }
  });
}
複製代碼

ExecutorCallbackCall的enqueue方法中調用了以前傳入的OkHttpCall的enqueue方法,代理

@Override public void enqueue(final Callback<T> callback) {
    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          //在開始以前,須要構建okhttp3.Call對象
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

    if (failure != null) {
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
      call.cancel();
    }

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

        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {
        callFailure(e);
      }

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }
複製代碼

上面一開始就須要構建OKHttp3的Call對象,由於最後仍是須要用OkHttp來訪問網絡

private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = serviceMethod.toCall(args);
    return call;
}
okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);
    
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    //這是以前建立的ParameterHandler 數組 裏面裝的是方法參數的註解value值
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    
    //由於上面示例的方法參數註解爲Path,因此apply方法就是將url中的須要替換的id和page替換成真實的數據
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    
    return callFactory.newCall(requestBuilder.build());
}
複製代碼

在構建OkHttp的Call以前,須要將url啊那些東西所有搞好,好比示例中的參數註解是Path,那麼就須要先將url中的id和page換成真實的數據放在那裏.而後

//RequestBuilder#build
Request build() {
    HttpUrl url;
    HttpUrl.Builder urlBuilder = this.urlBuilder;
    if (urlBuilder != null) {
      url = urlBuilder.build();
    } else {
      // No query parameters triggered builder creation, just combine the relative URL and base URL.
      //noinspection ConstantConditions Non-null if urlBuilder is null.
      url = baseUrl.resolve(relativeUrl);
    }

    RequestBody body = this.body;
    if (body == null) {
      // Try to pull from one of the builders.
      if (formBuilder != null) {
        body = formBuilder.build();
      } else if (multipartBuilder != null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        // Body is absent, make an empty body.
        body = RequestBody.create(null, new byte[0]);
      }
    }

    MediaType contentType = this.contentType;
    if (contentType != null) {
      if (body != null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        requestBuilder.addHeader("Content-Type", contentType.toString());
      }
    }

    //requestBuilder是Request.Builder對象,在構造方法裏面就初始化好了的
    //這裏就是正常的OkHttp的網絡請求該乾的事兒了 封裝url,method,而後Request構建出來
    return requestBuilder
        .url(url)
        .method(method, body)
        .build();
  }

複製代碼

到了這裏,就是把以前獲取的數據傳入Request對象中,進行正常的OkHttp網絡請求,構建一個Request對象.

而後經過這個Request對象建立Call對象,回到上面的OkHttpCall中的方法,只展現了剩下的邏輯

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

        try {
          //這裏是咱們示例中傳過來的那個CallBack對象,網絡請求成功
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {
        callFailure(e);
      }

      private void callFailure(Throwable e) {
        try {
          //網絡請求失敗
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }
複製代碼

好了,到這裏,網絡請求算是完成了.若是對OkHttp訪問網絡有興趣的請看文章

5. 總結

Retrofit主要是利用動態代理模式來實現了接口方法,根據這個方法獲取了網絡訪問請求全部的入參,而後再將入參組裝配置OkHttp的請求方式,最終實現利用OkHttp來請求網絡.方便開發者使用.代碼封裝得及其好,厲害.

相關文章
相關標籤/搜索