Android 網路框架之Retrofit2使用詳解及從源碼中解析原理

来源:http://www.cnblogs.com/guanmanman/archive/2016/11/21/6085200.html
-Advertisement-
Play Games

就目前來說Retrofit2使用的已相當的廣泛,那麼我們先來瞭解下兩個問題: 1 . 什麼是Retrofit? Retrofit是針對於Android/Java的、基於okHttp的、一種輕量級且安全的、並使用註解方式的網路請求框架。 2 . 我們為什麼要使用Retrofit,它有哪些優勢? ... ...


就目前來說Retrofit2使用的已相當的廣泛,那麼我們先來瞭解下兩個問題:

1 . 什麼是Retrofit?

Retrofit是針對於Android/Java的、基於okHttp的、一種輕量級且安全的、並使用註解方式的網路請求框架。

2 . 我們為什麼要使用Retrofit,它有哪些優勢?

首先,Retrofit使用註解方式,大大簡化了我們的URL拼寫形式,而且註解含義一目瞭然,簡單易懂;

其次,Retrofit使用簡單,結構層次分明,每一步都能清晰的表達出之所以要使用的寓意;

再者,Retrofit支持同步和非同步執行,使得請求變得異常簡單,只要調用enqueue/execute即可完成;

最後,Retrofit更大自由度的支持我們自定義的業務邏輯,如自定義Converters。

好,知道了Retrofit是什麼,有了哪些優勢,現在我們來學習下怎麼使用。

一 Retrofit2使用詳解:

在使用之前,你必須先導入必要的jar包,以androidStudio為例:

添加依賴:

    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'

因為Retrofit2是依賴okHttp請求的,而且請查看它的META-INF->META-INF\maven\com.squareup.retrofit2\retrofit->pom.xml文件,

<dependencies>
    <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
    </dependency>
    ...
</dependencies>

由此可見,它確實是依賴okHttp,okHttp有會依賴okio所以它會制動的把這兩個包也導入進來。

添加許可權:

既然要請求網路,在我們android手機上是必須要有訪問網路的許可權的,下麵把許可權添加進來

<uses-permission android:name="android.permission.INTERNET"/>

好了,下麵開始介紹怎麼使用Retrofit,既然它是使用註解的請求方式來完成請求URL的拼接,那麼我們就按註解的不同來分別學習:

首先,我們需要創建一個java介面,用於存放請求方法的:

public interface GitHubService {
}

然後逐步在該方法中添加我們所需要的方法(按照請求方式):

1 :Get : 是我們最常見的請求方法,它是用來獲取數據請求的。

①:直接通過URL獲取網路內容:

public interface GitHubService {
    @GET("users/octocat/repos")
    Call<List<Repo>> listRepos();
}

在這裡我們定義了一個listRepos()的方法,通過@GET註解標識為get請求,請求的URL為“users/octocat/repos”。

然後看看Retrofit是怎麼調用的,代碼如下:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos();
repos.enqueue(new Callback<List<Repo>>(){
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response){
    
    }

    @Override
    public void onFailure(Call<List<Repo>> call, Throwable t){

    }
});

代碼解釋:首先獲取Retrofit對象,然後通過動態代理獲取到所定義的介面,通過調用介面裡面的方法獲取到Call類型返回值,最後進行網路請求操作(這裡不詳細說明Retrofit 實現原理,後面會對它進行源碼解析),這裡必須要說的是請求URL的拼接:在構建Retrofit對象時調用baseUrl所傳入一個String類型的地址,這個地址在調用service.listRepos()時會把@GET("users/octocat/repos")的URL拼接在尾部。

ok,這樣就完成了,我們這次的請求,但是我們不能每次請求都要創建一個方法呀?這時我們就會想起動態的構建URL了

②:動態獲取URL地址:@Path

我們再上面的基礎上進行修改,如下:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);

這裡在Get註解中包含{user},它所對應的是@Path註解中的“user”,它所標示的正是String user,而我們再使用Retrofit對象動態代理的獲取到GitHubService,當調用listRepos時,我們就必須傳入一個String類型的User,如:

...

Call<List<Repo>> repos = service.listRepos("octocat");

...

如上代碼,其他的代碼都是不變的,而我們只需要使用@Path註解就完全的實現了動態的URL地址了,是不是很方便呢,這還不算什麼,通常情況下,我們去獲取一些網路信息,因為信息量太大,我們會分類去獲取,也就是攜帶一些必要的元素進行過濾,那我們該怎麼實現呢?其實也很簡單,因為Retrofit已經為我們封裝好了註解,請看下麵(官網實例):

③:動態指定條件獲取信息:@Query

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

我們只需要使用@Query註解即可完成我們的需求在@Query("sort")中,short就好比是URL請求地址中的鍵,而它說對應的String sort中的sort則是它的值。

但是我們想,在網路請求中一般為了更精確的查找到我們所需要的數據,過濾更多不需要的無關的東西,我們往往需要攜帶多個請求參數,當然可以使用@Query註解,但是太麻煩,很長,容易遺漏和出錯,那有沒有更簡單的方法呢,有,當然後,我們可以直接放到一個map鍵值對中:

④:動態指定條件組獲取信息:@QueryMap

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

使用@QueryMap註解可以分別地從Map集合中獲取到元素,然後進行逐個的拼接在一起。

ok,到這裡,我們使用@Get註解已經可以完成絕大部分的查詢任務了,下麵我們再來看看另一種常用的請求方式--post

2 POST : 一種用於攜帶傳輸數據的請求方式

稍微瞭解點Http的同學們,可能都會知道:相對於get請求方式把數據存放在uri地址欄中,post請求傳輸的數據時存放在請求體中,所以post才能做到對數據的大小無限制。而在Retrofit中,它又是怎麼使用的呢?請看下麵:

①:攜帶數據類型為對象時:@Body

@POST("users/new")
Call<User> createUser(@Body User user);

當我們的請求數據為某對象時Retrofit是這麼處理使用的:

首先,Retrofit用@POST註解,標明這個是post的請求方式,裡面是請求的url;
其次,Retrofit仿照http直接提供了@Body註解,也就類似於直接把我們要傳輸的數據放在了body請求體中,這樣應用可以更好的方便我們理解。

來看下應用:

Call<List<User>> repos = service.createUser(new User(1, "管滿滿", "28", "http://write.blog.csdn.net/postlist"));

這樣我們直接把一個新的User對象利用註解@Body存放在body請求體,並隨著請求的執行傳輸過去了。

但是有同學在這該有疑問了,Retrofit就只能傳輸的數據為對象嗎?當然不是,下麵請看

②:攜帶數據類型為表單鍵值對時:@Field

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

當我們要攜帶的請求數據為表單時,通常會以鍵值對的方式呈現,那麼Retrofit也為我們考慮了這種情況,它首先用到@FormUrlEncoded註解來標明這是一個表單請求然後在我們的請求方法中使用@Field註解來標示所對應的String類型數據的鍵,從而組成一組鍵值對進行傳遞。

那你是不是有該有疑問了,假如我是要上傳一個文件呢?

③:單文件上傳時:@Part

@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);

此時在上傳文件時,我們需要用@Multipart註解註明它表示允許多個@Part,@Part則對應的一個RequestBody 對象,RequestBody 則是一個多類型的,當然也是包括文件的。下麵看看使用

File file = new File(Environment.getExternalStorageDirectory(), "ic_launcher.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
RequestBody descriptionRequestBody = RequestBody.create(null, "this is photo.");

Call<User> call = service.updateUser(photoRequestBody, descriptionRequestBody);

這裡我們創建了兩個RequestBody 對象,然後調用我們定義的updateUser方法,並把RequestBody傳遞進入,這樣就實現了文件的上傳。是不是很簡單呢?

相比單文件上傳,Retrofit還進一步提供了多文件上傳的方式:

④:多文件上傳時:@PartMap

@Multipart
@PUT("user/photo")
Call<User> updateUser(@PartMap Map<String, RequestBody> photos, @Part("description") RequestBody description);

這裡其實和單文件上傳是差不多的,只是使用一個集合類型的Map封裝了文件,並用@PartMap註解來標示起來。其他的都一樣,這裡就不多講了。

3 Header : 一種用於攜帶消息頭的請求方式

Http請求中,為了防止攻擊或是過濾掉不安全的訪問或是為添加特殊加密的訪問等等以減輕伺服器的壓力和保證請求的安全,通常都會在消息頭中攜帶一些特殊的消息頭處理。Retrofit也為我們提供了該請求方式:

@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
-----------------------------------------------------------
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

以上兩種是靜態的為Http請求添加消息頭,只需要使用@Headers註解,以鍵值對的方式存放即可,如果需要添加多個消息頭,則使用{}包含起來,如上所示。但要註意,即使有相同名字得消息頭也不會被覆蓋,並共同的存放在消息頭中。

當然有靜態添加那相對的也就有動態的添加消息頭了,方法如下:

@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

使用@Header註解可以為一個請求動態的添加消息頭假如@Header對應的消息頭為空的話,則會被忽略,否則會以它的.toString()方式輸出。

ok,到這裡已基本講解完Retrofit的使用,還有兩個重要但簡單的方法也必須在這裡提一下:

1 call.cancel();它可以終止正在進行的請求,程式只要一旦調用到它,不管請求是否在終止都會被停止掉。

2 call.clone();當你想要多次請求一個介面的時候,直接用 clone 的方法來生產一個新的,否則將會報錯,因為當你得到一個call實例,我們調用它的 execute 方法,但是這個方法只能調用一次。多次調用則發生異常。

好了,關於Retrofit的使用我們就講這麼多,接下來我們從源碼的角度簡單的解析下它的實現原理。

二 Retrofit2 從源碼解析實現原理

首先先看一下Retrofit2標準示例

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
        
GitHubService service = retrofit.create(GitHubService.class);

service.enqueue();
service.execute();

由上面我們基本可以看出,Retrofit是通過構造者模式創建出來的,那麼我們就來看看Builder這個構造器的源碼:

public static final class Builder {
    ...
    Builder(Platform platform) {
      this.platform = platform;
      converterFactories.add(new BuiltInConverters());
    }

    public Builder() {
      this(Platform.get());
    }

    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }
    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      adapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }

    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();
      }

      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }
  }

源碼講解:

1:當我們使用new Retrofit.Builder()來創建時,在Builder構造器中,首先就獲得當前的設備平臺信息,並且把內置的轉換器工廠(BuiltInConverters)加添到工廠集合中,它的主要作用就是當使用多種Converters的時候能夠正確的引導並找到可以消耗該類型的轉化器。

2:從我們的基本示例中看到有調用到.baseUrl(BASE_URL)這個方法,實際上沒當使用Retrofit時,該方法都是必須傳入的,並且還不能為空,從源碼中可以看出,當baseUrl方法傳進的參數來看,如果為空的話將會拋出NullPointerException空指針異常。

3:addConverterFactory該方法是傳入一個轉換器工廠,它主要是對數據轉化用的,請網路請求獲取的數據,將會在這裡被轉化成我們所需要的數據類型,比如通過Gson將json數據轉化成對象類型。

4 : 從源碼中,我們看到還有一個client方法,這個是可選的,如果沒有傳入則就預設為OkHttpClient,在這裡可以對OkHttpClient做一些操作,比如添加攔截器列印log等

5:callbackExecutor該方法從名字上看可以得知應該是回調執行者,也就是Call對象從網路服務獲取數據之後轉換到UI主線程中。

6:addCallAdapterFactory該方法主要是針對Call轉換了,比如對Rxjava的支持,從返回的call對象轉化為Observable對象。

7:最後調用build()方法,通過new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);構造方法把所需要的對象傳遞到Retrofit對象中。

ok,當我們通過Builder構造器構造出Retrofit對象時,然後通過Retrofit.create()方法是怎麼把我們所定義的介面轉化成介面實例的呢?來看下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);
          }
        });
  }

當看到Proxy時,是不是多少有點明悟了呢?沒錯就是動態代理,動態代理其實已經封裝的很簡單了,主要使用newProxyInstance()方法來返回一個類的代理實例,其中它內部需要傳遞一個類的載入器,類本身以及一個InvocationHandler處理器,主要的動作都是在InvocationHandler中進行的,它裡面只有一個方法invoke()方法,每當我們調用代理類裡面的方法時invoke()都會被執行,並且我們可以從該方法的參數中獲取到所需要的一切信息,比如從method中獲取到方法名,從args中獲取到方法名中的參數信息等。

而Retrofit在這裡使用到動態代理也不會例外:

首先,通過method把它轉換成ServiceMethod ;

然後,通過serviceMethod, args獲取到okHttpCall 對象;

最後,再把okHttpCall進一步封裝並返回Call對象。

下麵來逐步詳解。

1:將method把它轉換成ServiceMethod

ServiceMethod serviceMethod = loadServiceMethod(method);

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;
  }

loadServiceMethod源碼方法中非常的好理解,主要就是通過ServiceMethod.Builder()方法來構建ServiceMethod,並把它給緩存取來,以便下次可以直接回去ServiceMethod。那下麵我們再來看看它是怎麼構建ServiceMethod方法的:

public Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }

public ServiceMethod build() {
      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 = createResponseConverter();
      ...
      return new ServiceMethod<>(this);
    }

首先在Builder()中初始化一些參數,然後在build()中返回一個new ServiceMethod<>(this)對象。

下麵來詳細的解釋下build()方法,完全理解了該方法則便於理解下麵的所有執行流程。

①:構建CallAdapter對象,該對象將會在第三步中起著至關重要的作用。

現在我們先看看它是怎麼構建CallAdapter對象的:createCallAdapter()方法源碼如下:

private CallAdapter<?> createCallAdapter() {
      Type returnType = method.getGenericReturnType();
      if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(
            "Method return type must not include a type variable or wildcard: %s", returnType);
      }
      if (returnType == void.class) {
        throw methodError("Service methods cannot return void.");
      }
      Annotation[] annotations = method.getAnnotations();
      try {
        return 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);
      }
    }

在createCallAdapter方法中主要做的是事情就是獲取到method的類型和註解,然後調用retrofit.callAdapter(returnType, annotations);方法:

public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
  }

public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {
    checkNotNull(returnType, "returnType == null");
    checkNotNull(annotations, "annotations == null");

    int start = adapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
      CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }
    ...
}

輾轉到Retrofit中nextCallAdapter()中,在for 迴圈中分別從adapterFactories中來獲取CallAdapter對象,但是adapterFactories中有哪些CallAdapter對象呢,這就需要返回到構建Retrofit對象中的Builder 構造器中查看了

public static final class Builder {
    ...
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      adapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }
    ...
    public Retrofit build() {
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    ...
    }  
  }

CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
    if (callbackExecutor != null) {
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }
    return DefaultCallAdapterFactory.INSTANCE;
  }

從上面的代碼中可以看到,不管有沒有通過addCallAdapterFactory添加CallAdapter,adapterFactories集合至少都會有一個ExecutorCallAdapterFactory對象。當我們從adapterFactories集合中回去CallAdapter對象時,那我們都會獲得ExecutorCallAdapterFactory這個對象。而這個對象將在第三步中和後面執行同步或非同步請求時起著至關重要的作用。

②:構建responseConverter轉換器對象,它的作用是尋找適合的數據類型轉化

該對象的構建和構建CallAdapter對象的流程基本是一致的,這裡就不在贅述。同學們可自行查看源碼。

2:通過serviceMethod, args獲取到okHttpCall 對象

第二步相對比較簡單,就是對象傳遞:

OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
    this.serviceMethod = serviceMethod;
    this.args = args;
  }

3:把okHttpCall進一步封裝並返回Call對象

這一步也是一句話 return serviceMethod.callAdapter.adapt(okHttpCall);但是想理解清楚必須先把第一步理解透徹,通過第一步我們找得到serviceMethod.callAdapter就是ExecutorCallAdapterFactory對象,那麼調用.adapt(okHttpCall)把okHttpCall怎麼進行封裝呢?看看源碼:

<R> T adapt(Call<R> call);

一看,嚇死寶寶了,就這麼一句,這是嘛呀,但是經過第一步的分析,我們已知道serviceMethod.callAdapter就是ExecutorCallAdapterFactory,那麼我們可以看看在ExecutorCallAdapterFactory類中有沒有發現CallAdapter的另類應用呢,一看,果不其然在重寫父類的get()方法中我們找到了答案:

 @Override
  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);
      }
    };
  }

當看到return new CallAdapter中的adapt(Call

ok,當我們得到介面的代理實例之後,通過代理介面調用裡面的方法,就會觸發InvocationHandler對象中的invoke方法,從而完成上面的三個步驟並且返回一個Call對象,通過Call對象就可以去完成我們的請求了,Retrofit為我們提供兩種請求方式,一種是同步,一種是非同步。我們這裡就以非同步方式來講解:

service.enqueue(new Callback<List<User>>() {
    @Override
    public void onResponse(Call<List<User>> call, Response<List<User>> response) {
        Log.d("response body",response.body());
    }

    @Override
    public void onFailure(Call<BoardInfo> call, Throwable t) {
        Log.i("response Throwable",t.getMessage().toString());
    }
});

從上面我們可以看到enqueue方法中有一個回調函數,回調函數裡面重寫了兩個方法分別代表請求成功和失敗的方法,但是我們想知道它是怎麼實現的原理呢?那麼請往下麵看:

在上面獲取介面的代理實例時,通過代理介面調用裡面的方法獲取一個Call對象,我們上面也分析了其實這個Call對象就是ExecutorCallbackCall,那麼我們來看看它裡面是怎麼實現的?

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override public void enqueue(final Callback<T> callback) {
      if (callback == null) throw new NullPointerException("callback == null");

      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()) {
                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類中,封裝了兩個對象一個是callbackExecutor,它主要是把現在運行的線程切換到主線程中去,一個是delegate對象,這個對象就是真真正正的執行網路操作的對象,那麼它的真身到底是什麼呢?還記得我們在獲取代理介面第三步執行的serviceMethod.callAdapter.adapt(okHttpCall)的分析吧,經過輾轉幾步終於把okHttpCall傳遞到了new ExecutorCallbackCall<>(callbackExecutor, call);中,然後看看ExecutorCallbackCall的構造方法:

ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

由此可以明白delegate 就是okHttpCall對象,那麼我們在看看okHttpCall是怎麼執行非同步網路請求的:

@Override 
public void enqueue(final Callback<T> callback) {
    if (callback == null) throw new NullPointerException("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 {
          call = rawCall = createRawCall();
        } catch (Throwable 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)
          throws IOException {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }

      @Override 
      public void onFailure(okhttp3.Call call, IOException e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

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

      private void callSuccess(Response<T> response) {
        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }

從上面代碼中,我們很容易就看出,其實它就是這裡面封裝了一個okhttp3.Call,直接利用okhttp進行網路的非同步操作,至於okhttp是怎麼進行網路請求的我們就不再這裡講解了,感興趣的朋友可以自己去查看源碼。

好了,到這裡整個Retrofit的實現原理基本已解析完畢,相信大家學習過都能夠很好的掌握了。ok,今天就講到這裡吧,本來還打算再寫個實例放上來的,看看篇幅也就放棄了,實戰部分會在下一篇和Rxjava一起放出來,Rxjava我自己學習的都心花怒放了。看完記住關註微信平臺。

更多資訊請關註微信平臺,有博客更新會及時通知。愛學習愛技術。
這裡寫圖片描述


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • <!-- ////////////////////////// 請款列表模板 開始--><div id="rtlist-temple"> <div class="list-box m-top-20"> <div class="list-title font-size-26 clearfix"> <s ...
  • 優美整潔的代碼,基本都需要遵循以下的幾大基本原則. 1.單一職責原則SRP (Single Responsibility Principle) 類,模塊,方法應該有且僅有一條修改的理由.也就是說,其僅僅負責一個功能,只有這個功能發生變化時,才需要修改它. 舉個慄子:人類 有 吃飯的方法,那麼這個方法 ...
  • 說到RecyclerView,相信大家都不陌生,它是我們經典級ListView的升級版,升級後的RecyclerView展現了極大的靈活性。同時內部直接封裝了ViewHolder,不用我們自己定義ViewHolder就能實現item的回收和復用功能。當然它肯定不止這些好處,比如我們可以自定義分割線,... ...
  • 1、打開CornerStone,找到偏號設置,找到Subversion選項 2、去看“Use default global ignores”前面的“勾”,刪除".a" 3、github上搜索“gitignore”,找到"Objective-C.gitignore",打開,對照著添加 4、添加以下幾項 ...
  • 一、寫在最前面 本次,來介紹一下安卓中為控制項--Button綁定事件的五種方式。 二、具體的實現 第一種:直接綁定在Button控制項上: 步驟1.在Button控制項上設置android:onClick=",其中這個屬性的屬性值對應的是MainActivity類中的方法名字(自己創建的方法): 步驟2 ...
  • 項目中經常需要底部彈出框,這裡我整理一下其中我用的比較順手的一個方式(底部彈出一個橫向滿屏的dialog)。 效果圖如下所示(只顯示關鍵部分): 步驟如下所示: 1.定義一個dialog的佈局(lay_share.xml) 1 <?xml version="1.0" encoding="utf-8" ...
  • 多線程學習 每一個iOS應用(進程)運行都會有一個主線程(UI線程),UI上的更新推薦在主線程中去完成。多線程本身並不複雜,難點在於多個線程在其生命周期的管理,如線程的執行順序、線程間的數據共用以及資源競爭等問題。 本文主要記錄開發中常用的3種多線程模式: NSThread NSOperation ...
  • 前言 學習本系列內容需要具備一定 HTML 開發基礎,沒有基礎的朋友可以先轉至 "HTML快速入門(一)" 學習 本人接觸 React Native 時間並不是特別長,所以對其中的內容和性質瞭解可能會有所偏差,在學習中如果有錯會及時修改內容,也歡迎萬能的朋友們批評指出,謝謝 文章第一版出自簡書,如果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...