Retrofit 2.0 詳解(一)基本用法

什么是 Retrofit ?

Retrofit是Square開發(fā)的一個Android和Java的REST客戶端庫。這個庫非常簡單并且具有很多特性,相比其他的網(wǎng)絡庫,更容易讓初學者快速掌握。

Retrofit配置

在 app/build.gradle 添加依賴
在這里我們最好查看一下retrofit的官網(wǎng)添加最新依賴。

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

創(chuàng)建retrofit實例

String baseUrl = "http://192.168.1.8:8080/RetrofitService/";
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .build();

創(chuàng)建Retrofit實例時需要通過Retrofit.Builder,并調(diào)用baseUrl方法設置URL。
注: Retrofit2 的baseUlr 必須以 /(斜線) 結(jié)束,不然會拋出一個IllegalArgumentException,所以如果你看到別的教程沒有以 / 結(jié)束。

接口定義

以獲取指定id的News為例:

public interface NewsService {
    //普通call方式
    @GET("NewsServlet")
    Call<ResponseBody> getNews(@Query("id") int id);
}

注意,這里是interface不是class,所以我們是無法直接調(diào)用該方法,我們需要用Retrofit創(chuàng)建一個NewsService的代理對象。

NewsService newsService = createRetrofit().create(NewsService.class);

拿到代理對象之后,就可以調(diào)用該方法啦。

接口調(diào)用

 Call<ResponseBody> answers = newsService.getNews(1);
        answers.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    Log(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                t.printStackTrace();
            }
        });

Retrofit注解詳解

上面提到Retrofit 共22個注解,這節(jié)就專門介紹這22個注解,為幫助大家更好理解我將這22個注解分為三類,并用表格的形式展現(xiàn)出來,表格上說得并不完整,具體的見源碼上的例子注釋

第一類:HTTP請求方法
HTTP請求方法注解.png

以上表格中的除HTTP以外都對應了HTTP標準中的請求方法,而HTTP注解則可以代替以上方法中的任意一個注解,有3個屬性:method、pathhasBody下面是用HTTP注解實現(xiàn)上面的例子。

     /**
     * method 表示請求的方法,區(qū)分大小寫
     * path表示路徑
     * hasBody表示是否有請求體
     */
    @HTTP(method = "GET", path = "NewsServlet", hasBody = false)
    Call<ResponseBody> getNews1(@Query("id") int id);

注:method 的值 retrofit 不會做處理,所以要自行保證其準確性,之前使用小寫也可以是因為示例源碼中的服務器不區(qū)分大小寫,所以希望大家注意。

第二類:標記類
標記類注解.png

FormUrlEncoded注解接口的2種寫法

    /**
     * {@link FormUrlEncoded} 表明是一個表單格式的請求(Content-Type:application/x-www-form-urlencoded)
     * <code>Field("username")</code> 表示將后面的 <code>String name</code> 中name的取值作為 username 的值
     */
    @POST("/NewsServlet")
    @FormUrlEncoded
    Call<ResponseBody> testFormUrlEncoded1(@Field("name") String name, @Field("age") int age);
    /**
     * Map的key作為表單的鍵
     */
    @POST("/NewsServlet")
    @FormUrlEncoded
    Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);

分別對應調(diào)用的2種方法

第一種:

NewsService newsService = createRetrofit().create(NewsService.class);
Call<ResponseBody> answers = newsService.testFormUrlEncoded1("txy", 18);

第二種:

NewsService newsService = createRetrofit().create(NewsService.class);
HashMap<String, Object> map = new HashMap<>();
map.put("name", "txy");
map.put("age", 18);
Call<ResponseBody> answers = newsService.testFormUrlEncoded2(map);

Multipart注解接口的3種寫法,Multipart一般是上傳文件的時候使用

    /**
     * {@link Part} 后面支持三種類型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意類型
     * 除 {@link okhttp3.MultipartBody.Part} 以外,其它類型都必須帶上表單字段({@link okhttp3.MultipartBody.Part} 中已經(jīng)包含了表單字段的信息),
     */
    @POST("NewsServlet")
    @Multipart
    Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

    /**
     * PartMap 注解支持一個Map作為參數(shù),支持 {@link RequestBody } 類型,
     * 如果有其它的類型,會被{@link retrofit2.Converter}轉(zhuǎn)換,如后面會介紹的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
     * 所以{@link MultipartBody.Part} 就不適用了,所以文件只能用<b> @Part MultipartBody.Part </b>
     */
    @POST("NewsServlet")
    @Multipart
    Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);

    @POST("NewsServlet")
    @Multipart
    Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);

對應的3種調(diào)用方式

第一種:

        NewsService newsService = createRetrofit().create(NewsService.class);
        MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "txy");
        RequestBody age = RequestBody.create(textType, "18");
        //構(gòu)建要上傳的文件
        File file = new File(Environment.getExternalStorageDirectory(), "paoche.jpg");
        RequestBody requestFile =
                RequestBody.create(MediaType.parse("application/otcet-stream"), file);
        MultipartBody.Part filePart =
                MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
        Call<ResponseBody> answers = newsService.testFileUpload1(name, age, filePart);

第二種:

        NewsService newsService = createRetrofit().create(NewsService.class);
        MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "txy");
        RequestBody age = RequestBody.create(textType, "18");
        Map<String, RequestBody> fileUpload2Args = new HashMap<>();
        fileUpload2Args.put("name", name);
        fileUpload2Args.put("age", age);
        //構(gòu)建要上傳的文件
        File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
        RequestBody requestFile =
                RequestBody.create(MediaType.parse("application/otcet-stream"), file);
        MultipartBody.Part filePart =
                MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
        Call<ResponseBody> answers = newsService.testFileUpload2(fileUpload2Args, filePart);

第二種:

        NewsService newsService = createRetrofit().create(NewsService.class);
        MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "txy");
        RequestBody age = RequestBody.create(textType, "18");
        Map<String, RequestBody> fileUpload3Args = new HashMap<>();
        fileUpload3Args.put("name", name);
        fileUpload3Args.put("age", age);
        //構(gòu)建要上傳的文件
        File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
        RequestBody requestFile =
                RequestBody.create(MediaType.parse("application/otcet-stream"), file);
        fileUpload3Args.put("fileUploader\"; filename=\"paoche3.jpg",requestFile);
        Call<ResponseBody> answers = newsService.testFileUpload3(fileUpload3Args);

第三類:參數(shù)類
參數(shù)類注解.png

注1:{占位符}和PATH盡量只用在URL的path部分,url中的參數(shù)使用QueryQueryMap 代替,保證接口定義的簡潔
注2: Query、FieldPart這三者都支持數(shù)組和實現(xiàn)了Iterable接口的類型,如ListSet等,方便向后臺傳遞數(shù)組。

Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//結(jié)果:ids[]=0&ids[]=1&ids[]=2

Field、FieldMap、PartPartMap 前面的例子已經(jīng)講過。
下面說一下HeaderHeaders的二種用法、靜態(tài)和動態(tài)我自己理解的,看下接口怎么寫
第一種靜態(tài)方式:

    //靜態(tài)添加Header
    @Headers("Cache-Control: max-age=640000")
    @GET("NewsServlet")
    Call<ResponseBody> testHeader1();
    //靜態(tài)添加多個Header
    @Headers({"X-Foo: Bar","X-Ping: Pong"})
    @GET("NewsServlet")
    Call<ResponseBody> testHeader2();

第二種動態(tài)方式:

    //動態(tài)添加Header
    @GET("NewsServlet")
    Call<ResponseBody> testHeader3(@Header("Cache-Control") int header, @Query("name") String name);
    //動態(tài)添加Header
    @GET("NewsServlet")
    Call<ResponseBody> testHeader4(@HeaderMap Map<String, String> headers, @Query("name") String name);

Gson與Converter

在默認情況下Retrofit只支持將HTTP的響應體轉(zhuǎn)換換為ResponseBody,這也是什么我在前面的例子接口的返回值都是 Call<ResponseBody>,但如果響應體只是支持轉(zhuǎn)換為ResponseBody的話何必要引用泛型呢,返回值直接用一個Call就行了嘛,既然支持泛型,那說明泛型參數(shù)可以是其它類型的,而Converter就是Retrofit為我們提供用于ResponseBody轉(zhuǎn)換為我們想要的類型,有了Converter之后我們就可以寫把我們的第一個例子的接口寫成這個樣子了:

public interface NewsService {
    //普通call方式
    @GET("NewsServlet")
    Call<News> getNews(@Query("id") int id);
}

當然只改變泛型的類型是不行的,我們在創(chuàng)建Retrofit時需要明確告知用于將ResponseBody轉(zhuǎn)換我們泛型中的類型時需要使用的Converter

引入Gson支持:

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

通過GsonConverterFactory為Retrofit添加Gson支持:

Gson gson = new GsonBuilder()
        //配置你的Gson
        .setDateFormat("yyyy-MM-dd hh:mm:ss")
        .create();
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        //可以接收自定義的Gson,當然也可以不傳
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

說道Gson我們就先聊一下有些Http服務返回一個固定格式的數(shù)據(jù)的問題。 例如:

{
 "code": 0,
 "message": "成功",
 "data": {}
}

或:

{
 "code": 0,
 "message": "成功",
 "data": []
}

不一樣的地方就是data是一個對象或?qū)ο髷?shù)組,這種情況下咱們怎么統(tǒng)一處理,我們可以創(chuàng)建一個BaseModel類

public class BaseModel<T> {
    public int resultCode;
    public String resultMessage;
    public  T data;
}

以demo返回的2種數(shù)據(jù)為例
data是普通對象

{"code":200,"data":{"comments":[{"content":"我是內(nèi)容"}],"date":"20170603","id":1,"likes":"我喜歡新聞","title":"我是標題","views":"我是views"},"message":"獲取成功!"}

以第一個例子為例咱們看下怎么修改,Call的泛型填寫B(tài)aseModel<News>即可

public interface NewsService {
    //普通call方式
    @GET("NewsServlet")
    Call<BaseModel<News>> testConverter1(@Query("id") int id);
}

data是普通對象數(shù)組

{"code":200,"data":[{"comments":[{"content":"我是內(nèi)容"}],"date":"20170603","id":1,"likes":"我喜歡新聞","title":"我是標題","views":"我是views"}],"message":"獲取成功!"}

Call的泛型填寫B(tài)aseModel<List<News>>即可

public interface NewsService {
    //普通call方式
    @POST("NewsServlet")
    Call<BaseModel<List<News>>> testConverter1();
}

結(jié)合我們現(xiàn)在用的只傳JSON的請求,請求headercontent-typeapplication/json,也就是@Body的用法寫一個列子:

@POST("NewsServlet")
Call<BaseModel<List<News>>> testConverte3(@Body News news);

@Body注解的的Blog將會被Gson轉(zhuǎn)換成RequestBody發(fā)送到服務器。
下面咱們看下接口怎么調(diào)用

NewsService newsService = createRetrofit().create(NewsService.class);
News news = new News();
news.likes = "likes";
news.date = "20170619";
news.title = "Converter用法3";
news.id = 1;
news.views = "views";
Call<BaseModel<List<News>>> call = newsService.testConverter3(news);

服務器收到的數(shù)據(jù)就是如下這樣

{"comments":[],"date":"20170619","id":1,"likes":"likes","title":"Converter用法3","views":"views"}

RxJava與CallAdapter

說到Retrofit就不得說到另一個火到不行的庫RxJava,網(wǎng)上已經(jīng)不少文章講如何與Retrofit結(jié)合,但這里還是會有一個RxJava的例子,不過這里主要目的是介紹使用CallAdapter所帶來的效果。

注意:下面說的RxJava是指的RxJava2.0

第3節(jié)介紹的Converter是對于Call<T>T的轉(zhuǎn)換,而CallAdapter則可以對Call轉(zhuǎn)換,這樣的話Call<T>中的Call也是可以被替換的,而返回值的類型就決定你后續(xù)的處理程序邏輯,同樣Retrofit提供了多個CallAdapter,這里以RxJava的為例,用Observable代替Call

引入RxJava支持:

compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
// 針對rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

通過RxJavaCallAdapterFactory為Retrofit添加RxJava支持:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(baseUrl)
    //可以接收自定義的Gson,當然也可以不傳
    .addConverterFactory(GsonConverterFactory.create(gson))
    // 針對rxjava2.x
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build();

接口設計:

public interface NewsService {
    @GET("NewsServlet")
    Observable<BaseModel<List<News>>> testCallAdapter();
}

使用:

NewsService newsService = createRetrofit().create(NewsService.class);
Observable<BaseModel<List<News>>> observable = newsService.testCallAdapter();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<BaseModel<List<News>>>() {
            @Override
            public void onSubscribe(Disposable d) {
                //運行在io線程
            }

            @Override
            public void onNext(BaseModel<List<News>> value) {
                Log("RXJava用法 onNext:" + FastJsonUtils.toJson(value));
            }

            @Override
            public void onError(Throwable e) {
                Log("RXJava用法 onError:" + e.getMessage());
            }

            @Override
            public void onComplete() {
                Log("RXJava用法 onComplete");
            }
        });

結(jié)果:

{"code":200,"data":[{"comments":[{"content":"我是內(nèi)容"}],"date":"20170603","id":1,"likes":"我喜歡新聞","title":"我是標題","views":"我是views"}],"success":true}

有的時候我們需要獲取原始的json的字符串,GSON是滿足不了我們的,下面我們來看下怎么獲取原始的json串,我們需要借助另一個Converter,后面也會介紹都有哪些Converter

引入scalars支持:

compile 'com.squareup.retrofit2:converter-scalars:2.0.2'

通過ScalarsConverterFactoryRetrofit添加String支持:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(baseUrl)
    //添加ScalarsConverterFactory支持
    .addConverterFactory(ScalarsConverterFactory.create())
    //可以接收自定義的Gson,當然也可以不傳
    .addConverterFactory(GsonConverterFactory.create(gson))
     // 針對rxjava2.x
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build();

注意: 如果需要既支持String又支持Gson需要先設置ScalarsConverterFactory 后設置 GsonConverterFactory 如果上所示。

接口

@GET("NewsServlet")
Observable<String> testScalars();

看下怎么調(diào)用

Observable<String> observable = newsService.testScalars();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<String>() {
            @Override
            public void onSubscribe(Disposable d) {
            }
            @Override
            public void onNext(String value) {
                Log("Scalar用法 onNext value:" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log("Scalar用法 onError " +e.getMessage());
            }
            @Override
            public void onComplete() {
            }
        });

像上面的這2種情況最后我們無法獲取到返回的Header和響應碼的,如果我們需要這兩者,提供兩種方案:
1、用Observable<Response<T>> Observable<T> ,這里的Responseretrofit2.Response
2、用Observable<Result<T>>代替Observable<T>,這里的Result是指retrofit2.adapter.rxjava.Result,這個Result中包含了Response的實例

看下第一種接口怎么設計

@GET("NewsServlet")
Observable<Response<BaseModel<List<News>>>> testRxAndroid2();

可以理解在以前BaseModel<List<News>>的外面把包裝一層Response最終Response<BaseModel<List<News>>>,接下來看下怎么調(diào)用

Observable<Response<BaseModel<List<News>>>> observable = newsService.testRxAndroid2();        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<Response<BaseModel<List<News>>>>() {
            @Override
            public void onSubscribe(Disposable d) {
            }
            @Override
            public void onNext(Response<BaseModel<List<News>>> value) {
                Headers headers = value.headers();
                Set<String> names = headers.names();
                Log("RXJava用法2 onNext value:" + FastJsonUtils.toJson(value.body()));
                Log("RXJava用法2 onNext headers:" + FastJsonUtils.toJson(names));
                BaseModel<List<News>> body = value.body();
                Log("RXJava用法2 onNext body:" + FastJsonUtils.toJson(body));
            }
            @Override
            public void onError(Throwable e) {
                Log("RXJava用法2 onError:"+e.getMessage());
            }
            @Override
            public void onComplete() {
                Log("RXJava用法2 onComplete");
            }
        });

通過Responseheaders()方法獲取所有header

其它說明

Retrofit.Builder

前面用到了 Retrofit.Builder 中的baseUrladdCallAdapterFactory、addConverterFactory、build方法,還有callbackExecutor、callFactory、clientvalidateEagerly這四個方法沒有用到,這里簡單的介紹一下。

方法 用途
callbackExecutor(Executor) 指定Call.enqueue時使用的Executor,所以該設置只對返回值為Call的方法有效
callFactory(Factory) 設置一個自定義的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就實現(xiàn)了okhttp3.Call.Factory接口,下面的client(OkHttpClient)最終也是調(diào)用了該方法,也就是說兩者不能共用
client(OkHttpClient) 設置自定義的OkHttpClient,以前的Retrofit版本中不同的Retrofit對象共用同OkHttpClient,在2.0各對象各自持有不同的OkHttpClient實例,所以當你需要共用OkHttpClient或需要自定義時則可以使用該方法,如:處理Cookie、使用stetho 調(diào)式等
validateEagerly(boolean) 是否在調(diào)用create(Class)時檢測接口定義是否正確,而不是在調(diào)用方法才檢測,適合在開發(fā)、測試時使用
Retrofit的Url組合規(guī)則
BaseUrl 和URL有關(guān)的注解中提供的值 最后結(jié)果
http://localhost:8080/RetrofitService/NewsServlet /post http://localhost:8080/post
http://localhost:8080/RetrofitService/NewsServlet post http://localhost:8080/RetrofitService/NewsServlet/post
http://localhost:8080/RetrofitService/NewsServlet http://www.itdecent.cn/ http://www.itdecent.cn/

從上面不能難看出以下規(guī)則:

  • 如果你在注解中提供的url是完整的url,則url將作為請求的url。

  • 如果你在注解中提供的url是不完整的url,且不以 / 開頭,則請求的url為baseUrl+注解中提供的值

  • 如果你在注解中提供的url是不完整的url,且以 / 開頭,則請求的url為baseUrl的主機部分+注解中提供的值

Retrofit提供的Converter
Converter Gradle依賴
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2
Retrofit提供的CallAdapter
CallAdapter Gradle依賴
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

結(jié)語

本篇博客已經(jīng)完成,后續(xù)我還會出進階版本,包括調(diào)用https請求、下載文件、報文加密等等。最后感謝 怪盜kidou 老鐵,本篇博客好多都是借鑒的這位兄弟的,添加了一些自己的理解。

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

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

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