Retrofit & OkHttp系列(一)

亂七八糟的個人感慨(個人淺見,勿噴!!)

不特別願意使用Retrofit框架,每加一個接口都需要到接口裏定義方法,還要添加相關請求實現,每次請求都要做這麼幾步,私下裏也曾想過封裝成泛型,實踐後發現有點問題,註解Method返回的Call這裏會出問題,不能取到明確類型。

這個框架你不能說個人用着感覺不方便就放棄,不去了解,但從團隊考慮隊友都會用,而你不會這就是你的不對了,所以一定要會熟練使用Retrofit+Okhttp框架,至於RX系列目前還沒到那種必須會的程度,自行考量吧。

目前呢很多人說你怎麼還在用xutils 、volley這些過時的框架啊?我只想說:能被很多人使用過的技術,說明他也有閃光點,這點誰都不能抹殺,很多新興的框架出現,比現有的框架還要完美,這並不是說現有框架就是過時了,一個項目用什麼框架取決於項目大小、團隊配合、項目維護成本等因素。最近有些人再吹RX+Dagger2+Retrofit2+Okhttp3+MVP,聽上去是那麼回事,讓人感覺你很nb,但是你在想想,如果我的項目只有三個接口5個簡單的界面,再採用這一套框架,代碼要多些多少呢?開發週期又會增加多少呢?如果你突然辭職了,公司招人不會RX 、Dagger2這些框架咋辦呢?這些都是技術成本,都是你爲公司留下的。作爲程序員僅僅考慮代碼寫得漂亮,有沒有考慮過產出的效率問題呢?有沒有爲公司考慮過呢?

最後在說一下框架過時這個問題,每個流行過的框架,他的代碼設計思想、模式等技術核心都值得我們去學習,如果你想說他過時了,就看不起它了,那麼我想請問你兩三個問題:你很牛逼麼?你是大牛你能寫出這樣的框架麼?請把你寫好的託管在github的框架地址給我一個,可以麼?


Retrofit

配置開發環境

Retrofit requires at minimum Java 7 or Android 2.3.

要求開發環境JDK1.7+ android SDK 2.3+,目前Retrofit出了多個版本,項目導入建議使用release版本,最新版本自行跟蹤項目託管地址

//github地址: https://github.com/square/retrofit

compile 'com.squareup.retrofit2:retrofit:2.1.0'


基本的請求流程

導入項目後,通過ServiceGenerator方法將創建HTTP客戶端包括頭定義的授權,調用自己定義的接口註解的方法獲取返回值。關於這裏要注意區分1.9版本與2.0+是有區別的,個人使用2.0版本這裏就不提1.x版本相關知識,如想了解更多請參考這裏(下面提到的Retrofit都是隻Retrofit2.0別咬文嚼字啊)

// https://futurestud.io/tutorials/android-basic-authentication-with-retrofit

在2.0以後Retrofit不再依賴Gson,使用需要自己添加依賴(當然你也可以使用Jackson)

compile 'com.squareup.retrofit2:converter-gson:2.1.0'

請求流程概要分爲以下幾步(這裏以發送驗證碼爲例)

  • 定義響應實體類ResponseBean.java

  • 定義接口SendAuthCodeService

/**
 * Created by idea on 2016/11/18.
 */
public interface SendAuthCodeService {

    @POST
    Call<ResponseBean> sendAuthCode();
}
  • 定義Service

public class ServiceGenerator {

    public static final String API_BASE_URL = "";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL).....略..

    public static <S> S createService(Class<S> serviceClass, final String phone) {

          //................略過攔截器處理..........................

        OkHttpClient client = httpClient.build();
        Retrofit retrofit = builder.client(client).build();
        return retrofit.create(serviceClass);
    }
}
  • 調用(Call的方法有異步和同步,使用時各取所需)

    public void sendAuthCode(String phone){
        SendAuthCodeService sendAuthCodeService =
                ServiceGenerator.createService(SendAuthCodeService.class, "15828331414");
        Call<ResponseBean > responseBean = sendAuthCodeService.sendAuthCode();
        responseBean.enqueue(new Callback<ResponseBean>(){
            @Override
            public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {

            }

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

            }
        });
    }

請求攜帶token和令牌驗證

每個app賬戶登錄基本都會有這麼一個token值,作爲訪問憑證,每次訪問都要攜帶,Okhttp有攔截器這麼個功能可以幫我們實現,至於攔截器這塊知識稍後再提。下面是相關實現代碼塊

 public static <S> S createService(Class<S> serviceClass, final String authToken) {
        if (authToken != null) {
            httpClient.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Interceptor.Chain chain) throws IOException {
                    Request original = chain.request();

                    // Request customization: add request headers
                    Request.Builder requestBuilder = original.newBuilder()
                            .header("Authorization", authToken)
                            .method(original.method(), original.body());

                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }

經常我們登錄緩存了token,而每次需要同步用戶信息就可以用到這個玩意兒了,代碼調用示例如下(OAuth 這塊略過,知識點都差不多。)

UserService userService =  
    ServiceGenerator.create(UserService.class, "auth-token");
Call<User> call = userService.getUserInformation();  
User user = call.execute().body();  

一個key對應1個或多個value的GET請求查詢

服務器的api無非就是增刪改查,客戶端傳遞參數執行這些操作。傳遞這些參數在Retrofit框架下該怎麼使用呢?且看下面代碼塊

public interface UserService {

    @GET("/user")
    Call<User> getUserInformation(@Query("userId") long userId);
}

只需要註解Query的字段參數即可,這裏補充說明一點:@GET @POST這些作用在方法之上的註解可以含參,用於拼接完整api接口,當@GET或@POST註解的url爲全路徑時,會直接使用註解的url。(從某個層面來講,對外只有一個接口,具體訪問哪個接口,只需要把這裏GET/POST的Path作爲參數通過post提交可能會更好一點)

baseUrl = "http://www.baidu.com"

@GET("/user")

requestUrl = "http://www.baidu.com/user"

// 補充:@Url作爲參數傳遞,@Url註解的路徑如果爲全路徑,則直接使用它,否則通過BaseUrl+@Url拼接

如遇到如下需求: hr林妹妹要統計公司員工都是211、985工程院校畢業有多少人?這個查詢條件多個值怎麼辦呢?Retrofit對這種需求也是信手拈來

public interface UserService {  

    @GET("/user")
    Call<Integer> getUserCount(@Query("school") List<Integer> schools);
}

schools: [211,985]

拼接後的Url: http://www.baidu.com/user?school=211&school=985


同步異步請求

同步請求

上面有提到過同步和異步請求,這裏簡單來了解相關知識。下面是一個同步請求接口定義實例代碼

public interface UserService {  

    @GET("/user")
    Call<User> getUserInformation(@Query String token);
}

同步請求的數據處理如下

Call<User> call = userService.getUserInformation("Uid1232342");  
user = call.execute().body();

Warning: 同步請求可能是導致APP在4.0及以上版本崩潰,你可能會得到一個Exception 的異常錯誤,爲了不阻塞UI,一般通過handler message來解決,這種使用方法很不爽的

異步請求

異步請求通過接口回調方式返回結果,也是我們最常用的方式,與同步請求從方法來來比較:excute()、enqueue(Callback)

 Call<ResponseBean > responseBean = sendAuthCodeService.sendAuthCode();
 responseBean.enqueue(new Callback<ResponseBean>(){
      @Override
      public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {

      }

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

     }
  });

如果你需要最原始的響應內容而不是經過映射後的數據,可以通過onResponse()response參數獲取(一般情況下用不到)

Response raw = response.raw();

請求參數使用@Body註解發送Object

請求的Body部分不再像以前那樣直接key value 這樣直接設置鍵值對,而是把請求參數封裝成對象,使用註解 @Body

public interface TaskService {  
    @POST("/tasks")
    Call<Task> createTask(@Body Task task);
}

public class Task {  
    private long id;
    private String text;

    public Task(long id, String text) {
        this.id = id;
        this.text = text;
    }
}

Task task = new Task(1, "my task title");  
Call<Task> call = taskService.createTask(task);  
call.enqueue(new Callback<Task>() {});  

上面這種方式在某種情況下,你會發現請求不成功,一直提示你請求參數格式有問題,這時候可能是服務器端的api可能僅僅支持form表單提交,這裏需要@FieldMap註解標籤,把對象轉換一下


響應參數映射 轉換器

請求響應內容JSON數據得映射轉換,Retrofit提供了幾種,最常用的就是GSON了(Jackson效率最高)

這塊我使用的就是GsonConverterFactory,這裏需要注意一下,如果你compile的版本是2.0及以下版本是沒有這個類的,我用的2.1.0.當然你也可以自定義Converter轉換器.

   retrofit = new Retrofit.Builder()
                    .baseUrl(Constact.BASE_URL)
                    .callFactory(OkhttpHelper.getOkhttpClient())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();

添加請求頭部

爲請求添加請求頭,主要方案有一下三種

  • 靜態請求頭

  • 動態請求頭

  • 請求頭攔截器

添加頭部請求頭靜態的方式也分爲兩種:單一請求頭和多個請求頭
public interface UserService {  
    @Headers("Cache-Control: max-age=640000")
    @GET("/tasks")
    Call<List<Task>> getTasks();
}

public interface UserService {  
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("/tasks/{task_id}")
    Call<Task> getTask(@Path("task_id") long taskId);
}
添加攔截器,通過攔截請求添加請求頭
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        Request request = original.newBuilder()
            .header("User-Agent", "Your-App-Name")
            .header("Accept", "application/vnd.yourapi.v1.full+json")
            .method(original.method(), original.body())
            .build();

        return chain.proceed(request);
    }
}

OkHttpClient client = httpClient.build();  
Retrofit retrofit = new Retrofit.Builder()  
    .baseUrl(API_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .client(client)
    .build();
添加動態請求頭
public interface UserService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);
}

Retrofit2 與Retrofit1添加請求頭截然不同,如果你想使用Retrofit1請忽略這篇blog.


@Query 註解的可選參數

根據查詢條件獲取請求結果,使用@GET請求註解+@Query/@QueryMap

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(@Query("sort") String order);
}

如果你使用的 BaseUrl https://your.api.com,調用上面接口的方法,傳入參數值=1,拼接後的請求路徑就是:

https://your.api.com/tasks?sort=1

註解@Query的參數支持多種類型:int, float, long等

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(
        @Query("sort") String order,
        @Query("page") Integer page);
}

How to Integrate XML Converter?關於這個xml解析這塊略過了(一般情況下不會用到,都JSON+GSON),使用也挺簡單一個套路。


Debug 調試請求,打印log日誌

在開發中打印網絡請求和返回結果是非常重要的,如果你在Retrofit中使用它,你會發現實現該功能的方法已經不可用了。

RestAdapter.Builder builder = new RestAdapter.Builder()  
    .setEndpoint(API_LOCATION)
    .setLogLevel(RestAdapter.LogLevel.FULL) // this is the important line
    .setClient(new OkClient(new OkHttpClient()));

Retrofit 依靠okhttp提供的日誌系統,HttpLoggingInterceptor.需要添加相關依賴,使用類似上面提到的添加頭部攔截器一樣。

個人更喜歡使用Logger庫配合輸出,所以自定義HttpLoggingInterceptor最好。修改HttpLoggingInterceptor.Logger接口類名定義和log方法調用即可

 public interface Logger {
    void log(String message);

    /** A {@link Logger} defaults output appropriate for the current platform. */
    Logger DEFAULT = new Logger() {
      @Override public void log(String message) {
        Platform.get().log(INFO, message, null);
      }
    };
  }

如何上傳文件到服務器

文件上傳有很多種方式,先大致列舉一二

  • File轉String上傳

  • 文件流上傳

  • byte字節上傳

  • 文件上傳

上傳的文件又分爲單一文件和多個文件,在Retrofit下面又該如何編碼呢?按照流程還是的定義interface,@Multipart註解方法,@Part註解參數


/**
 * Created by idea on 2016/11/28.
 */
public interface ApiService {

    @Multipart
    @POST("/upload")
    Call<ResponseBean> uploadPhone(@Part("description") RequestBody requestBody,@Part MultipartBody.Part part);
}

RequestBody在這裏進行一下拓展,RequestBody源自Okhttp3的一個類,writeTo方法依賴於Okio框架,這塊知識有想要了解的自行補腦。

MediaType這裏列舉三類

  • text/plain; charset=utf-8 //構建字符串請求體

  • application/octet-stream //構建字節請求體 、文件請求體

  • application/json; charset=utf-8 //post上傳json
    ……………

通過調用RequestBody定義好的create方法即可。當然獲取RequestBody實例方法不止這一種,大致可分爲三類

  • create()
/**
 * 此方法在使用Retrofit基本不會用到 post參數傳入  
 */
public Request getRequest(){
  Request request = new Request.Builder()  
     .url(baseUrl)  
     .post()
     .build();
  return request;
}
  • FormBody.Builder()
/**
 * 請求表單構建
 */
public RequestBody getRequestBody(){
   RequestBody formBody=new FormBody.Builder()  
                .add("name","idea")  
                .add("sex","1")       
                .build();  
   return request;             
}
  • MultipartBody.Builder()
 /**
  * MultipartBody.builder構建RequestBody
  */
 public void getRequestBody(String imageUrl,String uuid){
        RequestBody multipartBody=new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("uuid", uuid)
                .addFormDataPart("avatar", "20160808324.jpg",      RequestBody.create(MediaType.parse("application/octet-stream"),new File(imageUrl)))
                .addPart(...)
                .build();
        return multipartBody;
    }

addPart(..)方法用於添加RequestBody,Headers和添加自定義Part

下面是一段上傳文件的代碼塊

 public void onUploadImage(String imageUrl,String description,ApiService apiService){
        File file = new File(imageUrl);
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
        RequestBody descriptionRequestBody = RequestBody.create(MediaType.parse("multipart/form-data"), description);

        Call<ResponseBean> call = apiService.uploadPhone(descriptionRequestBody,body);
        call.enqueue(new Callback<ResponseBean>() {
            @Override
            public void onResponse(Call<ResponseBean> call,Response<String> response) {

            }

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

            }
        });
    }

這裏使用multipart/form-data,它和其他有什麼區別呢?

1.application/x-www-form-urlencoded

這是通過表單發送數據時默認的編碼類型。我們沒有在from標籤中設置enctype屬性時默認就是application/x-www-form-urlencoded類型的。application/x-www-form-urlencoded編碼類型會把表單中發送的數據編碼爲名稱/值對。這是標準的編碼格式。當表單的ACTION爲POST的時候,瀏覽器把form數據封裝到http body中,然後發送到服務器。當表單的ACTION爲GET的時候,application/x-www-form-urlencoded編碼類型會把表單中發送的數據轉換成一個字符串(name=coderbolg&key=php),然後把這個字符串附加到URL後面,並用?分割,接着就請求這個新的URL。

2.multipart/form-data

這個是專門用來傳輸特殊類型數據的,如我們上傳的非文本的內容,比如圖片或者MP3等。multipart/form-data編碼類型會把表單中的發送的數據編碼爲一條消息,頁面上每個表單控件對應消息中的一部分。當表單中有file類型控件並希望它正常工作的話(廢話吧)就必須設置成multipart/form-data類型,瀏覽器會把整個表單以控件爲單位分割,併爲每個部分加上Content-Disposition(form-data或者file),Content-Type(默認爲text/plain),name(控件 name)等信息,並加上分割符(boundary)。

3.text/plain

數據以純文本形式進行編碼,其中不含任何控件或格式字符。

以上資料三點摘自:http://www.fx114.net/qa-163-89638.aspx,平時上傳文件使用multipart/form-data基本沒問題,如果你想知道更多的HTTP Content-Type相關信息,可以參考下面鏈接

// http://tool.oschina.net/commons

Retrofit 2 與1.9的差異

Retrofit2與之前版本的具體差異變化自行參考(idea沒用過以以前版本的直接2.1.0)

// https://futurestud.io/tutorials/retrofit-2-upgrade-guide-from-1-9

這裏補充一點:假設項目你自己引入了高版本的Okhttp versionA,而使用的Retrofit2.x版本底層依賴的Okhttp的版本號versionB,明顯Okhttp重複引用,如果versionB 小於你當前引入versionA,當你刪除了versionA會發現method找不到等一些列問題,最好的解決辦法就是把Retrofit2依賴庫改成你當前compile的庫

compile ('com.squareup.retrofit2:retrofit:2.1.0') {  
  // exclude Retrofit’s OkHttp peer-dependency module and define your own module import
  exclude module: 'okhttp'
}
compile 'com.squareup.okhttp3:okhttp:3.4.1'  

Retrofit 2 錯誤異常處理

首先,我們創建一個錯誤對象,官方給出的ErrorUtils對個人來講並不太完美,我還是比較喜歡這樣

{
    code: 404,
    msg: "服務器連接失敗"
}

/**
 * Created by idea on 2016/11/9.
 */
public class AppErrorCode {
    /**
     * 未知錯誤
     */
    public static final int UNKNOWN = 1000;
    /**
     * 解析錯誤
     */
    public static final int PARSE_ERROR = 1001;
    /**
     * 網絡錯誤
     */
    public static final int NETWORK_ERROR = 1002;
    /**
     * 協議出錯
     */
    public static final int HTTP_ERROR = 1003;
}

/**
 * Created by idea on 2016/11/9.
 */
public class AppException extends Exception {
    private int code;
    private String msg;

    public AppException(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public static AppException handleException(Throwable e) {
        if (e instanceof JsonParseException
                || e instanceof IOException 
                || e instanceof JSONException
                || e instanceof ParseException) {
            return new ApiException(AppErrorCode.PARSE_ERROR, "解析錯誤");
        } else if (e instanceof ConnectException) {
            return new ApiException(AppErrorCode.NETWORK_ERROR, "連接失敗");
        } else if(){
            //.............略......................
        }else{
            return new ApiException(AppErrorCode.UNKNOWN, "未知錯誤");
        }
    }

}

舉個例子,當你convert response數據發生io異常是,只需要這樣

    try {
            error = converter.convert(response.errorBody());
        } catch (IOException e) {
            return AppException.handleException(e);
        }

表單提交@Field @FieldMap

表單提交可能存在一個key對應一個或多個值的情況,在Retrofit中編碼如下

public interface TaskService {  
    @FormUrlEncoded
    @POST("tasks")
    Call<Task> createTask(@Field("title") String title);
}

public interface TaskService {  
    @FormUrlEncoded
    @POST("tasks")
    Call<List<Task>> createTasks(@Field("title") List<String> titles);
}

當你的參數@Field參數過多了,可以嘗試用@FieldMap

public interface UserService {  
    @FormUrlEncoded
    @PUT("user")
    Call<User> update(@FieldMap Map<String, String> fields);
}

Retrofit 2 怎麼爲每個請求都添加 Query Parameters

第一個想到的就應該是攔截器了,intercept回調方法addQueryParameter

OkHttpClient.Builder httpClient =  
    new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        HttpUrl originalHttpUrl = original.url();

        HttpUrl url = originalHttpUrl.newBuilder()
                .addQueryParameter("apikey", "your-actual-api-key")
                .build();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                .url(url);

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});

Retrofit 2 Url語法

個人覺得這裏沒什麼好理解,參考實例看看就明白了


Retrofit 2 如何從服務器下載文件

從服務器下載文件主要分爲以下幾個步湊(個人更喜歡用開源庫進行下載,一般採用三級緩存+動態權限)

  • 如何改造Request
// option 1: a resource relative to your base URL
@GET("/resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();

// option 2: using a dynamic URL
@GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);  
  • 如何調用請求
FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);

call.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccess()) {
            Log.d(TAG, "server contacted and has file");

            boolean writtenToDisk = writeResponseBodyToDisk(response.body());

            Log.d(TAG, "file download was a success? " + writtenToDisk);
        } else {
            Log.d(TAG, "server contact failed");
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "error");
    }
});
  • 如何緩存文件
private boolean writeResponseBodyToDisk(ResponseBody body) {  
    try {
        // todo change the file location/name according to your needs
        File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + "Future Studio Icon.png");

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            byte[] fileReader = new byte[4096];

            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;

            inputStream = body.byteStream();
            outputStream = new FileOutputStream(futureStudioIconFile);

            while (true) {
                int read = inputStream.read(fileReader);

                if (read == -1) {
                    break;
                }

                outputStream.write(fileReader, 0, read);

                fileSizeDownloaded += read;

                Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
            }

            outputStream.flush();

            return true;
        } catch (IOException e) {
            return false;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }

            if (outputStream != null) {
                outputStream.close();
            }
        }
    } catch (IOException e) {
        return false;
    }
}
  • 載大文件使用@Streaming
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);  

final FileDownloadService downloadService =  
                ServiceGenerator.create(FileDownloadService.class);

new AsyncTask<Void, Long, Void>() {  
   @Override
   protected Void doInBackground(Void... voids) {
       Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
       call.enqueue(new Callback<ResponseBody>() {
           @Override
           public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               if (response.isSuccess()) {
                   Log.d(TAG, "server contacted and has file");

                   boolean writtenToDisk = writeResponseBodyToDisk(response.body());

                   Log.d(TAG, "file download was a success? " + writtenToDisk);
               }
               else {
                   Log.d(TAG, "server contact failed");
               }
           }
       return null;
   }
}.execute();    

Retrofit 2 — 取消 Requests

當前界面在請求中,突然要結束當前界面亦或者是有新的請求進來,需要取消之前的請求,此時可以調用Call提供的方法

new Callback<ResponseBody>() {  
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.d(TAG, "request success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                if(!call.isCanceled()){
                    call.cancel();
                }
                //..........................

            }
        };

Retrofit 2如何在運行時動態改變BaseUrl

一般情況下我們使用Retrofit2時都會進行一下封裝,把BaseUrl封裝進去。在某種情況下(上傳文件服務器和表單提交服務器不在一起),就需要我們改變BaseUrl,具體做法如下changeApiBaseUrl()

public class ServiceGenerator {  
    public static String apiBaseUrl = "http://futurestud.io/api";
    private static Retrofit retrofit;

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(apiBaseUrl);

    private static OkHttpClient.Builder httpClient =
            new OkHttpClient.Builder();

    // No need to instantiate this class.
    private ServiceGenerator() {
    }

    public static void changeApiBaseUrl(String newApiBaseUrl) {
        apiBaseUrl = newApiBaseUrl;

        builder = new Retrofit.Builder()
                        .addConverterFactory(GsonConverterFactory.create())
                        .baseUrl(apiBaseUrl);
    }

    public static <S> S createService(Class<S> serviceClass, AccessToken token) {
        String authToken = token.getTokenType().concat(token.getAccessToken());
        return createService(serviceClass, authToken);
    }

    // more methods
    // ...
}

請求的Path Parameters

如果你的url的路徑參數是正確的,但註解的路徑參數含有一個空字符串會導致錯誤的請求url

public interface TaskService {  
    @GET("tasks/{taskId}/subtasks")
    Call<List<Task>> getSubTasks(@Path("taskId") String taskId);
}

taskId傳遞空值將導致以下url

// https://your.api.url/tasks//subtasks  

不允許您傳遞null作爲路徑參數的值,如果你這樣做,就會拋出IllegalArgumentException


Retrofit 2響應內容爲String

有那麼一羣人不喜歡使用Gson、Jackson解析,喜歡自己封裝一套解析工具,那麼返回值需要爲String,我們就需要添加返回值的支持(返回他不支持的結果時,就會崩潰)

compile  'com.squareup.retrofit2:converter-scalars:2.1.0'  
private static Retrofit getRetrofit(String url) {
      return new Retrofit.Builder().baseUrl(url)
                //增加返回值爲String的支持
                .addConverterFactory(ScalarsConverterFactory.create())
                //增加返回值爲Gson的支持(以實體類返回)
                .addConverterFactory(GsonConverterFactory.create())
                //增加返回值爲Oservable<T>的支持
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

使用方面的知識點大概就這麼多了,以上內容多數參照https://futurestud.io/tutorials/retrofit-getting-started-and-android-client這裏,看完之後做個記錄,收穫還是不小的。

發佈了104 篇原創文章 · 獲贊 99 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章