retrofit 學習筆記

ps: 我寫這篇這是很晚了,也是怪我自己啊,一直以來拖拖拉拉的就是改不了,retrofit 看著好幾次了,看完有些日子就記不清了,也沒記錄下來,忘了都沒地方找去。所以今天好好理一理 retrofit ,注解的使用還是很有必要記記的,誰也不敢打包票自己不忘是不,尤其是我,我對于這可是有深刻的印象啊,16年那會完全不寫博客,看了好多東西,看完就忘,結果發現16年的收獲慘不忍睹啊。

另外說一點,寫博客真是對知識點的理解和記憶是大大的加深啊,所以大家總是說好記性不如爛筆頭呀

retrofit 集成,簡單使用


  1. 添加依賴
    // retrofit 依賴
    compile 'com.squareup.retrofit2:retrofit:2.4.0'
    compile 'com.squareup.retrofit2:converter-gson:2.4.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
  1. 編寫遠程接口
    retrofit 使用直接注解動態生成遠程請求 API ,這讓我們專注于接口聲明,而不用去管理實現,既簡單又明了
public interface BlueService {
    @GET("book/search")
    Call<ResponseBody> getSearchBooks(@Query("q") String name,
                                      @Query("tag") String tag, @Query("start") int start,
                                      @Query("count") int count);
}
  1. 原生簡單請求
        String baseUrl = "https://api.douban.com/v2/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .build();

        BlueService blueService = retrofit.create(BlueService.class);
        blueService.getSearchBooks("小王子", "", 0, 3)
                .enqueue(new Callback<ResponseBody>() {
                    @Override
                    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                        try {
                            String message = response.body().string();

                            Log.d("AAA", "onResponse: " + message);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                    }

                    @Override
                    public void onFailure(Call<ResponseBody> call, Throwable t) {
                        Log.d("AAA", "onFailure: 聯網失敗");
                    }
                });

注意啊:

  • baseUrl 跟路徑必須以 “ / ” 結尾,現在強制要求了,不寫會報錯
  • response.body().string() 可以獲取原始的 response 數據(字符串),我們使用的 .string() 而不是 .toString() , toString 出來的 class 名字。同時返回參數數據類型我們必須使用 ResponseBody 這個才行,這是 okhttp3 里面的,這樣才能拿到原始響應體
  • 這個寫法一般是獲取 json 轉 bean 或是測試通不通用的,正常我們不這么用
  1. 添加 json 數據變換
        String baseUrl = "https://api.douban.com/v2/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        BlueService blueService = retrofit.create(BlueService.class);
        blueService.getSearchBooks("小王子", "", 0, 3)
                .enqueue(new Callback<BookResponse>() {
                    @Override
                    public void onResponse(Call<BookResponse> call, Response<BookResponse> response) {
                        try {
                            BookResponse bookResponse = response.body();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }

                    @Override
                    public void onFailure(Call<BookResponse> call, Throwable t) {
                        Log.d("AAA", "onFailure: 聯網失敗");
                    }
                });

這個我們在 retrofit.build 時添加了類型轉換器,就可以讓 retrofit 自動幫我們轉換 json 了,然后遠程接口,callback 我們都可以直接使用具體的數據類型了。我們還是 try 一下保險

  1. 添加 rxjava 支持,變換請求結果為 observable
public interface BlueService {
    @GET("book/search")
    Observable<BookResponse> getSearchBooks(@Query("q") String name,
                                            @Query("tag") String tag, @Query("start") int start,
                                            @Query("count") int count);
}
        String baseUrl = "https://api.douban.com/v2/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        BlueService blueService = retrofit.create(BlueService.class);

        blueService.getSearchBooks("小王子", "", 0, 3)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<BookResponse>() {

                    Disposable disposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                    }

                    @Override
                    public void onNext(BookResponse bookResponse) {
                        Log.d("AAA", "onNext: " + bookResponse.getBooks().size());
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {
                        disposable.dispose();
                    }
                });
    }

要注意 rxjava2 的支持和1不同,rxjava2 的支持請安上面的依賴添加

中間插一段 http 協議基礎


我也是個搬運工,http 協議我摘一點作用明顯的,大家看看,然后覺得不怎么懂就去專門看下,我在下面會附上地址的

HTTP 的請求報文分為三個部分 請求行、請求頭和請求體,格式如圖:


1724103-c43900117e983241.png
  1. 請求行

請求行(Request Line)分為三個部分:請求方法、請求地址和協議及版本,HTTP/1.1 定義的請求方法有8種:

  • GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的兩種GET和POST
  • RestFul 規范的接口的話會用到 GET、POST、DELETE、PUT,分別對應數據庫的增刪改查操作:
    • PUT -> 增
    • DELETE -> 刪
    • POST -> 改
    • GET -> 查

在了解請求地址之前,先了解一下URL的構成:


1724103-95c263da5671d6fa.png

所以我們在 retrofit 的 baseUrl 添加跟地址的行為就是添加 http 協議和主機地址

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(baseUrl)

剩下的 http 基礎內容還有很多,我就不復制了,大家看這里:你應該知道的HTTP基礎知識,一定要看哦,要不后面你會暈,有地方搞不明白的

  1. 請求頭
    其中有一點我們一定要弄清楚的是 http 請求體類型,也就是我們會看到的 Content-Type
  • Content-Type:application/json
    表示這是提交的 json
  • Content-Type:application/x-www-form-urlencoded
    表單提交
  • Content-Type:multipart/form-data; boundary={boundary}
    文件提交,也叫模擬表單

重頭戲來啦,注解詳解


整篇文章的重點就在這啦,前面的都是浮云,初次使用的同學可能覺得有些繁瑣,有些不好理解,但是對于有點水平的同學們來說就不叫事了不是,所x以注解這個才是我們需要重點學習的,看了上面的 http 協議的部分知識點,我們再去研究下 retrofit 的注解就不是那么撓頭了

1. 請求類型

請求類型是請求頭中的一個內容,寫在 header 里,字段是:Content-Type,是描述我們給服務器發送的數據采用什么格式發送,下面放個例子:

interface APIStore {
   @Headers({"Content-Type: application/json","Accept: application/json"})
   @POST ("vdyweb/ws/rest/Login")
   Call<ResponseBody>getMessage(@Body RequestBody info);   
}

大家看到在請求頭中添加了 Content-Type 字段的數據,這里總結下有幾種請求方式啊,這點很重要的,寫不對你的遠程通訊就會不同,或是服務器拿不到數據哦。retrofit 已經為我們封裝好了常用的請求方式,這里結合的說一下:

  • 什么都不寫
    什么都不寫表示傳遞參數沒有規范,發送過去的是二進制流,傳值你得用 @Body 注解,服務器接受到的是二進制流,然后可以轉成字符串按 json 解析數據,不過一般我么不推薦這么寫。
  • json 提交
    json 的話我司現在就是用這種方法,適合做 post 萬能請求接口,不過在 RestFul 規范的今天, 不在推薦這么寫了,我們也得讓后臺兄弟舒服不是。所以我們在 retrofit 找不到這個注解,就是因為不推薦這種寫法了, 下面是自己寫的方式,這里只是接口聲明,詳細在后面
interface APIStore {
   @Headers({"Content-Type: application/json","Accept: application/json"})
   @POST ("vdyweb/ws/rest/Login")
   Call<ResponseBody>getMessage(@Body  Book book);  
}

@Body 的數據對象,Retrofit2 會自動轉成 Gson 字符串發送

  • get 方式
    get 方式很特殊,大家都知道 get 請求方式很特殊,把一切都拼接在 url 地址中,所以沒那么復雜, Content-Type 不用設置
  • 表單提交
    表單提交,數據都以 key—value 的形式存在請求體中,服務器獲取參數也是用 key—value 的形式去拿數據,作為最常使用的提交方式,retrofit 已經給我們封裝好了,專門有一個字段
    @FormUrlEncoded
    Observable<BookResponse> getSearchBooks(@Query("q") String name);

自己寫的話就是

    @Headers("Content-Type:application/x-www-form-urlencoded")
    Observable<BookResponse> getBooks();
  • 模擬表單
    在表單提交的基礎上可以添加文件數據,retrofit 也給我們封裝好了
    @Multipart
    Observable<BookResponse> getBooks();

自己寫的話就是

    @Headers("Content-Type:multipart/form-data; boundary={boundary}")
    Observable<BookResponse> getBooks();

boundary 部分可寫可不些,我看有人不寫 boundary 也沒事,boundary=xxxxxxx,xxxxxx規定了請求體中的內容分隔符

  • 文檔相關資料


    retrofit3.png

2. 請求方法

請求方法上面 http 基礎應說過了,這個不難理解,需要注意的是 restFul 規范下的部分:
* PUT -> 增
* DELETE -> 刪
* POST -> 改
* GET -> 查
這里要提一下 http 這個請求方法,HTTP注解則可以代替任意一個青谷去方法,有3個屬性:method,path,hasBody,下面是例子:

public interface BlogService {
    /**
     * method 表示請的方法,不區分大小寫
     * path表示路徑
     * hasBody表示是否有請求體
     */
    @HTTP(method = "get", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getFirstBlog(@Path("id") int id);
} 

3. 數據接收類型

在 http 協議中可以指定接受服務器返回數據的類型,這個一般我們不用自己去設置,retrofit 默認就是 json 格式的,但是自己也可以設,使用的是上面可以看到的 Accept 這個字段

@Headers({"Content-Type: application/json","Accept: application/json"})

寫在 head 請求頭了,看到這大家可以發現,這些設置除了 get/post 之外,所有的請求設置都是寫在請求頭里的,請求頭就是干這事的,類似于我們常用的 config ,包裹配置參數和一些通用參數。
retrofit 還提供了 @Streaming 這個注解,我們接收的數據不再是 json 的了,而是一個二進制輸入流。

    @Streaming

4. 傳參相關參數

retrofit4.png

retrofit 用來在不同請求方法中傳遞參數的注解都在上面了,單說沒意思,結合 get / post 和請求不同的數據來說最簡單明了,容易懂。

5. get 傳參詳解

    @GET("xxxUrl")
    Observable<User> getUserInfo(
            @Part("xxxUrl") String userUrl,
            @Query("userid") int id,
            @QueryMap Map<String, String> options,
            @Query("list") List<String> options2);

可以看到 get 可以接受4種傳參注解:

  • @Part -1
    用來替換具體的 utl 路徑的,一般我們都是直接就寫 url 地址了,這里通過 @Part 提供一種動態傳入 url 地址方式,@Part 注解是所有請求方法通用的。
  • @Part -2
    上面的 @Part 寫法是整體替換具體的 URL 請求,其實要是后臺同學的 restFul API 寫的規范的話,我們請求,操作數據的 URL 也是可以做到動態的,不是費的一個接口寫一個固定的地址給我們。這時 @Part 可以用來實現動態替換 URL 中參數的事,使用 {“xxx”} 來標記 URL 中可變的部分,下面就是一個例子:
    @GET("book/{id}")
    Observable<User> getBook(@Part("id") String id);
  • @Query
    傳單個參數,Query("userid") 里面的字符串是 key,后面指定 value 的類型
  • @QueryMap
    可以接受多個參數,所有參數直接寫在 map 里
  • Query("list")
    get 方式支持直接傳入一個集合,可以看到其實和我們單個傳參一樣,只不過區別是接收的數據是單個對象,還是一個集合對象

6. post 傳參詳解

    @POST("xxxUrl")
    Observable<User> getUserInfo(
            @Field("userid") int id,
            @FieldMap Map<String, String> options,
            @Field("list") List<String> options2);

post 傳參其實和 get 差不多,卻別不多,一個是 query ,一個是 field

  • @Field
    傳單個參數,和 @Query 一樣
  • @FieldMap
    傳多個數據
  • @Field("list")
    同樣支持直接傳遞集合數據類型
  • @POST
    @POST 除了get 都能用,用來傳遞一個數據對象,服務器按 stream 流的方式接收數據
    @POST("name")
    Observable<User> getName(@Body Book book);

7. 上傳文件

上傳文件一直是個有問題的地方,不管是 Xutils,okhttp,retrofit 都有不是很明確的地方,這里就能看出網絡基礎是多么重要,有時候你寫完之后,服務器接收不到參數,或是問你發的數據用的什么類型,我怎么接受,你都回答不上來,這個很尷尬不是,我是遇到過,還好后臺的兄弟沒打我臉,哈哈哈哈.......

不看我寫的,看看這些文章也是可以的:

有一點我們要說,表單提交的時候參數的編碼問題 enctype屬性。enctype:規定了form表單在發送到服務器時候編碼方式,他有如下的幾個值:

  • application/x-www-form-urlencoded
    默認值,不寫就是這個,retrofit 的表單默認也是這個,不能用來傳文件,也就是二進制數據
  • multipart/form-data
    所有的數據以二進制流發送
  • application/otcet-stream
    很奇怪,我查了查是八進制.........這個恕我真的不知道為啥有二進制不用,去用八進制
  • text/plain
    純文本格式

在帶有 file 的表單提交中:

  • file 只能用流的形式上傳
    • 可以用:multipart/form-data / application/otcet-stream 這2種流上傳,差別估計是后臺接受數據的 API 不同。
  • 文本參數可以用流的方式上傳,也可以用文本格式上傳
    • 可以使用 multipart/form-data 這樣后臺用流接數據,getParame 拿不到數據的。
    • 最好用 text/plain 文本格式傳,這樣后臺獲取數據和原來的方式相同。

好了看過這些,我們心里總算是對這些彎彎繞的東西有些初步了解了,再去和后臺溝通也大概知道怎么說了,查資料也知道去找什么東東了

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadMultipleFiles(
            @Part("description") RequestBody description,
            @PartMap Map<String, RequestBody> options
            @Part MultipartBody.Part file1,
            @Part MultipartBody.Part file2);

這是一個帶其他參數的文件上傳 API ,大家注意看注解如何使用,這個是固定的,大家背下來就行。期中 @Part("description") 里面的字符串是普通文本參數的 key

我們來看看重點的 java 代碼:

  • 創建文本參數
    注意這里我用的是 text/plain 發送文本參數,用 multipart/form-data 二進制的方式也可以的
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), "文本參數");
  • 創建 file 參數
        File imageFile = new File("一張圖片");
        RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
        MultipartBody.Part data = MultipartBody.Part.createFormData("file", imageFile.getName(), requestBody);
  • 文件數據傳入 map 集合中的寫法
    可能我們的接口是這樣的
    @POST("NewsServlet")
    @Multipart
    Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> map);

所有參數都放在這個集合中,集合參數的放置如下,注意 file 類型的參數的 key 要包含 file 的name 進去,需要自己拼接字符串的

        NewsService newsService = createRetrofit().create(NewsService.class);
         Map<String, RequestBody> fileUpload3Args = new HashMap<>();

        MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "txy");
        RequestBody age = RequestBody.create(textType, "18");

        fileUpload3Args.put("name", name);
        fileUpload3Args.put("age", age);

        //構建要上傳的文件
        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)

上傳文件基本就是這些了,這塊容易忘,容易混,記下來非常有必要的。

8. 上傳圖片

上傳圖片看這篇也行,總結的也很好,比我全面一點:

圖片也是文件,但是為啥要單獨拿出來說呢,因為圖片有自己單獨的數據格式 : image/png

RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), imageFile);
MultipartBody.Part photo = MultipartBody.Part.createFormData("上傳的key", "文件名.png", photoRequestBody);

9. 添加請求頭參數

請求頭參數我們可以在網絡請求的 API 接口里寫,也可以給 okhttp 添加一個攔截器進來,在請求構建完成,發送前的那一刻攔截,然后添加請求頭數據,這樣適合添加動態可變參數

  • @Headers 添加靜態請求頭參數
public interface BlueService {
    @Headers("Cache-Control: max-age=640000")
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 
            @Query("tag") String tag, @Query("start") int start, 
            @Query("count") int count);
}

注意添加一條和多條的區別

  • @Header 添加動態請求頭參數
public interface BlueService {
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(
    @Header("Content-Range") String contentRange, 
    @Query("q") String name, @Query("tag") String tag, 
    @Query("start") int start, @Query("count") int count);
}
  • 添加攔截器動態添加請求頭參數
public class HeadInterceptor implements Interceptor {
    @Override
    public Response intercept(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 = new OkHttpClient.Builder()
                .addInterceptor(new HeadInterceptor ())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

添加 header 參數 Request 提供了兩個方法:

  • header(key, value)
    如果有重名的將會覆蓋
  • ddHeader(key, value)
    允許相同 key 值的 header 存在

10. 為某個請求設置完整的URL

有個別的請求的 URL 不用用的我們基礎的 baseurl 的,怎么辦,我們可以使用 @Url 注解來忽略 baseurl 的

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

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

BlueService service = retrofit.create(BlueService.class);  
service.profilePicture("https://s3.amazon.com/profile-picture/path");

json 上傳的再次說明


json 上傳數據不推薦,但是有時候我們真的需要,這里多記錄一下找到的東西。

json 除了我們直接 @Body Book book 直接寫具體的數據類型的做法,我們也可以使用 RequestBody 來寫

public interface PostRoute {  
   @Headers({"Content-Type: application/json","Accept: application/json"})
    @POST("api/FlyRoute/Add")  
   Call<FlyRouteBean> postFlyRoute(@Body RequestBody route)RequestBody  
}  
// 先 json 一個字符串數據出來
Book book= new Book ();  
Gson gson=new Gson();  
String route = gson.toJson(book);

// 我們生成一個 RequestBody 請求對象出來
PostRoute postRoute=retrofit.create(PostRoute.class);  
RequestBody body=RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),route);  
Call<Book> call=postRoute.postFlyRoute(body);  

添加日志管理


這個是 okhttp 的部分,但是呢我們不打算專門寫一個 okhttp 的入了,在 retrofit 中我們已經充分使用了 okhttp 了,索性就一起寫了

retrifit 又開源的日志攔截器,要不我們就自己寫攔截器打印日志,不過肯定大的不如直接誒個開源的全啊。

添加依賴

compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

使用

        // 創建日志攔截器對象出來
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        //包含header、body數據
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        // 添加日志攔截器到 okhttp 對象中
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();

        // 把 okhttp 對象添加到 retrofit 對象中
        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

打印樣式圖:


436713-a7e0267212e9f809.png

注意里面 --> 、<-- 箭頭的方向:

  • --> 表示我們請求的內容,包括請求頭參數,請求信息等,這里我們是最簡單的 get 請求所以表現不出來
  • <-- 是我們接受到的內容,包括接受數據形式
  • interceptor 有幾個級別,我們用 body 就行,body 會代印包括頭在內的所有信息

facebook 網絡調試器 Stetho


我還看到有一位朋友提到 facebook 開源了 Stetho 網絡監測工具,試了試我沒成功,有興趣的朋友請看:使用OkHttp高效開發調試

其他內容


對于 https 證書有兩種策略:

參考資料


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容