Retrofit2.0使用總結(jié)

概述

隨著Google對(duì)HttpClient 摒棄,和Volley的逐漸沒落,OkHttp開始異軍突起,而Retrofit則對(duì)okHttp進(jìn)行了強(qiáng)制依賴。

Retrofit是由Square公司出品的針對(duì)于Android和Java的類型安全的Http客戶端,

如果看源碼會(huì)發(fā)現(xiàn)其實(shí)質(zhì)上就是對(duì)okHttp的封裝,使用面向接口的方式進(jìn)行網(wǎng)絡(luò)請(qǐng)求,利用動(dòng)態(tài)生成的代理類封裝了網(wǎng)絡(luò)接口請(qǐng)求的底層,其將請(qǐng)求返回javaBean,對(duì)網(wǎng)絡(luò)認(rèn)證 REST API進(jìn)行了很好對(duì)支持此,使用Retrofit將會(huì)極大的提高我們應(yīng)用的網(wǎng)絡(luò)體驗(yàn)。

REST

既然是RESTful架構(gòu),那么我們就來看一下什么是REST吧。REST(REpresentational State Transfer)是一組架構(gòu)約束條件和原則。RESTful架構(gòu)都滿足以下規(guī)則:
(1)每一個(gè)URI代表一種資源;
(2)客戶端和服務(wù)器之間,傳遞這種資源的某種表現(xiàn)層;
(3)客戶端通過四個(gè)HTTP動(dòng)詞,對(duì)服務(wù)器端資源進(jìn)行操作,實(shí)現(xiàn)"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。

更多關(guān)于REST的介紹:什么是REST - GitHub講解的非常詳細(xì)

2.0與1.9使用比較

如果之前使用過Retrofit1,會(huì)發(fā)現(xiàn)2.0后的API會(huì)有一些變化,比如創(chuàng)建方式,攔截器,錯(cuò)誤處理,轉(zhuǎn)換器等,如下我們列舉以下他們使用起來具體的區(qū)別有哪些。
(1) 在Retrofit1中使用的是RestAdapter,而Retrofit2中使用的Retrofit實(shí)例,之前的setEndpoint變?yōu)榱薭aseUrl。
(2) Retrofit1中使用setRequestInterceptor設(shè)置攔截器,對(duì)http請(qǐng)求進(jìn)行相應(yīng)等處理。
(3) Retrofit2通過OKHttp的攔截器攔截http請(qǐng)求進(jìn)行監(jiān)控,重寫或重試等,包括日志打印等。(4) converter,Retrofit1中的setConverter,換以addConverterFactory,用于支持Gson轉(zhuǎn)換。

Retrofit1體驗(yàn)不好的地方:
(1) Retrofit1不能同時(shí)操作response返回?cái)?shù)據(jù)(比如說返回的 Header 部分或者 URL)
和序列化后的數(shù)據(jù)(JAVABEAN)
(2) Retrofit1中同步和異步執(zhí)行同一個(gè)方法需要分別定義接口。
(3) Retrofit1對(duì)正在進(jìn)行的網(wǎng)絡(luò)任務(wù)無法取消。

參考:官方CHANGELOG.md更新到Retrofit2的一些技巧

1.9使用配置:

 //gson converter
        final static Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
            .serializeNulls()
            .create();

        // client
        OkHttpClient client = new OkHttpClient();
        client.setReadTimeout(12, TimeUnit.SECONDS);

        RestAdapter.Builder builder = new RestAdapter.Builder();
        builder.setClient(new OkClient(client))
               //日志打印
               .setLogLevel(RestAdapter.LogLevel.FULL)
               //baseUrl
               .setEndpoint("https://api.github.com")
               //轉(zhuǎn)換器
               .setConverter(new GsonConverter(gson))
               //錯(cuò)誤處理
               .setErrorHandler(new ErrorHandler() {
                           @Override
                           public Throwable handleError(RetrofitError cause) {
                               return null;
                           }
                       })
               //攔截器
               .setRequestInterceptor(authorizationInterceptor)
        RestAdapter restAdapter = builder.build();
        apiService = restAdapter.create(ApiService.class);

2.0使用配置

引入依賴
compile 'com.squareup.retrofit2:retrofit:2.0.2' 
compile 'com.squareup.retrofit2:converter-gson:2.0.2' 
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' 
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

OkHttp配置
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .retryOnConnectionFailure(true)
                .connectTimeout(15, TimeUnit.SECONDS)
                .addNetworkInterceptor(authorizationInterceptor)
                .build();

其中 level 為 BASIC / HEADERS / BODY,BODY等同于1.9中的FULLretryOnConnectionFailure:錯(cuò)誤重聯(lián);
addInterceptor:設(shè)置應(yīng)用攔截器,可用于設(shè)置公共參數(shù),頭信息,日志攔截等;addNetworkInterceptor:網(wǎng)絡(luò)攔截器,可以用于重試或重寫,對(duì)應(yīng)與1.9中的setRequestInterceptor。
參考:Interceptors中文翻譯:Okhttp-wiki 之 Interceptors 攔截器

Retrofit配置
  Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(client)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();
        apiService = retrofit.create(ApiService.class);

其中baseUrl相當(dāng)于1.9中的setEndPoint,addCallAdapterFactory提供RxJava支持,如果沒有提供響應(yīng)的支持(RxJava,Call),則會(huì)跑出異常。addConverterFactory提供Gson支持,可以添加多種序列化Factory,但是GsonConverterFactory必須放在最后,否則會(huì)拋出異常。
參考:用 Retrofit 2 簡(jiǎn)化 HTTP 請(qǐng)求

2.0使用介紹

注意:retrofit2.0后:BaseUrl要以/結(jié)尾;@GET 等請(qǐng)求不要以/開頭;@Url: 可以定義完整url,不要以 / 開頭。

關(guān)于URL拼接注意事項(xiàng):Retrofit 2.0:有史以來最大的改進(jìn)

基本用法:

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

//獲取實(shí)例
Retrofit retrofit = new Retrofit.Builder()
    //設(shè)置OKHttpClient,如果不設(shè)置會(huì)提供一個(gè)默認(rèn)的
    .client(new OkHttpClient())
    //設(shè)置baseUrl
    .baseUrl("https://api.github.com/")
    //添加Gson轉(zhuǎn)換器
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

//同步請(qǐng)求
//https://api.github.com/users/octocat/repos
Call<List<Repo>> call = service.listRepos("octocat");
try {
     Response<List<Repo>> repos  = call.execute();
} catch (IOException e) {
     e.printStackTrace();
}

//call只能調(diào)用一次。否則會(huì)拋 IllegalStateException
Call<List<Repo>> clone = call.clone();

//異步請(qǐng)求
clone.enqueue(new Callback<List<Repo>>() {
        @Override
        public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
            // Get result bean from response.body()
            List<Repo> repos = response.body();
            // Get header item from response
            String links = response.headers().get("Link");
            /**
              * 不同于retrofit1 可以同時(shí)操作序列化數(shù)據(jù)javabean和header
              */
        }

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

        }
    });

// 取消
call.cancel();
//rxjava support
public interface GitHubService {
  @GET("users/{user}/repos")
  Observable<List<Repo>> listRepos(@Path("user") String user);
}

// 獲取實(shí)例
// Http request
Observable<List<Repo>> call = service.listRepos("octocat");
retrofit注解:

方法注解:包含@GET、@POST、@PUT、@DELETE、@PATH、@HEAD、@OPTIONS、@HTTP。
標(biāo)記注解:包含@FormUrlEncoded、@Multipart、@Streaming。
參數(shù)注解:包含@Query,@QueryMap、@Body、@Field,@FieldMap、@Part,@PartMap。
其他注解:@Path、@Header,@Headers、@Url

幾個(gè)特殊的注解@HTTP:可以替代其他方法的任意一種

  /**
     * method 表示請(qǐng)的方法,不區(qū)分大小寫
     * path表示路徑
     * hasBody表示是否有請(qǐng)求體
     */
    @HTTP(method = "get", path = "users/{user}", hasBody = false)
    Call<ResponseBody> getFirstBlog(@Path("user") String user);

@Url:使用全路徑復(fù)寫baseUrl,適用于非統(tǒng)一baseUrl的場(chǎng)景。

@GET
Call<ResponseBody> v3(@Url String url);

@Streaming:用于下載大文件

@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);
ResponseBody body = response.body();
long fileSize = body.contentLength();
InputStream inputStream = body.byteStream();

常用注解@Path:URL占位符,用于替換和動(dòng)態(tài)更新,相應(yīng)的參數(shù)必須使用相同的字符串被@Path進(jìn)行注釋

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);
//--> http://baseurl/group/groupId/users

//等同于:
@GET
Call<List<User>> groupListUrl(
      @Url String url);

@Query,@QueryMap:查詢參數(shù),用于GET查詢,需要注意的是@QueryMap可以約定是否需要encode

@GET("group/users")
Call<List<User>> groupList(@Query("id") int groupId);
//--> http://baseurl/group/users?id=groupId
Call<List<News>> getNews((@QueryMap(encoded=true) Map<String, String> options);

@Body:用于POST請(qǐng)求體,將實(shí)例對(duì)象根據(jù)轉(zhuǎn)換方式轉(zhuǎn)換為對(duì)應(yīng)的json字符串參數(shù),這個(gè)轉(zhuǎn)化方式是GsonConverterFactory定義的。

@POST("add") 
Call<List<User>> addUser(@Body User user);

@Field,@FieldMap:Post方式傳遞簡(jiǎn)單的鍵值對(duì),需要添加@FormUrlEncoded表示表單提交Content-Type:application/x-www-form-urlencoded

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

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@FieldMap Map<String,String> mapParams);


@Part,@PartMap:用于POST文件上傳,其中@Part MultipartBody.Part代表文件,@Part("key") RequestBody代表參數(shù)需要添加@Multipart表示支持文件上傳的表單,Content-Type: multipart/form-data

@Multipart 
@POST("upload") 
Call<ResponseBody> upload(@Part("description") RequestBody description, @Part MultipartBody.Part file);

 // https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
    // use the FileUtils to get the actual file by uri
    File file = FileUtils.getFile(this, fileUri);

    // create RequestBody instance from file
    RequestBody requestFile =
            RequestBody.create(MediaType.parse("multipart/form-data"), file);

    // MultipartBody.Part is used to send also the actual file name
    MultipartBody.Part body =
            MultipartBody.Part.createFormData("picture", file.getName(), requestFile);

    // add another part within the multipart request
    String descriptionString = "hello, this is description speaking";
    RequestBody description =
            RequestBody.create(
                    MediaType.parse("multipart/form-data"), descriptionString);

參考: Retrofit2 完全解析 探索與okhttp之間的關(guān)系Retrofit 2 — How to Upload Files to Server

@Header:header處理,不能被互相覆蓋,用于修飾參數(shù),

//動(dòng)態(tài)設(shè)置Header值
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

等同于 :

//靜態(tài)設(shè)置Header值
@Headers("Authorization: authorization")//這里authorization就是上面方法里傳進(jìn)來變量的值
@GET("widget/list")
Call<User> getUser()

@Headers 用于修飾方法,用于設(shè)置多個(gè)Header值:

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

自定義Converter

retrofit默認(rèn)情況下支持的converts有Gson,Jackson,Moshi...
要自定義Converter<F, T>,需要先看一下GsonConverterFactory的實(shí)現(xiàn),GsonConverterFactory實(shí)現(xiàn)了內(nèi)部類Converter.Factory。

其中GsonConverterFactory中的主要兩個(gè)方法,主要用于解析request和response的,在Factory中還有一個(gè)方法stringConverter,用于String的轉(zhuǎn)換。

//主要用于響應(yīng)體的處理,F(xiàn)actory中默認(rèn)實(shí)現(xiàn)為返回null,表示不處理
 @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }

/**
  *主要用于請(qǐng)求體的處理,F(xiàn)actory中默認(rèn)實(shí)現(xiàn)為返回null,不能處理返回null
  *作用對(duì)象Part、PartMap、Body
  */
  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
//Converter.Factory$stringConverter
/**
  *作用對(duì)象Field、FieldMap、Header、Path、Query、QueryMap
  *默認(rèn)處理是toString
  */
  public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
          Retrofit retrofit) {
        return null;
      }

GsonRequestBodyConverter實(shí)現(xiàn)了Converter<F, T>
接口,主要實(shí)現(xiàn)了轉(zhuǎn)化的方法
T convert(F value) throws IOException;

StringConverterFactory實(shí)現(xiàn)源碼

自定義Interceptor

Retrofit 2.0 底層依賴于okHttp,所以需要使用okHttp的Interceptors 來對(duì)所有請(qǐng)求進(jìn)行攔截。我們可以通過自定義Interceptor來實(shí)現(xiàn)很多操作,打印日志,緩存,重試等等。

要實(shí)現(xiàn)自己的攔截器需要有以下步驟
(1) 需要實(shí)現(xiàn)Interceptor接口,并復(fù)寫intercept(Chain chain)方法,返回response
(2) Request 和 Response的Builder中有header,addHeader,headers方法,需要注意的是使用header有重復(fù)的將會(huì)被覆蓋,而addHeader則不會(huì)。

標(biāo)準(zhǔn)的 Interceptor寫法

public class OAuthInterceptor implements Interceptor {

  private final String username;
  private final String password;

  public OAuthInterceptor(String username, String password) {
    this.username = username;
    this.password = password;
  }

  @Override public Response intercept(Chain chain) throws IOException {

    String credentials = username + ":" + password;

    String basic = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);

    Request originalRequest = chain.request();
    String cacheControl = originalRequest.cacheControl().toString();

    Request.Builder requestBuilder = originalRequest.newBuilder()
        //Basic Authentication,也可用于token驗(yàn)證,OAuth驗(yàn)證
        .header("Authorization", basic)
        .header("Accept", "application/json")
        .method(originalRequest.method(), originalRequest.body());

    Request request = requestBuilder.build();

    Response originalResponse = chain.proceed(request);
    Response.Builder responseBuilder =
        //Cache control設(shè)置緩存
        originalResponse.newBuilder().header("Cache-Control", cacheControl);

    return responseBuilder.build();
  }
}

緩存策略

設(shè)置緩存就需要用到OkHttp的interceptors,緩存的設(shè)置需要靠請(qǐng)求和響應(yīng)頭。如果想要弄清楚緩存機(jī)制,則需要了解一下HTTP語義,其中控制緩存的就是Cache-Control
字段參考:Retrofit2.0+okhttp3緩存機(jī)制以及遇到的問題How Retrofit with OKHttp use cache data when offline使用Retrofit和Okhttp實(shí)現(xiàn)網(wǎng)絡(luò)緩存。無網(wǎng)讀緩存,有網(wǎng)根據(jù)過期時(shí)間重新請(qǐng)求

一般情況下我們需要達(dá)到的緩存效果是這樣的:
沒有網(wǎng)或者網(wǎng)絡(luò)較差的時(shí)候要使用緩存(統(tǒng)一設(shè)置)
有網(wǎng)絡(luò)的時(shí)候,要保證不同的需求,實(shí)時(shí)性數(shù)據(jù)不用緩存,一般請(qǐng)求需要緩存(單個(gè)請(qǐng)求的header來實(shí)現(xiàn))。

OkHttp3中有一個(gè)Cache類是用來定義緩存的,此類詳細(xì)介紹了幾種緩存策略,具體可看此類源碼。

  • noCache :不使用緩存,全部走網(wǎng)絡(luò)
  • noStore : 不使用緩存,也不存儲(chǔ)緩存
  • onlyIfCached : 只使用緩存
  • maxAge :設(shè)置最大失效時(shí)間,失效則不使用
  • maxStale :設(shè)置最大失效時(shí)間,失效則不使用
  • minFresh :設(shè)置最小有效時(shí)間,失效則不使用
  • FORCE_NETWORK : 強(qiáng)制走網(wǎng)絡(luò)
  • FORCE_CACHE :強(qiáng)制走緩存
配置目錄

這個(gè)是緩存文件的存放位置,okhttp默認(rèn)是沒有緩存,且沒有緩存目錄的。

 private static final int HTTP_RESPONSE_DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024;

  private Cache cache() {
         //設(shè)置緩存路徑
         final File baseDir = AppUtil.getAvailableCacheDir(sContext);
         final File cacheDir = new File(baseDir, "HttpResponseCache");
         //設(shè)置緩存 10M
         return new Cache(cacheDir, HTTP_RESPONSE_DISK_CACHE_MAX_SIZE);
     }

其中獲取cacahe目錄,我們一般采取的策略就是應(yīng)用卸載,即刪除。一般就使用如下兩個(gè)目錄:

  • data/$packageName/cache:Context.getCacheDir()
  • /storage/sdcard0/Andorid/data/$packageName/cache:Context.getExternalCacheDir()

且當(dāng)sd卡空間小于data可用空間時(shí),使用data目錄。
最后來一張圖看懂Android內(nèi)存結(jié)構(gòu),參考:Android文件存儲(chǔ)使用參考 - liaohuqiu

 /**
     * |   ($rootDir)
     * +- /data                    -> Environment.getDataDirectory()
     * |   |
     * |   |   ($appDataDir)
     * |   +- data/$packageName
     * |       |
     * |       |   ($filesDir)
     * |       +- files            -> Context.getFilesDir() / Context.getFileStreamPath("")
     * |       |      |
     * |       |      +- file1     -> Context.getFileStreamPath("file1")
     * |       |
     * |       |   ($cacheDir)
     * |       +- cache            -> Context.getCacheDir()
     * |       |
     * |       +- app_$name        ->(Context.getDir(String name, int mode)
     * |
     * |   ($rootDir)
     * +- /storage/sdcard0         -> Environment.getExternalStorageDirectory()/ Environment.getExternalStoragePublicDirectory("")
     * |                 |
     * |                 +- dir1   -> Environment.getExternalStoragePublicDirectory("dir1")
     * |                 |
     * |                 |   ($appDataDir)
     * |                 +- Andorid/data/$packageName
     * |                                         |
     * |                                         | ($filesDir)
     * |                                         +- files                  -> Context.getExternalFilesDir("")
     * |                                         |    |
     * |                                         |    +- file1             -> Context.getExternalFilesDir("file1")
     * |                                         |    +- Music             -> Context.getExternalFilesDir(Environment.Music);
     * |                                         |    +- Picture           -> Context.getExternalFilesDir(Environment.Picture);
     * |                                         |    +- ...               -> Context.getExternalFilesDir(String type)
     * |                                         |
     * |                                         |  ($cacheDir)
     * |                                         +- cache                  -> Context.getExternalCacheDir()
     * |                                         |
     * |                                         +- ???
     * <p/>
     * <p/>
     * 1.  其中$appDataDir中的數(shù)據(jù),在app卸載之后,會(huì)被系統(tǒng)刪除。
     * <p/>
     * 2.  $appDataDir下的$cacheDir:
     * Context.getCacheDir():機(jī)身內(nèi)存不足時(shí),文件會(huì)被刪除
     * Context.getExternalCacheDir():空間不足時(shí),文件不會(huì)實(shí)時(shí)被刪除,可能返回空對(duì)象,Context.getExternalFilesDir("")亦同
     * <p/>
     * 3. 內(nèi)部存儲(chǔ)中的$appDataDir是安全的,只有本應(yīng)用可訪問
     * 外部存儲(chǔ)中的$appDataDir其他應(yīng)用也可訪問,但是$filesDir中的媒體文件,不會(huì)被當(dāng)做媒體掃描出來,加到媒體庫(kù)中。
     * <p/>
     * 4. 在內(nèi)部存儲(chǔ)中:通過  Context.getDir(String name, int mode) 可獲取和  $filesDir  /  $cacheDir 同級(jí)的目錄
     * 命名規(guī)則:app_ + name,通過Mode控制目錄是私有還是共享
     * <p/>
     * <code>
     * Context.getDir("dir1", MODE_PRIVATE):
     * Context.getDir: /data/data/$packageName/app_dir1
     * </code>
     */
緩存第一種類型

配置單個(gè)請(qǐng)求的@Headers,設(shè)置此請(qǐng)求的緩存策略,不影響其他請(qǐng)求的緩存策略,不設(shè)置則沒有緩存。

// 設(shè)置 單個(gè)請(qǐng)求的 緩存時(shí)間
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
緩存第二種類型

有網(wǎng)和沒網(wǎng)都先讀緩存,統(tǒng)一緩存策略,降低服務(wù)器壓力。

private Interceptor cacheInterceptor() {
      Interceptor cacheInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);

                String cacheControl = request.cacheControl().toString();
                if (TextUtils.isEmpty(cacheControl)) {
                    cacheControl = "public, max-age=60";
                }
                return response.newBuilder()
                        .header("Cache-Control", cacheControl)
                        .removeHeader("Pragma")
                        .build();
            }
        };
      }

此中方式的緩存Interceptor實(shí)現(xiàn):ForceCachedInterceptor.java

緩存第三種類型

結(jié)合前兩種,離線讀取本地緩存,在線獲取最新數(shù)據(jù)(讀取單個(gè)請(qǐng)求的請(qǐng)求頭,亦可統(tǒng)一設(shè)置)。

private Interceptor cacheInterceptor() {
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();

                if (!AppUtil.isNetworkReachable(sContext)) {
                    request = request.newBuilder()
                            //強(qiáng)制使用緩存
                            .cacheControl(CacheControl.FORCE_CACHE)
                            .build();
                }

                Response response = chain.proceed(request);

                if (AppUtil.isNetworkReachable(sContext)) {
                    //有網(wǎng)的時(shí)候讀接口上的@Headers里的配置,你可以在這里進(jìn)行統(tǒng)一的設(shè)置
                    String cacheControl = request.cacheControl().toString();
                    Logger.i("has network ,cacheControl=" + cacheControl);
                    return response.newBuilder()
                            .header("Cache-Control", cacheControl)
                            .removeHeader("Pragma")
                            .build();
                } else {
                    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                    Logger.i("network error ,maxStale="+maxStale);
                    return response.newBuilder()
                            .header("Cache-Control", "public, only-if-cached, max-stale="+maxStale)
                            .removeHeader("Pragma")
                            .build();
                }

            }
        };
    }

此中方式的緩存Interceptor實(shí)現(xiàn):OfflineCacheControlInterceptor.java

錯(cuò)誤處理

在請(qǐng)求網(wǎng)絡(luò)的時(shí)候,我們不止會(huì)得到HttpException,還有我們和服務(wù)器約定的errorCode和errorMessage,為了統(tǒng)一處理,我們可以預(yù)處理以下上面兩個(gè)字段,定義BaseModel,在ConverterFactory中進(jìn)行處理,可參照:
Retrofit+RxJava實(shí)戰(zhàn)日志(3)-網(wǎng)絡(luò)異常處理
retrofit-2-simple-error-handling

網(wǎng)絡(luò)狀態(tài)監(jiān)聽

一般在沒有網(wǎng)絡(luò)的時(shí)候使用緩存數(shù)據(jù),有網(wǎng)絡(luò)的時(shí)候及時(shí)重試獲取最新數(shù)據(jù),其中獲取是否有網(wǎng)絡(luò),我們采用廣播的形式:

ublic class NetWorkReceiver extends BroadcastReceiver {

     @Override
     public void onReceive(Context context, Intent intent) {
         HttpNetUtil.INSTANCE.setConnected(context);
     }
 }

HttpNetUtil實(shí)時(shí)獲取網(wǎng)絡(luò)連接狀態(tài),關(guān)鍵代碼

 /**
     * 獲取是否連接
     */
    public boolean isConnected() {
        return isConnected;
    }
   /**
     * 判斷網(wǎng)絡(luò)連接是否存在
     *
     * @param context
     */
    public void setConnected(Context context) {
        ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (manager == null) {
            setConnected(false);


            if (networkreceivers != null) {
                for (int i = 0, z = networkreceivers.size(); i < z; i++) {
                    Networkreceiver listener = networkreceivers.get(i);
                    if (listener != null) {
                        listener.onConnected(false);
                    }
                }
            }

        }

        NetworkInfo info = manager.getActiveNetworkInfo();

        boolean connected = info != null && info.isConnected();
        setConnected(connected);

        if (networkreceivers != null) {
            for (int i = 0, z = networkreceivers.size(); i < z; i++) {
                Networkreceiver listener = networkreceivers.get(i);
                if (listener != null) {
                    listener.onConnected(connected);
                }
            }
        }

    }

在需要監(jiān)聽網(wǎng)絡(luò)的界面或者base(需要判斷當(dāng)前activity是否在棧頂)實(shí)現(xiàn)Networkreceiver。

Retrofit封裝

全局單例的OkHttpClient:

okHttp() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        okHttpClient = new OkHttpClient.Builder()
                //打印日志
                .addInterceptor(interceptor)

                //設(shè)置Cache目錄
                .cache(CacheUtil.getCache(UIUtil.getContext()))

                //設(shè)置緩存
                .addInterceptor(cacheInterceptor)
                .addNetworkInterceptor(cacheInterceptor)

                //失敗重連
                .retryOnConnectionFailure(true)

                //time out
                .readTimeout(TIMEOUT_READ, TimeUnit.SECONDS)
                .connectTimeout(TIMEOUT_CONNECTION, TimeUnit.SECONDS)

                .build()

        ;
    }

全局單例的Retrofit.Builder,這里返回builder是為了方便我們?cè)O(shè)置baseUrl的,我們可以動(dòng)態(tài)創(chuàng)建多個(gè)api接口,當(dāng)然也可以用@Url注解

Retrofit2Client() {
        retrofitBuilder = new Retrofit.Builder()
                //設(shè)置OKHttpClient
                .client(okHttp.INSTANCE.getOkHttpClient())

                //Rx
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())

                //String轉(zhuǎn)換器
                .addConverterFactory(StringConverterFactory.create())

                //gson轉(zhuǎn)化器
                .addConverterFactory(GsonConverterFactory.create())
        ;
    }

Retrofit2+RxJava 使用Demo:Retrofit2Demo
參考:Articles tagged in: Retrofit
官方文檔
Retrofit2 完全解析 探索與okhttp之間的關(guān)系
Retrofit 2.0 + OkHttp 3.0 配置
更新到Retrofit2的一些技巧
Effective OkHttp
Okhttp-wiki 之 Interceptors 攔截器
Retrofit2.0+okhttp3緩存機(jī)制以及遇到的問題
How Retrofit with OKHttp use cache data when offline
使用Retrofit和Okhttp實(shí)現(xiàn)網(wǎng)絡(luò)緩存。無網(wǎng)讀緩存,有網(wǎng)根據(jù)過期時(shí)間重新請(qǐng)求
用 Retrofit 2 簡(jiǎn)化 HTTP 請(qǐng)求
Retrofit請(qǐng)求參數(shù)注解字段說明
Android文件存儲(chǔ)使用參考 - liaohuqiu
Retrofit+RxJava實(shí)戰(zhàn)日志(3)-網(wǎng)絡(luò)異常處理
retrofit-2-simple-error-handling

轉(zhuǎn)載至:BoBoMEe的文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 隨著Google對(duì)HttpClient 摒棄,和Volley的逐漸沒落,OkHttp開始異軍突起,而Retr...
    BoBoMEe閱讀 80,535評(píng)論 13 144
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,058評(píng)論 25 709
  • 前言 在Android開發(fā)中,網(wǎng)絡(luò)請(qǐng)求十分常用 而在Android網(wǎng)絡(luò)請(qǐng)求庫(kù)中,Retrofit是當(dāng)下最熱的一個(gè)網(wǎng)...
    Carson帶你學(xué)安卓閱讀 71,487評(píng)論 48 395
  • 對(duì)于錢 幾百幾千沒感覺。三八節(jié)那天刷了一萬五,之前阿光刷了兩萬。瞬間身無分文,沒有存錢的能力。沒有錢的壓力 ,必須...
    行一館閱讀 115評(píng)論 0 0
  • 日課7:仔細(xì)回顧自己的生活中,已經(jīng)做到的并行和串行的事情。并思索你的生活中還有哪些任務(wù)可以多任務(wù)操作? 以前不知道...
    Leice閱讀 348評(píng)論 2 2

友情鏈接更多精彩內(nèi)容