Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

青蛙要fly發表於2018-12-24

前言:

哈哈,其實寫的還是很水,各位原諒我O(∩_∩)O。

介於自己的網路方面知識爛的一塌糊塗,所以準備寫相關網路的文章,但是考慮全部寫在一篇太長了,所以分開寫,希望大家能仔細看,最好可以指出我的錯誤,讓我也能糾正。

1.講解相關的整個網路體系結構:

Android技能樹 — 網路小結(1)之網路體系結構

2.講解相關網路的重要知識點,比如很多人都聽過相關網路方面的名詞,但是僅限於聽過而已,什麼tcp ,udp ,socket ,websocket, http ,https ,然後webservice是啥,跟websocket很像,socket和websocket啥關係長的也很像,session,token,cookie又是啥。

Android技能樹 — 網路小結(2)之TCP/UDP

Android技能樹 — 網路小結(3)之HTTP/HTTPS

Android技能樹 — 網路小結(4)之socket/websocket/webservice

相關網路知識點小結- cookie/session/token(待寫)

3.相關的第三方框架的原始碼解析,畢竟現在面試個大點的公司,okhttp和retrofit原始碼是必問的。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析


正文

因為我平時使用的都是Rxjava2 + Retrofit ,所以我相關的原始碼解析都是配合RxJava來的,而不是Call返回物件。

讀本文的我推薦大家最好對OKHttp原始碼有所瞭解,再來看本文,因為Retrofit內部還是通過OkHttp發出網路請求。大家也可以看我前面寫的:Android技能樹 — 網路小結之 OkHttp超超超超超超超詳細解析, 同時本文不會再去教大家Retrofit的基礎使用,如果要看一些簡單使用,可以看下面的一些推薦部落格:

Android Retrofit 2.0 的詳細 使用攻略(含例項講解)

Android:Retrofit 結合 RxJava的優雅使用(含例項教程)

我們先上一張別的大佬部落格中的一張圖:

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

這個圖畫的很好,但是這個圖更多的是從大局觀來看,所以如果對於原始碼不是有一些基礎瞭解的話,看這個圖很容易就忘記。

看過我的Okhttp原始碼分析的文章:Android技能樹 — 網路小結之 OkHttp超超超超超超超詳細解析,我們文中的Okhttp流程圖就是跟著原始碼一步步來畫的。我更喜歡是跟著原始碼一步步來畫流程圖(PS:其實是我水平太差了,無法一下子總結處第三方庫的各種設計模式的使用),所以Retrofit我也畫了下面這個圖:

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

而等會我們分析完這個跟著原始碼分析的流程圖後,再回頭看上面的別人部落格中的總結的Retrofit結構圖,就會很簡單了。

首先我們來確定總體大綱:

我們知道我們的目標是要發起一次網路請求,他有這麼幾步:

  1. 告訴它一些基本資訊,比如url地址,網路請求方式(get、post、...等),請求引數值。然後拼裝成一個標準的網路Request請求的格式發出去。所以這裡有二步動作:1.先解析我們寫的引數,2.再解析完後拼裝成標準的網路Request請求格式
  2. 發出請求後,接收到了後臺的Response返回結果,我們要把Resonse轉換成我們想要的返回結果。但是我們寫的想要的返回結果又有二大關鍵地方,我們平常的返回結果可能是X <Y>,我們先來看外面的X的型別,比如我們常見的返回結果是Call<Y> 和 Observable<Y>,所以我們在轉換的時候一是要考慮最外面的那個返回型別的轉換。另外一個是Y的型別,也就是裡面我們具體寫的Bean物件,比如我們直接返回字串,那可能就是Observable<String>,又或者是自己定義的xxxBean物件,那就是Observable<xxxBean>所以我們要有二類轉換:1.外層的結果型別,比如Call或者Observable等,2.是泛型裡面填的具體的Bean物件型別

所以我們總結起來就需要四步:

  1. 解析並拿到我們寫的一些引數(url,請求方式(post/get),請求引數......)
  2. 根據我們寫的引數,拼成一個網路請求Request,去幫我們發起請求。
  3. Response如何轉換成Call或者Observable等返回型別,和第4步中的Bean物件拼成了Call《Bean》或者Observable《Bean》
  4. Response如何轉換成我們所需要的具體的Bean物件。

沒錯,下次別人問你,你就心裡有數了,到底Retrofit做了什麼內容,你就跟別人說很簡單啦,大致做了上面四步,逼格一下子提高了。。


1. 建立Retrofit物件

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

我這裡直接先把建立Retrofit的物件的程式碼寫上:

Retrofit retrofit = new Retrofit.Builder()
    .client(new OkHttpClient())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl("https://xxxx.com/")
    .build();
複製程式碼

不要問建立Retrofit的各自的方法是幹嘛的,我們後面會一步步講解。

2. 如何解析並拿到我們寫的引數

我們知道我們平常是這樣寫的:

我們隨便寫一個常見的獲取某個使用者的個人資訊介面來說明:

InfoApi.java:
interface InfoApi{
    @GET("userinfo.do")
    Observable<UserBean> getInfo(@Query("name") String nameStr);
}
複製程式碼

那我們要拿到:

  1. path值:上面建立Retrofit時候傳入的baseUrl + userinfo.do = "https://xxxx.com/userinfo.do"
  2. 網路請求的方式:GET請求
  3. 傳送的引數query :name=nameStr

最終我們發現是GET請求,所以這麼拼在一起:path + "?" + query = http://xxxx/userinfo.do?name=nameStr

所以我們來看如何一步步拿到相關引數:

我們知道上面寫的InfoApi.java是要被retrofit載入進去的:

retrofit.create(InfoApi.class);
複製程式碼

所以我們要來看create方法的具體操作前,我們先來了解一下基礎知識,那就是代理模式,如果知道代理模式的,直接可以忽略此處,直接往下看。

2.1 create方法:

在看create程式碼之間,我們要先學會代理模式相關知識

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

本來也想一步步長篇大論的寫下,但是後來看到一篇不錯的文章,寫的挺仔細的:java動態代理實現與原理詳細分析 ,希望大家能仔細看完,在看下面的內容。

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

我們點進去檢視具體的程式碼:

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (this.validateEagerly) {
        this.eagerlyValidateMethods(service);
    }

    
    //'使用了代理模式'
    return Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
        private final Platform platform = Platform.get();

        public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
            } else if (this.platform.isDefaultMethod(method)) {
                return this.platform.invokeDefaultMethod(method, service, proxy, args);
            } else {
                
                //'我們可以看到我們寫的介面裡面的的method傳入到了loadServiceMethod方法裡面,從而得到了我們定義在method上面的相關引數資訊。'
                ServiceMethod<Object, Object> serviceMethod = Retrofit.this.loadServiceMethod(method);
                
                //'我們傳入的方法的引數args和上面獲得的ServiceMethod,一起傳入OkHttpCall建構函式中,得到OkHttpCall物件'
                OkHttpCall<Object> okHttpCall = new OkHttpCall(serviceMethod, args);
                
                return serviceMethod.adapt(okHttpCall);
            }
        }
    });
}
複製程式碼

我們可以看到我們呼叫的getInfo這個method方法傳入了:

ServiceMethod<Object, Object> serviceMethod = Retrofit.this.loadServiceMethod(method);
複製程式碼

我們進去檢視:

ServiceMethod<?, ?> loadServiceMethod(Method method) {

//'從快取中去讀'
ServiceMethod<?, ?> result = (ServiceMethod)this.serviceMethodCache.get(method);
    if (result != null) {
        return result;
    } else {
        Map var3 = this.serviceMethodCache;
        synchronized(this.serviceMethodCache) {
            result = (ServiceMethod)this.serviceMethodCache.get(method);
            if (result == null) {
                //'如果快取中沒有,則新建'
                result = (new retrofit2.ServiceMethod.Builder(this, method)).build();
                //'新建完後再放入快取中'
                this.serviceMethodCache.put(method, result);
            }

            return result;
        }
    }
}
複製程式碼

我們可以看到新建的方法:

(new retrofit2.ServiceMethod.Builder(this, method)).build();
複製程式碼

我們來看ServiceMethod類下的Builder的建構函式:

Builder(Retrofit retrofit, Method method) {
    this.retrofit = retrofit;
    this.method = method;
    //'Java特有的方法,可以獲取Java方法上面的註解標識,比如:@POST,@GET'
    this.methodAnnotations = method.getAnnotations();
    //'獲取方法引數裡面定義的引數型別,比如:String,boolean'
    this.parameterTypes = method.getGenericParameterTypes();
    //'獲取方法裡面的註解標識,比如:@Query,@Path'
    this.parameterAnnotationsArray = method.getParameterAnnotations();
}
複製程式碼

是不是一下子就知道了,原來是通過這樣的方式拿到了我們寫在方法上面的一些引數值,如果還不清楚Method的這幾個方法,可以看下面的相關連結:
Java獲取類、方法、屬性上的註解
java.lang.reflect.Method.getGenericParameterTypes()方法示例.
使用反射獲得引數列表裡的註解getParameterAnnotations.

我們建立ServiceMethod因為是使用的Builder模式,所以最終要呼叫build()方法來建立例項:


public ServiceMethod build() {
      //'建立了CallAdapter物件,具體幹嘛用的,具體後面會講解'
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + “ is not a valid response body type. Did you mean ResponseBody?”);
      }
      
      //'建立了ResponseConverter物件,具體後面會講解'
      responseConverter = createResponseConverter();
    
      //'對於我們寫的介面請求方法的方法上面的註解進行相關判斷,'
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      //'因為進行方法上面註解的解析了,所以httpMethod的也就相應的被賦值了,
      如果為空,就說明你寫的請求介面方法沒有寫@GET等,就會丟擲異常'
      if (httpMethod == null) {
        throw methodError(“HTTP method annotation is required (e.g., @GET, @POST, etc.).”);
      }
      
      //'因為上面解析了,所以比如我們發現是@GET請求,這時候hasBody會是false,如果你還用了Multipart註解,就會報錯了,他要求是要有request body的,@GET請求是不能使用Multipart的'
      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              “Multipart can only be specified on HTTP methods with request body (e.g., @POST).”);
        }
        
        //'同上,表單提交是一定要求有request body的'
        if (isFormEncoded) {
          throw methodError(“FormUrlEncoded can only be specified on HTTP methods with 
              + request body (e.g., @POST).”);
        }
      }
        
      
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      
      //'遍歷我們獲取的方法裡面的註解集合,比如@Query,@Path等'
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, “Parameter type must not include a type variable or wildcard: %s”,
              parameterType);
        }

        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
          throw parameterError(p, “No Retrofit annotation found.”);
        }
        
        //'然後對我們寫的方法內部引數註解進行判斷,看寫的是否正確等
        這裡的判斷很長,比如如果你用的是註解@Body,那麼先判斷你是否用了方法上面的@FormEncode註解或者@Multipart註解,
        不然就報錯,然後因為我們填的引數是物件了,所以內部需要通過RequestBodyConverter來進行轉換,把我們傳的物件,變成了RequestBody物件。
        具體很多很多判斷,各種註解的判斷我都不一一講了,大家只要進去看方法詳細程式碼就可以了。'
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      if (relativeUrl == null && !gotUrl) {
        throw methodError(“Missing either @%s URL or @Url parameter.”, httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError(“Non-body HTTP method cannot contain @Body.”);
      }
      if (isFormEncoded && !gotField) {
        throw methodError(“Form-encoded method must contain at least one @Field.”);
      }
      if (isMultipart && !gotPart) {
        throw methodError(“Multipart method must contain at least one @Part.”);
      }

      return new ServiceMethod<>(this);
}
複製程式碼

好,我們已經成功拿到了我們的方法中的紅色框出來的部分,綠色的部分我們還沒有獲取。

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

而代理模式的invoke方法裡面的引數 @Nullable Object[] args,就是我們具體傳入的引數,比如我這麼寫:

getInfo("青蛙要fly");
複製程式碼

args裡面就有了我們傳入的"青蛙要fly"字串。這樣我們是不是就獲取了上面的其中一個綠色框nameStr的內容了。

我們拿到包含了這些紅色框引數的ServiceMethod物件後,加上我們傳入的綠色的框的nameStr的具體的值,我們已經可以進行網路Request請求的所必要的引數了 (另外一個綠色的框只是用來最後網路請求成功後拿到的Response進行轉換,所以這時候不知道都不影響Request請求)

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

我們可以看到我們獲得到的資訊,又用來生成了OkHttpCall物件,然後呼叫了serviceMethod.adapt(okHttpCall);方法。

那我們可以看到create接下去已經沒有其他程式碼了,所以serviceMethod.adapt(okHttpCall);肯定會幫我們用剛才拿到的已知引數,幫我們拼成Request,完成一次網路請求。


3.根據我們寫的引數,拼成Request請求

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

我們上面已經說到了進入了serviceMethod.adapt(okHttpCall);方法了,我們點進去檢視:

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

我們可以看到是呼叫了callAdapter類的adapt方法。那這個callAdapter物件又是什麼呢?

還記不記得我們第一大步:建立Retrofit物件時候的程式碼:

Retrofit retrofit = new Retrofit.Builder()
    .client(new OkHttpClient())
    //'這裡傳入了CallAdapterFactory,而Factory類是用來建立具體的CallAdapter物件的工廠類'
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl("https://xxxx.com/")
    .build();
複製程式碼

所以本文中我們使用的CallAdapter是RxJava2CallAdapterFactory建立的,我們先來看ServiceMethod裡面建立CallAdapter的方法:

private CallAdapter<T, R> createCallAdapter() {

    //'我們上面的介面請求方法綠色框裡面的返回型別還沒有拿到的,終於在這裡拿到了'
    Type returnType = method.getGenericReturnType();
    
    //'如果方法的返回結果包含了泛型表示式、泛型、泛型陣列,就丟擲異常'
    if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(
        “Method return type must not include a type variable or wildcard: %s”, returnType);
    }
    
    //'如果方法的返回結果是void,則丟擲異常'
    if (returnType == void.class) {
        throw methodError(“Service methods cannot return void.”);
    }
    
    //'我們前面提過的,獲取方法上的註解,比如@GET等'
    Annotation[] annotations = method.getAnnotations();
    try {
       
       //'拿著我們的介面請求方法的返回物件及方法上的註解資訊,'
       //'去通過Retrofit類的callAdapter類去生成一個CallAdapter物件'
       return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
       throw methodError(e, “Unable to create call adapter for %s”, returnType);
    }
}
複製程式碼

而Retrofit類中的這個callAdapter方法,我們不看都知道,通過我們前面建立Retrofit物件時候傳入的addCallAdapterFactory的工廠類來建立具體的CallAdapter,當然我們具體還是要具體程式碼一步步來看過程。

我們在呼叫addCallAdapterFactory加入我們的RxJava2CallAdapterFactory.create(),所以先來看下addCallAdapterFactory方法做了什麼:

public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
  callAdapterFactories.add(checkNotNull(factory, "factory == null"));
  return this;
}
複製程式碼

我們可以簡單的看到,就是把我們的Factory工廠類物件加入到private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();這個List佇列中而已。

那這個佇列到底都加了哪些工廠類的,如果我在建立Retrofit物件時候不呼叫addCallAdapterFactory方法,難道這個佇列就是空的????那又怎麼去生成CallAdapter物件?

首先肯定要加入我們自己傳入的Factory,有可能一個,也可能傳入多個:

Retrofit retrofit = new Retrofit.Builder()
    ........
    
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addCallAdapterFactory(xxxxxxCallAdapterFactory.create())
    .addCallAdapterFactory(yyyyyyCallAdapterFactory.create())
    ........
    ........
    .build();

複製程式碼

但是為了防止我們建立Retrofit物件時候不呼叫addCallAdapterFactory傳入自己的Factory,所以本身這個佇列還會加入預設的Factory:

//'看名字就知道,加入平臺的預設的CallAdapterFactory(有java8 和 Android Platform,我們這裡肯定是Android)'
callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
複製程式碼

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

當然這個ExecutorCallAdapterFactory肯定是繼承了CallAdapter.Factory:

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

我們已經知道了我們的CallAdapterFactory佇列裡面包含了哪些工廠類了。接下來我們再來具體的Retrofit的callAdapter的方法:

public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
    //'因為可能有多個CallAdapterFactory工廠類,所以要每個工廠類都去試一下,有一個成功就直接返回了'
    return nextCallAdapter(null, returnType, annotations);
}



public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {
    checkNotNull(returnType, "returnType == null");
    checkNotNull(annotations, "annotations == null");
    
    
    int start = callAdapterFactories.indexOf(skipPast) + 1;
    
    //'迴圈遍歷所有的CallAdapterFactory,然後哪個能成功生成CallAdapter,就直接返回'
    for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }
    
    //'如果所有的CallAdapterFctory都不能使用,就拼接字串,丟擲異常'
    StringBuilder builder = new StringBuilder("Could not locate call adapter for ")
        .append(returnType)
        .append(".\n");
    if (skipPast != null) {
      builder.append("  Skipped:");
      for (int i = 0; i < start; i++) {
        builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
      }
      builder.append('\n');
    }
    builder.append("  Tried:");
    for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
      builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
    }
    throw new IllegalArgumentException(builder.toString());
}
複製程式碼

有可能有人會問,為什麼CallAdapterFactory有可能生成CallAdapter不成功??還要一個個去遍歷?

因為我們同時傳入了我們需要返回的物件的型別傳入到了CallAdapterFactory中,你說如果你是預設的ExecutorCallAdapterFactory工廠類,你卻傳入了Rxjava的返回相關引數,比如我們例子中的Observable<UserBean>,它的程式碼裡面都不認識這種返回型別,怎麼幫你去生成物件,而且程式碼也是加了判斷,如果返回型別不是Call型別,直接就退出了。

@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    //'如果不是Call.class,直接退出生成CallAdapter物件'
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    .......
    .......
}
複製程式碼

所以我們來看下RxJava2CallAdapterFactory裡面怎麼生成相應的CallAdapter的:

@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    Class<?> rawType = getRawType(returnType);
    
    
    
    //'如果我們的返回型別是Completable,就直接返回RxJava2CallAdapter物件,裡面的responseType是void'
    if (rawType == Completable.class) {
      // Completable is not parameterized (which is what the rest of this method deals with) so it
      // can only be created with a single configuration.
      return new RxJava2CallAdapter(Void.class, scheduler, isAsync, false, true, false, false,
          false, true);
    }


    //'判斷是否是Flowable或者Single或者Maybe'
    boolean isFlowable = rawType == Flowable.class;
    boolean isSingle = rawType == Single.class;
    boolean isMaybe = rawType == Maybe.class;
    //'如果既不是上面三種又不是Observable型別,直接返回null'
    if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) {
      return null;
    }


    boolean isResult = false;
    boolean isBody = false;
    Type responseType;
    
    //'如果不是泛型類的,比如Observable<XXXX> ,則拋異常'
    if (!(returnType instanceof ParameterizedType)) {
      String name = isFlowable ? "Flowable"
          : isSingle ? "Single"
          : isMaybe ? "Maybe" : "Observable";
      throw new IllegalStateException(name + " return type must be parameterized"
          + " as " + name + "<Foo> or " + name + "<? extends Foo>");
    }
    
    //'獲取泛型中的具體引數,比如Observable<xxxBean>中的xxxBean的type'
    Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
    //'獲取xxxBean具體的Class物件'
    Class<?> rawObservableType = getRawType(observableType);
    
    //'判斷我們上面獲取的泛型內容(xxxBean)是不是Response'
    if (rawObservableType == Response.class) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException("Response must be parameterized"
            + " as Response<Foo> or Response<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
      
      //'判斷我們上面獲取的泛型內容(xxxBean)是不是Result'
    } else if (rawObservableType == Result.class) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException("Result must be parameterized"
            + " as Result<Foo> or Result<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
      isResult = true;
    } else {
    
      //'我們平常開發泛型裡面填的肯定是自己的Bean物件
      //所以最後走的是這裡的程式碼'
      responseType = observableType;
      //'同時isBody設定為true'
      isBody = true;
    }

    //'生成具體的Rxjava2CallAdapter物件'
    return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
        isSingle, isMaybe, false);
}
複製程式碼

既然我們CallAdapter物件也建立完了,我們回到最剛開始的地方,還記得我們前面分析的程式碼是到了callAdapter.adapt(okHttpCall)(如果忘記的同學,可以重新回頭看下)

所以我們現在已經建立的Rxjava2CallAdapter物件了,我們來看下它的adapt方法:

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

@Override public Object adapt(Call<R> call) {
    /**
    '很多人會說這個isAsync,是否非同步是哪裡設定的,
    其實就是再我們傳入Factory物件時候建立的。
    
    Retrofit retrofit = new Retrofit.Builder()
    .client(new OkHttpClient())
    我們看見建立Factory物件,可以是createAsync()或者create()方法二種來建立,從而決定是同步還是非同步操作
    .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
    或者是
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create()
    .build();'
    */
    
     //'我們可以看到上面根據是否非同步,建立不同的Observable物件,我們用複雜點的來講解吧,
     就當我們建立的時候使用的是RxJava2CallAdapterFactory.createAsync()方法,所以拿到的物件是CallEnqueueObservable'
    Observable<Response<R>> responseObservable = isAsync
        ? new CallEnqueueObservable<>(call)
        : new CallExecuteObservable<>(call);
   

    //'因為我們Observable<xxxBean>裡面包含的是自己Bean,所以建立的時候isBody = true;'
    Observable<?> observable;
    if (isResult) {
      observable = new ResultObservable<>(responseObservable);
    } else if (isBody) {
      //'所以我們的Observable為BodyObservable'
      observable = new BodyObservable<>(responseObservable);
    } else {
      observable = responseObservable;
    }

    if (scheduler != null) {
      observable = observable.subscribeOn(scheduler);
    }

    if (isFlowable) {
      return observable.toFlowable(BackpressureStrategy.LATEST);
    }
    if (isSingle) {
      return observable.singleOrError();
    }
    if (isMaybe) {
      return observable.singleElement();
    }
    if (isCompletable) {
      return observable.ignoreElements();
    }
    
    //'所以最終返回了BodyObservable<CallEnqueueObservable>'
    return observable;
  }
複製程式碼

BodyObservable看名字就知道是一個自定義Observable:

final class BodyObservable<T> extends Observable<T> {
  private final Observable<Response<T>> upstream;

  BodyObservable(Observable<Response<T>> upstream) {
    this.upstream = upstream;
  }

  @Override protected void subscribeActual(Observer<? super T> observer) {
  
    //'當有Observer註冊我們的Observable的時候,
    其實是我們前面的傳入的CallEnqueueObservable去註冊了
    一個BodyObserver<我們自己寫的Observer>'
    upstream.subscribe(new BodyObserver<T>(observer));
  }

}
複製程式碼

所以核心還是我們傳入的CallEnqueueObservable這個Observable,所以最後還是要看這個類的原始碼:


final class CallEnqueueObservable<T> extends Observable<Response<T>> {
  private final Call<T> originalCall;

  CallEnqueueObservable(Call<T> originalCall) {
    this.originalCall = originalCall;
  }

  @Override protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    
    
    Call<T> call = originalCall.clone();
    //'我們可以看到簡歷了一個CallCallback物件,傳入了使用者寫的我們前面建立的OkHttpCall物件和使用者寫的observer物件'
    CallCallback<T> callback = new CallCallback<>(call, observer);
    observer.onSubscribe(callback);
    //'然後呼叫了call的enqueue方法,
    因為是OkHttpCall物件,所以我們直接看OkHttpCall物件的enqueue方法即可'
    call.enqueue(callback);
  }

  private static final class CallCallback<T> implements Disposable, Callback<T> {
   .......
   .......
   .......
  }
}


複製程式碼

OkHttpCall的enqueue方法:

@Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
        
          //'建立Okhttp3的Call物件(畢竟最後發起網路請求是Okhttp,也要使用它的Call物件)'
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

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

    if (canceled) {
      call.cancel();
    }
    
    
    //'常規Okhttp的操作,call.enqueue方法發起非同步請求,估計大家都看得懂,我就不多介紹了,我們直接看拿到返回的資料處理'
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
        try {
        
          //'我們這裡成功的拿到了Okhttp3.Response物件,
          所以使用parseResponse方法將rawResponse物件轉換成Retrofit的Response物件'
          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();
        }
      }
    });
  }
複製程式碼

到這裡,我們已經成功的傳送了網路請求,並且拿到了主句

4. 如何將Resonse轉換成最終我們想要的結果物件

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

我們上面可以看到我們是講OkHttp3.Response物件轉換成了Retrofit.Response物件,我們具體來看下:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {

    //'把Okhttp3.Response中的body部分取出來'
    ResponseBody rawBody = rawResponse.body();

    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

    int code = rawResponse.code();
    
    //'我們就當成功請求回來的,所以code是200'
    
    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) {
      rawBody.close();
      return Response.success(null, rawResponse);
    }
    
    
    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
    
      //'核心程式碼:
      把body部分,通過toResponse方法,變成我們寫入的泛型(也就是Observable<xxxBean>這個xxxBean物件'
      T body = serviceMethod.toResponse(catchingBody);
      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;
    }
}
複製程式碼

所以最終我們發現又回到了serviceMethod裡面了,相關的方法呼叫都在這裡面:

R toResponse(ResponseBody body) throws IOException {
   //'可以看到我們通過responseConverter轉換器來對body部分進行了轉換'
   return responseConverter.convert(body);
}
複製程式碼

這個responseConverter又是怎麼來的呢?我們再回到建立Retrofit物件的地方:

Retrofit retrofit = new Retrofit.Builder()
    .client(new OkHttpClient())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    
    //'看見了ConverterFactory沒有,就是這裡傳入了我們的轉換器物件'
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl("https://xxxx.com/")
    .build();

複製程式碼

我們來看下具體的程式碼:

public final class GsonConverterFactory extends Converter.Factory {
    
    //'可以看到預設內部使用的是GSON來進行轉換'
    public static GsonConverterFactory create() {
        return create(new Gson());
    }
    
    public static GsonConverterFactory create(Gson gson) {
        return new GsonConverterFactory(gson);
    }
    
    private final Gson gson;

    private GsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }
    
    //'這個方法從名字就可以看出來,是用用來給ResponseBody轉換成我們要的物件'
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(final Type type, Annotation[] annotations, Retrofit retrofit) {
        Type newType = new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return new Type[] { type };
            }

            @Override
            public Type getOwnerType() {
                return null;
            }

            @Override
            public Type getRawType() {
                return HttpResult.class;
            }
        };
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(newType));
        
        //'可以看到,用在Response的轉換器是叫GsonResponseBodyConverter物件'
        return new GsonResponseBodyConverter<>(adapter);
    }
    
    //'這個名字也可以看出來是把我們傳入的物件轉換成RequestBody,從而發起請求'
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations,
        Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        
        //'可以看到,用在Request的轉換器是叫GsonRequestBodyConverter物件'
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}
複製程式碼

我們具體來看看GsonResponseBodyConverter:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    //'根據傳入的ResponseBody得到JsonReader'
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      //'很簡單,就是GSON進行相關的JSON解析'
      T result = adapter.read(jsonReader);
      if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
        throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
    } finally {
      value.close();
    }
  }
}
複製程式碼

這裡我們看到了既然解析部分是在這裡,是不是我們可以做很多定製化操作,答案當然是Yes,比如我寫了個自定義的GsonResponseBodyConverter來進行替換(下面的類就隨便寫寫,大家可以根據自己的需求寫自己的自定義轉換器):

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {

    private final TypeAdapter<T> adapter;

    GsonResponseBodyConverter(TypeAdapter<T> adapter) {
        this.adapter = adapter;
    }

    @Override
    public Object convert(ResponseBody value) throws IOException {
        try {
            //'因為我統一的外層物件都是使用的HttpResult,我的程式碼是這麼寫的Observable<HttpResult<xxxBean>>'
            HttpResult apiModel = (HttpResult) adapter.fromJson(value.charStream());
            
            //'直接在這裡就對統一處理操作'
            if (apiModel.getCode().equals(CompanyHttpCode.TOKEN_NOT_EXIST)) {
                throw new TokenNotExistException();
            } else if (apiModel.getCode().equals(CompanyHttpCode.TOKEN_INVALID)) {
                throw new TokenInvalidException();
            } else if (!apiModel.getCode().equals(CompanyHttpCode.SUCCESS_CODE)) {
                // 特定 API 的錯誤,在相應的 Subscriber 的 onError 的方法中進行處理
                throw new ApiException();
            } else if (apiModel.getCode().equals(CompanyHttpCode.SUCCESS_CODE) || apiModel.getCode().equals(CompanyHttpCode.SUCCESS_CODE_STR)) {
                return apiModel;
            }

        } finally {
            value.close();
        }
        return null;
    }
}
複製程式碼

好了,我們已經拿到了相應的Observable<xxxBean>裡面的xxxBean物件了,我們可以看到:

try {
    //'我們前面講過,通過這個方法轉換的parseResponse(rawResponse);
    把OkHttp3.Response轉換成了Retrofit.Response<我們的bean> '
} catch (Throwable e) {
    callFailure(e);
    return;
}

try {
    //'在轉換成功後,我們就把具體的response重新通過回撥函傳回去給CallEnqueueObservable'
    callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
    t.printStackTrace();
}
複製程式碼

CallEnqueueObservable的onResponse方法:

@Override public void onResponse(Call<T> call, Response<T> response) {
      if (disposed) return;

      try {
        
        //'我們可以看到,Observable呼叫了observerde的onNext方法把Retrofit.Reponse物件傳送了出去'
        observer.onNext(response);
        
        ......
        ......
        ......
}
複製程式碼

可能有些人就會奇怪了,我們平常使用,明明拿到的就是具體的裡面的xxxBean物件,而不是Response<xxxBean>,那是因為上面我們提過的,我們的Observer被BodyObserver包了一層:

  private static class BodyObserver<R> implements Observer<Response<R>> {
   
    @Override public void onNext(Response<R> response) {
      if (response.isSuccessful()) {
        
        //'最終到我們的Observer的時候,就是Response裡面包含了的我們寫的xxxBean物件了。'
        observer.onNext(response.body());
      
          
      } else {
        .....
        .....
      }
    }
    
    ......
    ......
  }
複製程式碼

結語:

所以現在我們再來看程式碼,是不是已經就能懂中間到底做了什麼操作。哈哈:


interface InfoApi{
    @GET("userinfo.do")
    Observable<UserBean> getInfo(@Query("name") String nameStr);
}

Retrofit retrofit = new Retrofit.Builder()
    .client(new OkHttpClient())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl("https://xxxx.com/")
    .build();


retrofit.create(InfoApi.class)
    .getInfo("青蛙要fly")
    .subscribe(new ResourceObserver<UserBean>() {
        @Override
        public void onNext(UserBean userBean) {
                        
        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onComplete() {

        }
});

複製程式碼

然後再回頭看這個圖片,是不是也看得懂了:

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析

不知不覺就寫完了,哈哈,可能有些地方不詳細或者是寫的不好又或者是寫錯了。可以留言,我更希望的是能指出我哪裡寫錯了,哈哈,這樣我也可以糾正錯誤的知識。

相關文章