[Android組件解讀] 初次接觸Retrofit

說到網(wǎng)絡(luò)庫就會(huì)想到Google的HttpUrlConnection和Apache的HttpClient。

Google推薦使用HttpUrlConnection,拋棄了HttpClient

HttpClient已在Android6.0被廢棄了(吐槽:明明HttpClient功能更強(qiáng)大啊),

查看為什么拋棄HttpClient,可以看下【Android】Android2.3版本以上谷歌為何推薦使用HttpURLConnection卻棄用 Apache HttpClient

想繼續(xù)使用HttpClient的話,可以參考android6.0SDK 刪除HttpClient的相關(guān)類的解決方法

除了HttpUrlConnection,還有Google推薦的Volley(沒怎么用過),和square的okhttp,還有就是今天介紹的square的retrofit

這個(gè)年頭不會(huì)點(diǎn)retrofit + rxjava + mvp...的組合拳,都不好意思說是新時(shí)代的Android開發(fā)

為什么我用的組件都是square公司出品的呢?只能說明他們做的太好了。


回到正題

關(guān)于Retrofit的介紹,引用它在github上的一句話

Type-safe HTTP client for Android and Java by Square
用于Android和Java的類型安全的Http框架

現(xiàn)在市面上使用的是retrofit2.0版本,跟以前的1.9版本有部分API不一樣,可以查看Retrofit 1.9 遷移到 Retrofit 2.0,建議使用新的版本

Retrofit是使用RESETful API的。

這里簡(jiǎn)單的介紹下關(guān)于RESETful的架構(gòu):

(1)每一個(gè)URI代表一種資源;
(2)客戶端和服務(wù)器之間,傳遞這種資源的某種表現(xiàn)層;
(3)客戶端通過四個(gè)HTTP動(dòng)詞(GET用來獲取資源,POST用來新建資源(也可以用于更新資源),PUT用來更新資源,DELETE用來刪除資源),
對(duì)服務(wù)器端資源進(jìn)行操作,實(shí)現(xiàn)"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。

更多關(guān)于RESET API可以看阮一峰的理解RESTful架構(gòu)

Retrofit的git地址:https://github.com/square/retrofit

Retrofit的官方地址:http://square.github.io/retrofit/

使用姿勢(shì)

  1. 往build.gradle添加依賴,不需要額外添加okhttp,retrofit內(nèi)部支持

     // retrofit
     compile 'com.squareup.retrofit2:retrofit:2.2.0'
     // gson轉(zhuǎn)化器
     compile 'com.squareup.retrofit2:converter-gson:2.2.0'
    
  2. 創(chuàng)建接口,聲明API

     // 獲取請(qǐng)求API
     // 請(qǐng)求接口 https://api.github.com
     public interface GitHub {
     @GET("/repos/{owner}/{repo}/contributors")
     Call<List<Contributor>> contributors(
             @Path("owner") String owner,
             @Path("repo") String repo);
     }
     
     // 請(qǐng)求對(duì)象類
     public static class Contributor {
         public final String login;
         public final int contributions;
    
         public Contributor(String login, int contributions) {
         this.login = login;
         this.contributions = contributions;
         }
     }
    
  3. 方法調(diào)用

     public void request() throws IOException {
    
        // 1.創(chuàng)建Retrofit對(duì)象,根據(jù)Retrofit模板創(chuàng)建
        Retrofit retrofit = new Retrofit.Builder()
                // 傳入baseurl地址,
                .baseUrl("https://api.github.com")
                // 傳入gson轉(zhuǎn)化器
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    
        // 2.獲取GitHub的對(duì)象
        GitHub github = retrofit.create(GitHub.class);
    
        // 3.傳入?yún)?shù)
            Call<List<Contributor>> call = github.contributors("square", "retrofit");
    
        // 4.異步請(qǐng)求接口
        // 請(qǐng)求接口格式為"https://api.github.com/repos/square/retrofit/contributors"
        call.enqueue(new Callback<List<Contributor>>() {
            @Override
            public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
                List<Contributor> contributors = response.body();
                for (Contributor contributor : contributors) {
                    Log.e("MainActivity",contributor.login + " (" + contributor.contributions + ")");
                }
            }
    
            @Override
            public void onFailure(Call<List<Contributor>> call, Throwable t) {
    
            }
        });
     }
    

通過上面的例子,應(yīng)該能夠理解Retrofit的使用了吧

關(guān)于使用,可以參考下官網(wǎng)介紹http://square.github.io/retrofit/

對(duì)此,作者菌有幾個(gè)問題

  • Retrofit提供的這么多注解是怎么運(yùn)用的?
  • Retrofit有多少種注解
  • Retrofit生成API對(duì)象后,怎么發(fā)起請(qǐng)求的

源碼解讀

先查看retrofit的代碼構(gòu)造

.
└── retrofit2
    ├── BuiltInConverters.java --默認(rèn)的轉(zhuǎn)化器,支持流,字符串
    ├── Call.java  -- 請(qǐng)求的發(fā)送/取消,并獲取當(dāng)前請(qǐng)求的狀態(tài)
    ├── CallAdapter.java
    ├── Callback.java. --回調(diào)方法
    ├── Converter.java -- 轉(zhuǎn)化器,將okhttp中的RequestBody,ResponseBody進(jìn)行轉(zhuǎn)化,支持XML,JSON,ProtocolBuf,如GsonConverterFactory,將返回的內(nèi)容或者發(fā)送Body中的內(nèi)容轉(zhuǎn)化,
    ├── DefaultCallAdapterFactory.java
    ├── ExecutorCallAdapterFactory.java
    ├── HttpException.java
    ├── OkHttpCall.java --實(shí)現(xiàn)了Call的接口,使用okhttp3.0的API
    ├── ParameterHandler.java --參數(shù)句柄,處理retrofit頭文件的操作請(qǐng)求
    ├── Platform.java --判斷當(dāng)前運(yùn)行環(huán)境是Android還是Java8
    ├── RequestBuilder.java
    ├── Response.java 服務(wù)端返回內(nèi)容,可以通過response.body()獲取到返回內(nèi)容
    ├── Retrofit.java  --`對(duì)外方法`
    ├── ServiceMethod.java
    ├── Utils.java
    ├── http --http文件夾中包含的是retrofit所有的注解類,關(guān)于Http的操作和上傳字段,以及Header配置
    │   ├── Body.java  用于Post,根據(jù)轉(zhuǎn)換方式將實(shí)例對(duì)象轉(zhuǎn)化為對(duì)應(yīng)字符串傳遞參數(shù).比如Retrofit添加GsonConverterFactory則是將body轉(zhuǎn)化為gson字符串進(jìn)行傳遞
    │   ├── DELETE.java Delete請(qǐng)求
    │   ├── Field.java  用于Post方式傳遞參數(shù),需要在請(qǐng)求接口方法上添加@FormUrlEncoded,即以表單的方式傳遞參數(shù)
    │   ├── FieldMap.java
    │   ├── FormUrlEncoded.java 同@Field使用
    │   ├── GET.java GET請(qǐng)求
    │   ├── HEAD.java
    │   ├── HTTP.java
    │   ├── Header.java 添加http header
    │   ├── HeaderMap.java
    │   ├── Headers.java 跟@Header作用一樣,只是使用方式不一樣,@Header是作為請(qǐng)求方法的參數(shù)傳入,@Headers是以固定方式直接添加到請(qǐng)求方法上
    │   ├── Multipart.java 配合@Multipart使用,一般用于文件上傳
    │   ├── OPTIONS.java
    │   ├── PATCH.java
    │   ├── POST.java  Post請(qǐng)求
    │   ├── PUT.java   PUT請(qǐng)求
    │   ├── Part.java 一般用于文件上傳
    │   ├── PartMap.java
    │   ├── Path.java 用于URL上占位符
    │   ├── Query.java 用于Http Get請(qǐng)求傳遞參數(shù)
    │   ├── QueryMap.java
    │   ├── QueryName.java  用于Http Get請(qǐng)求傳遞參數(shù)
    │   ├── Streaming.java  用于Http Get請(qǐng)求傳遞流形式參數(shù)
    │   ├── Url.java 直接賦值URL
    │   └── package-info.java
    └── package-info.java

附上一篇?jiǎng)e人寫的###Retrofit注解含義###

講解下Retrofit的使用原理

  1. 首先講解Retrofit初始化
 Retrofit retrofit = new Retrofit.Builder()
            // 傳入baseurl地址,
            .baseUrl("https://api.github.com")
            // 傳入gson轉(zhuǎn)化器
            .addConverterFactory(GsonConverterFactory.create())
            .build();

此處用了Build機(jī)制來初始化,現(xiàn)在很多初始化通過Build機(jī)制,如Dialog,OKHttp,Picasso等

Build構(gòu)建時(shí)可以傳入以下配置

Builder(Platform platform) {
      this.platform = platform;
      // 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());
    }

    public Builder() {
        // 獲取當(dāng)前的系統(tǒng)版本
        this(Platform.get());
    }

      /**
       * 配置Client,默認(rèn)使用okhttp3.OkHttpClient
       * @param client
       * @return
       */
    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

      /**
       * 創(chuàng)建CallFactory,用于生成請(qǐng)求Http的Call
       * @param factory
       * @return
       */
    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }

      /**
       * 配置URL
       * @param baseUrl
       * @return
       */
    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);
    }

    /**
     * 配置URL,下面有幾組實(shí)例配置URL
     * <p>
     * <b>Correct:</b><br>
     * Base URL: http://example.com/api/<br>
     * Endpoint: foo/bar/<br>
     * Result: http://example.com/api/foo/bar/
     * <p>
     * <b>Incorrect:</b><br>
     * Base URL: http://example.com/api<br>
     * Endpoint: foo/bar/<br>
     * Result: http://example.com/foo/bar/
     * <p>
     * This method enforces that {@code baseUrl} has a trailing {@code /}.
     * <p>
     * <b>Endpoint values which contain a leading {@code /} are absolute.</b>
     * <p>
     * Absolute values retain only the host from {@code baseUrl} and ignore any specified path
     * components.
     * <p>
     * Base URL: http://example.com/api/<br>
     * Endpoint: /foo/bar/<br>
     * Result: http://example.com/foo/bar/
     * <p>
     * Base URL: http://example.com/<br>
     * Endpoint: /foo/bar/<br>
     * Result: http://example.com/foo/bar/
     * <p>
     * <b>Endpoint values may be a full URL.</b>
     * <p>
     * Values which have a host replace the host of {@code baseUrl} and values also with a scheme
     * replace the scheme of {@code baseUrl}.
     * <p>
     * Base URL: http://example.com/<br>
     * Endpoint: https://github.com/square/retrofit/<br>
     * Result: https://github.com/square/retrofit/
     * <p>
     * Base URL: http://example.com<br>
     * Endpoint: //github.com/square/retrofit/<br>
     * Result: http://github.com/square/retrofit/ (note the scheme stays 'http')
     */
    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;
    }

      /**
       * 添加轉(zhuǎn)換器
       * @param factory
       * @return
       */
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    /**
     * Add a call adapter factory for supporting service method return types other than {@link
     * Call}.
     */
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      adapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

      /**
       * 回調(diào)線程
       * @param executor
       * @return
       */
    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

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

    /**
     * Create the {@link Retrofit} instance using the configured values.
     * <p>
     * Note: If neither {@link #client} nor {@link #callFactory} is called a default {@link
     * OkHttpClient} will be created and used.
     */
    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      // 默認(rèn)使用okhttp3.0的OKHttpClient
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      // 若Platform是Android,回調(diào)的是主線程
      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

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

此處注意的是baseUrl,正確寫法

* Base URL: http://example.com/api/
* Endpoint: foo/bar/
* Result: http://example.com/api/foo/bar/

Platform判斷當(dāng)前運(yùn)行環(huán)境是Android還是JAVA8

private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();
      }
    } catch (ClassNotFoundException ignored) {
    }
    try {
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
  }

2.獲取GitHub的對(duì)象,傳入?yún)?shù)

GitHub github = retrofit.create(GitHub.class);
//傳入?yún)?shù)
Call<List<Contributor>> call = github.contributors("square", "retrofit");

通過代理模式來處理接口

public <T> T create(final Class<T> service) {
        // 校驗(yàn)提供的是接口實(shí)現(xiàn)的類
        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,進(jìn)行URL組裝
                        ServiceMethod<Object, Object> serviceMethod =
                                (ServiceMethod<Object, Object>) loadServiceMethod(method);
                        OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                        return serviceMethod.callAdapter.adapt(okHttpCall);
                    }
                });
    }

3.調(diào)用call.enqueue(new Callback...);

使用的是OKHttpCall,使用了OKHttp的Call,然后進(jìn)行Http請(qǐng)求。

Call的接口定義

public interface Call<T> extends Cloneable {
  /**
   * 同步發(fā)送請(qǐng)求,返回結(jié)果
   * @return
   * @throws IOException
   */
  Response<T> execute() throws IOException;

  /**
   * 異步發(fā)送請(qǐng)求,通過Callback回調(diào)返回
   * @param callback
   */
  void enqueue(Callback<T> callback);

  /**
   * 是否正在執(zhí)行發(fā)送
   * @return
   */
  boolean isExecuted();

  /**
   * 取消發(fā)送的請(qǐng)求,若請(qǐng)求還未執(zhí)行
   */
  void cancel();

  /**
   * 請(qǐng)求是否被取消
   * @return
   */
  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call<T> clone();

  /** The original HTTP request. */
  Request request();
}

4.其他:如何構(gòu)建Http請(qǐng)求,主要實(shí)在ServiceMethod.java這個(gè)類中

以@Path為例

 else if (annotation instanceof Path) {
        if (gotQuery) {
          throw parameterError(p, "A @Path parameter must not come after a @Query.");
        }
        if (gotUrl) {
          throw parameterError(p, "@Path parameters may not be used with @Url.");
        }
        if (relativeUrl == null) {
          throw parameterError(p, "@Path can only be used with relative url on @%s", httpMethod);
        }
        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());
        }

通過剛才的代理模式調(diào)用ServiceMethod,將接口中的注解生成完整的Http請(qǐng)求

相關(guān)資料

你真的會(huì)用Retrofit2嗎?Retrofit2完全教程

Retrofit各個(gè)注解的含義及作用

Android 網(wǎng)絡(luò)框架 Retrofit2.0介紹、使用和封裝

理解RESTful架構(gòu)

RESTful API 設(shè)計(jì)指南

jdk動(dòng)態(tài)代理實(shí)現(xiàn)原理


期待下4月新番-夏目友人帳第六季
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容