Android小知識-剖析Retrofit中網路請求的兩種方式

公眾號_顧林海發表於2018-11-06

本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,包括年底前會更新kotlin由淺入深系列教程,目前計劃在微信公眾號進行首發,如果大家想獲取最新教程,請關注微信公眾號,謝謝!

在上一節《Android小知識-剖析Retrofit中ServiceMethod相關引數以及建立過程》介紹了動態代理類中三行核心程式碼的第一行,通過loadServiceMethod方法獲取ServiceMethod物件,在loadServiceMethod方法中先會檢查快取集合中是否有對應網路請求介面方法的ServiceMethod物件,如果不存在就通過Builder模式建立,同時介紹了ServiceMethod內部的一些成員變數,其實ServiceMethod就是對網路請求介面內部一個個方法的封裝,通過解析方法內部或方法上的註解來封裝ServiceMethod物件。這節來介紹三行核心程式碼的剩餘兩行。

  public <T> T create(final Class<T> 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 {
            ...
            //核心程式碼1
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            //核心程式碼2
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            //核心程式碼3
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }
複製程式碼

在核心程式碼2處建立OkHttpCall,OkHttpCall是Call的實現類,在Retrofit中內部是通過OkHttp來進行網路的請求,這個OkHttpCall就是對OkHttp請求的封裝。

final class OkHttpCall<T> implements Call<T> {
  ....
  private @Nullable okhttp3.Call rawCall;

  OkHttpCall(ServiceMethod<T, ?> serviceMethod, @Nullable Object[] args) {
    this.serviceMethod = serviceMethod;
    this.args = args;
  }
}
複製程式碼

在OkHttpCall中可以看到rawCall,它是OkHttp的Call,這也驗證之前所說的內部會通過OkHttp來實現網路請求,OkHttpCall建構函式傳入兩個引數,serviceMethod物件和args網路請求引數,接著看核心程式碼3。

   return serviceMethod.adapt(okHttpCall);
複製程式碼

serviceMethod的adapt方法中會呼叫callAdatper的adapter方法,通過介面卡的adapt方法來將OkHttpCall轉換成其他平臺使用的物件,這個callAdapter是在建立serviceMethod時通過構建者模式建立的,它代表網路請求的介面卡,這裡使用的RxJava平臺。

回到一開始的例項程式碼:

    private void initRetrofit() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://icould.glh/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

        NetworkInterface networkInterface = retrofit.create(NetworkInterface.class);

        Map<String, String> params = new HashMap<>();
        params.put("newsId", "1");
        params.put("token", "yud133f");
        Call call = networkInterface.getNewsDetails(params);

        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                System.out.println(response.body());
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                System.out.println("請求錯誤");
            }
        });
    }
複製程式碼

通過networkInterface介面呼叫getNewsDetails是不行的,因此在Retrofit的create獲取網路介面的動態代理,在執行networkInterface的getNewDetails方法時,通過動態代理攔截,並執行動態代理物件內InvocationHandler中的invoke方法,將OkHttpCall轉換成RxJava平臺的適用的Call,而這個OkHttpCall物件是對OkHttp網路庫的封裝,最後返回OkHttpCall型別的Call物件,有了這個Call物件就可以進行同步或非同步請求,OkHttpCall內提供了同步請求方法execute和非同步請求方法enqueue,接著下來重點分析這兩個方法。

在Retrofit同步請求流程中,首先需要對網路請求介面中方法以及引數進行解析,通過ParameterHandler進行解析,然後根據ServiceMethod物件建立OkHttp的Request物件,ServiceMethod物件內部包含了網路請求的所有資訊,它是對網路介面方法的封裝,有了Request物件後就可以通過OkHttp這個庫來進行網路請求,最後解析服務端給客戶端返回的資料,通過converter資料轉換器來完成資料的轉換。

OkHttpCall的同步請求execute方法:

    @Override public Response<T> execute() throws IOException {
        okhttp3.Call call;

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

            if (creationFailure != null) {
                if (creationFailure instanceof IOException) {
                    throw (IOException) creationFailure;
                } else if (creationFailure instanceof RuntimeException) {
                    throw (RuntimeException) creationFailure;
                } else {
                    throw (Error) creationFailure;
                }
            }

            call = rawCall;
            if (call == null) {
                try {
                    call = rawCall = createRawCall();
                } catch (IOException | RuntimeException | Error e) {
                    throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
                    creationFailure = e;
                    throw e;
                }
            }
        }

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

        return parseResponse(call.execute());
    }
複製程式碼

下面貼出execute區域性程式碼,方便分析。

    @Override public Response<T> execute() throws IOException {
        okhttp3.Call call;

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

            if (creationFailure != null) {
                if (creationFailure instanceof IOException) {
                    throw (IOException) creationFailure;
                } else if (creationFailure instanceof RuntimeException) {
                    throw (RuntimeException) creationFailure;
                } else {
                    throw (Error) creationFailure;
                }
            }

            ...
        }

       ...
    }
複製程式碼

上面程式碼中一開始建立了一個OkHttp的call物件,下面是一個同步程式碼塊,通過判斷executed是否執行過通過請求,如果執行過就會丟擲異常,接著判斷creationFailure,不為null時,判斷異常型別並丟擲異常,execute方法的前段部分就是對異常的判斷。

    @Override public Response<T> execute() throws IOException {
        okhttp3.Call call;
        synchronized (this) {
            ...
            call = rawCall;
            if (call == null) {
                try {
                    call = rawCall = createRawCall();
                } catch (IOException | RuntimeException | Error e) {
                    throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
                    creationFailure = e;
                    throw e;
                }
            }
        }
        ...
    }
複製程式碼

當沒有任何異常時,將rawCall也就是OkHttp的原生call賦值給區域性變數call,當call為null時,通過createRawCall方法建立OkHttp的Call物件以及Request。

進入createRawCall方法:

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = serviceMethod.toCall(args);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }
複製程式碼

內部通過serviceMethod的toCall方法將傳入的請求引數轉換成Call物件。

進入serviceMethod的toCall方法:

  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<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    ...

    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return callFactory.newCall(requestBuilder.build());
  }
複製程式碼

RequestBuilder內部儲存著網路請求的相關引數,接著在for迴圈中通過ParameterHandler對引數進行解析,最後通過callFactory的newCall建立OkHttp的Call物件,newCall內部傳入的是Request物件,通過requestBuilder.build()建立Request物件,到這裡將OkHttp的Call物件返回給execute方法內部的成員變數call以及OkHttpCall的成員變數rawCall。

    @Override public Response<T> execute() throws IOException {
        okhttp3.Call call;
        ...
        if (canceled) {
            call.cancel();
        }
        return parseResponse(call.execute());
    }
複製程式碼

有了OkHttp的Call之後,就通過call.execute()進行阻塞式的同步請求,並將返回的Response傳入parseResponse方法中。

進入parseResponse方法:

    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ...
        try {
            T body = serviceMethod.toResponse(catchingBody);
            return Response.success(body, rawResponse);
        } catch (RuntimeException e) {
            ...
        }
    }
複製程式碼

只看核心程式碼,通過呼叫serviceMethod的toResponse方法返回body,進入toResponse方法,看它到底做了哪些操作。

  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
複製程式碼

原來是呼叫了資料轉換器將OkHttp返回的Response轉換成Java物件,這裡我們使用的Gson,也就是通過Gson將伺服器返回的資料轉換成我們需要的Java物件,最後通過Response的success方法將返回的Java物件封裝成Response。

  Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
   ...
   return Response.success(body, rawResponse);
}
複製程式碼

進入Response的success方法:

  public static <T> Response<T> success(@Nullable T body, okhttp3.Response rawResponse) {
    ...
    return new Response<>(rawResponse, body, null);
  }
複製程式碼

返回建立好的Response,將body也就是我們的Java物件傳過去。

Response的建構函式:

  private Response(okhttp3.Response rawResponse, @Nullable T body,
      @Nullable ResponseBody errorBody) {
    this.rawResponse = rawResponse;
    this.body = body;
    this.errorBody = errorBody;
  }
複製程式碼

到這裡大家應該很熟悉了,我們利用Retrofit進行網路的同步或非同步請求,最終會返回一個Response物件並通過response.body來獲取結果,這個body就是通過轉換器轉換好的Java物件。

接下來分析非同步請求:

        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                System.out.println(response.body());
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                System.out.println("請求錯誤");
            }
        });
複製程式碼

執行非同步請求的流程和同步類似,只不過非同步請求的結果是通過回撥來傳遞的,非同步是通過enqueue方法來執行的,而這個Call的實現類是OkHttpCall,進入OkHttpCall的enqueue方法。

OkHttpCall的enqueue方法(上半部分):

    @Override public void enqueue(final Callback<T> callback) {
        ...
        okhttp3.Call call;
        ...
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;

            call = rawCall;
            failure = creationFailure;
            if (call == null && failure == null) {
                try {
                    call = rawCall = createRawCall();
                } catch (Throwable t) {
                    throwIfFatal(t);
                    failure = creationFailure = t;
                }
            }
        }
        ...
    }
複製程式碼

可以發現enqueue的上半部分與上面介紹同步請求時是一樣的,建立OkHttp的Call,並檢查相關異常,如果call為null,就通過createRawCall方法建立OkHttp的Call以及請求所需要的Request。

OkHttpCall的enqueue方法(下半部分):

    @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.onResponse(OkHttpCall.this, response);
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
            ...
        });
    }
複製程式碼

上面這段程式碼不用我講,大家也應該知道就是通過OkHttp的Call的enqueue方法進行非同步請求,關於OkHttp相關知識可以閱讀之前寫的OkHttp分析的相關係列教程,在OkHttp的Call的enqueue方法的回撥方法onResponse方法中,將返回的Response通過parseResponse方法轉換成Java物件並返回Retrofit的Response物件,通過前面傳入的Callback物件將Response回撥給客戶端。

到這裡關於Retrofit網路請求框架的封裝就講解完畢了!


838794-506ddad529df4cd4.webp.jpg

相關文章