1. 程式人生 > >Android Retrofit 2.0(一)初次見面請多多關照

Android Retrofit 2.0(一)初次見面請多多關照

    Retrofit 是SQUARE美國一家移動支付公司最近新發布的在Android平臺上 Http 訪問的開源專案。官方標語:“A type-safe HTTP client for Android and Java”語意很明顯是一款Android安全型別的http客戶端。 這裡安全指什麼呢?是支援https或是支援本地執行緒安全呢?而且,Retrofit其內部都是支援lambda語法(鏈式語法),內部支援okHttp, 並且支援響應式RxJava,當然JDK 1.8 和 android studio工具也支援lambda。帶著這些疑問 我開始探究一下。

Retrofit 官方GitHub

地址 

系列文章推薦:

新增依賴

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'

com.squareup.retrofit2:converter-gson:2.0.0-beta4 此依賴非必須,只是方便我對http返回的資料進行解析,下面會講到。


基礎使用

private  void getLogin() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://localhost:8080/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    ApiManager apiService = retrofit.create(ApiManager.class);

    Call<LoginResult> call = apiService.getData("lyk", "1234");
   call.enqueue(new Callback<LoginResult>() {
       @Override
       public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
           if (response.isSuccess()) {
               // 請求成功
           } else {
              //直接操作UI 或彈框提示請求失敗
           }
       }

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

           //錯誤處理程式碼
       }
   });
}

ApiManager介面

public interface ApiManager {

 @GET("login/")
 Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);

以上就是實現一個登入Login介面的小功能 ,先了解一下Retrofit的基本用法。

基礎介紹

1、首先,例項化一個Retrofit物件。

 Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:8080/")
          .addConverterFactory(GsonConverterFactory.create())
          .build();

*addConverterFactory 制定資料解析器,上面新增依賴的gson就是用在這裡做預設資料返回的, 之後通過build()創建出來。

Retrofit內部自帶如下格式:
  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

2、然後,定義帶參的請求介面

  @GET("login/")
  Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);

Call<T>是繼承Cloneable的 並支援泛型,且此類是Retrofit統一返回物件,支援Callback<T>回撥,在2.0上已支援RxJava觀察者物件Observable<T>,此案例暫時用call ,後面入門了retrofit之後再接入RxJava。

接著我們可以傳入制定的解析Modle,就會在主執行緒裡返回對應的model資料,無需開發者手動解析json資料。返回格式由開發者自己設定。

這裡主要用註解@get @post 設定請求方式,後面“login/”是方法Url, @Query("name")來設定body的parameters。

  • 如果想用表單 @FieldMap
    @FormUrlEncoded
    @POST("/url")
     Call<T> postForm(
         @FieldMap Map<String , Object> maps);
  • 如果直接用物件 @Body

    @POST("url")
     Call<T> PostBody(
          @Body Objects objects);
  • 如果直接多引數 @QueryMap

    @PUT("/url")
    Call<T> queryMap(
          @QueryMap Map<String, String> maps);
  • 如果上傳檔案 @Part

    @Multipart
    @POST("/url")
    Call<ResponseBody> uploadFlie(
          @Part("description") RequestBody description,
          @Part("files") MultipartBody.Part file);
  • 如果多檔案上傳 @PartMap()

    @Multipart
    @POST("{url}")
    Call<T> uploadFiles(
          @Path("url") String url,
          @PartMap() Map<String, RequestBody> maps);

3、最後,呼叫API發起請求

   Call<LoginResult> call = apiService.getData("lyk", "1234");
   call.enqueue(new Callback<LoginResult>() {
       @Override
       public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {

       }

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

       }
   });
}

Retrofit支援非同步和同步:

  • call.enqueue(new Callback<LoginResult>)採用非同步請求;
  • call.execute() 採用同步方式。

4、取消請求

 call.cancel();

進階練習


我們依然要面對的問題是 ,Retrofit 的內部怎麼無法輸出Log ?header怎麼加入?請求怎麼支援https?包括怎麼結合RxJava? 不用擔心,這些問題Retrofit 2.0 都提供了支援okhttp的自定義的Interceptor(攔截器)解決了。通過不同的攔截器可以實現不同的自定義請求形式,比如統一加head、引數、加入證書(ssl)等,前提必須結合okhttp來實現 , 通過給OkHttpClient新增Interceptor,然後給Retrofit設定http即可。Retrofit提供了.client()方法供我們自定義請求,當然預設請求就是okhttps的。

1、開啟Log

用攔截器實現, retrofit已經提供了 HttpLoggingInterceptor 裡面有四種級別,輸出的格式,可以看下面介紹。

public enum Level {
    /** No logs. */
    NONE,
    /**
     * Logs request and response lines.
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1 (3-byte body)
     *
     * <-- 200 OK (22ms, 6-byte body)
     * }</pre>
     */
    BASIC,
    /**
     * Logs request and response lines and their respective headers.
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1
     * Host: example.com
     * Content-Type: plain/text
     * Content-Length: 3
     * --> END POST
     *
     * <-- 200 OK (22ms)
     * Content-Type: plain/text
     * Content-Length: 6
     * <-- END HTTP
     * }</pre>
     */
    HEADERS,
    /**
     * Logs request and response lines and their respective headers and bodies (if present).
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1
     * Host: example.com
     * Content-Type: plain/text
     * Content-Length: 3
     *
     * Hi?
     * --> END GET
     *
     * <-- 200 OK (22ms)
     * Content-Type: plain/text
     * Content-Length: 6
     *
     * Hello!
     * <-- END HTTP
     * }</pre>
     */
    BODY
  }

例如,開啟請求頭新增攔截器

   Retrofit retrofit = new Retrofit.Builder().client(new OkHttpClient.Builder()

                         .addNetworkInterceptor(
                                    new   HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))       
     .build())

開啟body日誌新增攔截器

.addNetworkInterceptor(
                                    new   HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))

基礎的輸出新增攔截器

.addNetworkInterceptor(
                                    new   HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))

2、增加頭部資訊

通用請求頭

 new Retrofit.Builder()
           .addConverterFactory(GsonConverterFactory.create())

           .client(new OkHttpClient.Builder()
                   .addInterceptor(new Interceptor() {
                       @Override
                       public Response intercept(Chain chain) throws IOException {
                           Request request = chain.request()
                                   .newBuilder()
                                   .addHeader("mac", "f8:00:ea:10:45")
                                   .addHeader("uuid", "gdeflatfgfg5454545e")
                                   .addHeader("userId", "Fea2405144")
                                   .addHeader("netWork", "wifi")
                                   .build();
                           return chain.proceed(request);
                       }
                   })

                   .build()

特殊API介面單獨加入

@Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-your-App"})
@get("users/{username}")
Call<User>   getUser(@Path("username") String username);

3、新增證書Pinning

證書可以在自定義的OkHttpClient加入certificatePinner 實現

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(new CertificatePinner.Builder()
            .add("YOU API.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
            .add("YOU API..com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
            .add("YOU API..com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
            .add("YOU API..com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
            .build())

4、支援https

加密和普通http客戶端請求支援https一樣,步驟如下:

  1. CertificateFactory 得到Context.getSocketFactory;
  2. 新增證書原始檔;
  3. 繫結到okhttpClient;
  4. 設定okhttpClient到retrofit中。

證書同樣可以設定到okhttpclient中,我們可以把證書放到raw路徑下:

 SLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);

準備證書原始檔

加入證書原始檔,我的證書是放在Raw下面的: 


證書

繫結證書

protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {

if (context == null) {
    throw new NullPointerException("context == null");
}

CertificateFactory certificateFactory;
try {
    certificateFactory = CertificateFactory.getInstance("X.509");
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);

    for (int i = 0; i < certificates.length; i++) {
        InputStream certificate = context.getResources().openRawResource(certificates[i]);
        keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(certificate));

        if (certificate != null) {
            certificate.close();
        }
    }
    SSLContext sslContext = SSLContext.getInstance("TLS");
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);
    sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
   return sslContext.getSocketFactory();

構建HostnameVerifier

 protected static HostnameVerifier getHostnameVerifier(final String[] hostUrls) {

    HostnameVerifier TRUSTED_VERIFIER = new HostnameVerifier() {

        public boolean verify(String hostname, SSLSession session) {
            boolean ret = false;
            for (String host : hostUrls) {
                if (host.equalsIgnoreCase(hostname)) {
                    ret = true;
                }
            }
            return ret;
        }
    };

return TRUSTED_VERIFIER;
}

設定setSocketFactory

okhttpBuilder.socketFactory(HttpsFactroy.getSSLSocketFactory(context, certificates));

certificates 是你raw下證書源ID, int[] certificates = {R.raw.myssl}

設定setNameVerifie

okhttpBuilder.hostnameVerifier(HttpsFactroy.getHostnameVerifier(hosts));

hosts是你的host資料,例如 String hosts[]`= {“https//:aaaa,com”, “https//:bbb.com”}

實現自定義 新增到Retrofit

okHttpClient = okhttpBuilder.build(); 
  Retrofit retrofit = new Retrofit.Builder() .client(okHttpClient) .build();

如果信任所有https請求,
可以直接將OkHttpClient的HostnameVerifier設定為false

OkHttpClient client = new OkHttpClient();

    client.setHostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }
    });
    TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
        @Override
        public void checkClientTrusted(
                java.security.cert.X509Certificate[] x509Certificates,
                String s) throws java.security.cert.CertificateException {
        }

        @Override
        public void checkServerTrusted(
                java.security.cert.X509Certificate[] x509Certificates,
                String s) throws java.security.cert.CertificateException {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[] {};
        }
    } };
    try {
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        client.setSslSocketFactory(sc.getSocketFactory());
    } catch (Exception e) {
        e.printStackTrace();
    }

   clent.protocols(Collections.singletonList(Protocol.HTTP_1_1)) .build();

常規問題

1 url被轉義

   http://api.myapi.com/http%3A%2F%2Fapi.mysite.com%2Fuser%2Flist

請將@path改成@url

   public interface APIService { 
    @GET Call<Users> getUsers(@Url String url);}

或者:

  public interface APIService {
    @GET("{fullUrl}")
    Call<Users> getUsers(@Path(value = "fullUrl", encoded = true) String fullUrl);
}

2Method方法找不到

java.lang.IllegalArgumentException: Method must not be null

請指定具體請求型別@get @post等

   public interface APIService { 

   @GET Call<Users> getUsers(@Url String url);
}

3Url編碼不對,@fieldMap parameters must be use FormUrlEncoded

如果用fieldMap加上FormUrlEncoded編碼

@POST()
@FormUrlEncoded
Observable<ResponseBody> executePost(
        @FieldMap Map<String, Object> maps);

上層需要轉換將自己的map轉換為FieldMap

 @FieldMap(encoded = true) Map<String, Object> parameters,

4 paht和url一起使用

Using @Path and @Url paramers together with retrofit2 

java.lang.IllegalArgumentException: @Path parameters may not be used with @Url. (parameter #4

如果你是這樣的:

 @GET
Call<DataResponse> getOrder(@Url String url,
 @Path("id") int id);

請在你的url指定佔位符.url:

www.myAPi.com/{Id}

看了以上的知識點你發現Retrofit同樣支援RxJava,通過以下設定Call適配模式.就可以完美關聯RxJava。

 retrofit .addCallAdapterFactory(RxJavaCallAdapterFactory.create())