1. 程式人生 > >Retrofit解析2之使用簡介

Retrofit解析2之使用簡介

整體Retrofit內容如下:

前面介紹完RESTful之後,我們先來初步認識下Retrofit的使用"姿勢"。本文的主要內容如下:

  • 1、Retrofit是什麼?
  • 2、Retrofit的配置
  • 3、Retrofit的那些註解
  • 4、為什麼要用Retrofit?
  • 5、Form表單提交與multipart/form-data
  • 6、Retrofit2 對multipart/form-data的支援
  • 7、總結

Retrofit的官網是這樣說的:

A type-safe HTTP client for Android and Java

我簡單翻譯一下就是:

一個型別安全的、Android或者Java的客戶端

通過使用註解去描述一個HTTP請求,並且支援Multipart請求和檔案上傳。

我理解的Retrfit: 一個可以簡化我們網路操作的工作的第三方庫。當然我們自己也可以實現,但是自己去實現帶來的是比較高的時間成本和檢驗成本。同樣,Retrofit是Square公司開源的一個高質量高效率的HTTP庫,它將我們自己開發的底層的程式碼和細節都封裝了起來,有了Retrofit之後我們對於一些請求我們就只需要一行程式碼或者一個註解。所有的網路通訊,其核心任務就只有一個就是:Client端與Server端進行資料和互動操作,所有Retrofit就將底層程式碼都封裝起來,只是暴露除了我們業務中的資料模型和操作方法。下面讓我們瞭解下Retrofit的配置

retrofit.png

二、Retrofit的配置

1、首選在你的專案build.gradle裡面新增如下配置

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

如果你序列化 採用GSON,同時回撥採用RxJava 處理,還應該新增如下內容

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

當然也別忘記新增網路許可權

<uses-permission android:name="android.permission.INTERNET" />

2、定義返回值實體類

這個類煲劇哦返回資料的所有屬性

public class GitHubRepo {  
    private int id;
    private String name;

    public GitHubRepo() {
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

3、定義網路介面

寫一個interface名字是GitHubService

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

4、建立Retrofit物件

 retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .client(client)//這個client是OkHttpClient,以後和Okhttp的基本用法和流程分析中細說
      //          .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .build();
                

5、建立Service物件

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

6、發起請求

Call<List<Repo>> repos = service.listRepos("octocat");

三、註解詳解

Retrofit 2.3 包含很多的註解,包括如下內容:

  • 1、方法註解:@GET @POST、@PUT、@DELETE、@PATCH、@OPTIONS、@HTTP
  • 2、標記註解:@FormUrlEncoded、@Multipart、@Streaming
  • 3、引數註解:@Query 、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap
  • 4、其它註解:@Path、@Header、@Headers、@Url

(一)、方法註解:

1、@GET:用於傳送一個get請求

@GET 註解一般必須新增相對路徑或者絕對路徑或者全路徑,如果不想用在@GET 註解後新增請求路徑,則可以在方法的第一個引數中用@Url 新增請求路徑。

2、@POST:用於傳送一個POST請求

@POST 註解一般必須新增相對路徑或絕對路徑或者全路徑,如果不想在@POST 後新增請求路徑,則可以在方法的第一個引數用@Url 註解新增請求路徑。

3、@PUT:用於傳送一個PUT請求

@PUT 註解一般必須新增相對路徑或者絕對路徑或者全路徑,如果不想在PUT註解後新增請求路徑,則可以在方法的第一個引數用@Url 註解新增請求路徑。

4、@DELETE:用於傳送一個DELETE請求

@DELETE 註解 一般必須新增相對路徑或者絕對路徑或者全路徑,如果不想在DELETE註解後新增請求路徑,則可以在方法的第一個引數中用@Url 註解新增請求路徑。

5、@PATCH:用於傳送一個PATCH請求

@PATCH 註解 一般必須新增相對路徑或絕對路徑或者全路徑,如果不想在PATCH註解後新增請求路徑,則可以在方法的第一惡引數用@Url 註解新增請求路徑

6、@OPTIONS:用於傳送一個OPTIONS請求

@OPTIONS 註解一般必須新增相對路徑或絕對路徑或者全路徑,如果不想在OPTIONS註解後新增請求路徑,則可以在方法的第一個引數用@Url 註解新增請求路徑。

7、@HTTP:作用於方法,用於傳送一個自定義的HTTP

如下所示:

//自定義HTTP請求的標準樣式
interface Service {
     @HTTP(method = "CUSTOM", path = "custom/endpoint/")
     Call<ResponseBody> customEndpoint();
   }
//傳送一個DELETE請求
interface Service {
     @HTTP(method = "DELETE", path = "remove/", hasBody = true)
     Call<ResponseBody> deleteObject(@Body RequestBody object);
   }

(二)、標記註解:

1、@FormUrlEncoded:用於修飾Fiedl註解 和FileldMap註解

使用該註解,表示請求正文將使用表單網址編碼。欄位應該宣告為引數,並用@Field 註解和 @FieldMap 註解,使用@FormUrlEncoded 註解的請求將具有"application/x-www-form-urlencoded" MIME型別。欄位名稱和值將先進行UTF-8進行編碼,再根據RFC-3986進行URI編碼。

2、@Multipart:作用於方法

使用該註解,表示請求體是多部分的,每個部分作為一個引數,且用Part註解宣告。

3、@Streaming:作用於方法

未使用@Straming 註解,預設會把資料全部載入記憶體,之後通過流獲取資料也是讀取記憶體中資料,所以返回資料較大時,需要使用該註解。 處理返回Response的方法的響應體,用於下載大檔案

@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);

提醒:如果是下載大檔案必須加上@Streaming 否則會報OOM

(三)、引數註解

1、@Query:作用於方法引數,用於新增查詢引數,即請求引數

引數值通過String.valueOf()轉換為String 並進行URL編碼,使用該註解定義的引數,引數值可以為空,為空時,忽略該值,當傳入一個List或array時,為每個非空item拼接請求鍵值對,所有的鍵是統一的,如:name=張三&name=李四&name=王五。

@GET("/list")
Call<ResponseBody> list(@Query("page") int page);
@GET("/list")
Call<ResponseBody> list(@Query("category") String category);
//傳入一個數組
@GET("/list")
Call<ResponseBody> list(@Query("category") String... categories);
//不進行URL編碼
@GET("/search")
Call<ResponseBody> list(@Query(value="foo", encoded=true) String foo);

2、@QueryMap:作用於方法的引數

以map的形式新增查詢引數,即請求引數,引數的鍵和值都通過String.valueOf()轉換為String格式。預設map的值進行URL編碼,map中的每一項發鍵和值都不能為空,否則跑出IllegalArgumentException異常。 示例如下:

//使用預設URL編碼
@GET("/search")
Call<ResponseBody> list(@QueryMap Map<String, String> filters);
//不使用預設URL編碼
@GET("/search")
Call<ResponseBody> list(@QueryMap(encoded=true) Map<String, String> filters);

3、@Body:作用於方法引數

使用@Body 註解定義的引數不能為null 當你傳送一個post或put請求,但是又不想作為請求引數或表單的方式傳送請求時,使用該註解定義的引數可以直接傳入一個實體類,retrofit會通過convert把該實體序列化並將序列化的結果直接作為請求體傳送出去。 示例如下: 先來看下實體類

   class Repo {
    final String owner;
    final String name;
    Repo(String owner, String name) {
      this.owner = owner;
      this.name = name;
    }
  }

再來看下介面類

  interface Service {
    @POST("/")
    Call<ResponseBody> sendNormal(@Body Repo repo);

4、@Field:作用於方法的引數

用String.valueOf()把引數值轉換為String,然後踐行URL編碼,當引數值為null是=時,會自動忽略,如果傳入的是一個List或者array,則為每一個非空的item拼接一個鍵值對,每一個鍵值對中的鍵是相同的,值就是非空的item的值。如:name=張三&name=李四&name=王五,如果itme的值有空格,在拼接的時候會自動忽略,例如某個item的值為:張 三,則拼接後為name=張三。 示例如下:

//普通引數
@FormUrlEncoded
@POST("/")
Call<ResponseBody> example(@Field("name") String name,@Field("occupation") String occupation);

//固定或可變陣列
@FormUrlEncoded
@POST("/list")
Call<ResponseBody> example(@Field("name") String... names);

5、@FieldMap:作用於方法的引數

map中的每一項的鍵和值都不能為空,否則丟擲IllegalArgumentException異常。 如下:

@FormUrlEncoded
@POST("/things")
Call<ResponseBody> things(@FieldMap Map<String, String> fields);

6、@Part:作用於方法的引數,用於定義Multipart請求的每和part

使用該註解定義的引數,引數值可以為空,為空時,則忽略。使用該註解定義的引數型別有如下3中方式可選:

  • 1 okhttp2.MulitpartBody.Part,內容將被直接使用。省略part中的名稱,即@Part MultipartBody.Part part
  • 2 如果型別是RequestBody,那麼該值直接與其內容型別一起使用。在註釋中提供part名稱(例如,@Part("foo") RequestBody foo)
  • 3 其它物件型別將通過使用轉換器轉換為適當的格式。在註釋中提供part名稱(例如,@Part("foo") Image photo)。

示例如下:

@Multipart
@POST("/")
Call<ResponseBody> example(
       @Part("description") String description,
       @Part(value = "image", encoding = "8-bit") RequestBody image);

7、@PartMap:作用於方法的引數

以map的方式定義Multipart請求的每個part map中每一項的鍵和值都不能為空,否則丟擲IllegalArgumentException異常。 使用@PartMap 註解定義的引數型別有一下兩種:

  • 1 如果型別是RequestBody,那麼該值將直接與其內容型別與其使用。
  • 2 其它物件型別將通過使用轉換器轉換為適當的格式。

(四)其他註解:

1、@Path:用於方法的引數

在URL路徑中替換指定引數值。使用String.valueOf()和URL編碼將值轉換為字串。 使用@Path 註解 定義的引數的值不能為空,引數值預設使用URL編碼。

2、@Header:作用於方法的引數,用於新增請求頭

使用 @Header 註解 定義的請求頭可以為空,當為空時,會自動忽略,當傳入一個List或者array時,為拼接每個非空的item的值到請求頭中。 具有相同名稱的請求頭不會相互覆蓋,而是照樣新增到請求頭中 程式碼如下:

@GET("/")
Call<ResponseBody> foo(@Header("Accept-Language") String lang);

3、@Headers:作用於方法,用於新增一個或多個請求頭中

具有相同名稱的請求頭不會相互覆蓋,而是會照樣新增到請求頭中。 示例如下:

//新增一個請求頭
 @Headers("Cache-Control: max-age=640000")
 @GET("/")

//新增多個請求頭
@Headers({ "X-Foo: Bar", "X-Ping: Pong"})
@GET("/")

4、@Url: 作用於方法引數

用於新增請求的介面地址: 程式碼如下:

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

(四)注意事項:

  • 1、Map用來組合複雜的引數,並且對於FieldMap,HeaderMap,PartMap,QueryMap這四種作用方法的註解,其引數型別必須為Map例項,且key的型別必須為String型別,否則丟擲異常。
  • 2、Query、QueryMap與Field、FieldMap功能一樣,生成的資料形式一樣;Query、QueryMap的資料體現在Url上;Field、FieldMap的資料是請求體
  • 3、{佔位符}和PATH儘量只用在URL的path部分,url的引數使用Query、QueryMap代替,保證介面的簡潔
  • 4、Query、Field、Part支援資料和實現了iterable介面的型別,如List、Set等,方便向後臺傳遞陣列,程式碼如下:
  • 5、以上部分註解真正的實現在ParameterHandler類中,每個註解的真正實現都是ParameterHandler類中的一個final型別的內部類,每個內部類都對各個註解的使用要求做了限制,比如引數是否可空、鍵和值是否可空等。
  • 6、@FormUrlEncoded 註解和@Multipart 註解不能同時使用,否則會丟擲methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod類中parseMethodAnnotation()方法中找到不能同時使用的具體原因。
  • 7、@Path 與@Url 註解不能同時使用,否則會丟擲parameterError(p, "@Path parameters may not be used with @Url."),可在ServcieMethod類中parseParameterAnnotation()方法中找到不能同時使用的具體程式碼。其實原因也是很好理解:Path註解用於替換url中的引數,這就要求在使用path註解時,必須已經存在請求路徑。不然沒法替換路徑中指定的引數。而@Url 註解是在引數中指定了請求路徑的,這時候情定請求路徑已經晚,path註解找不到請求路徑,更別提更換請求路徑了中的引數了。
  • 8 使用@Body 註解的引數不能使用form 或multi-part編碼,即如果為方法使用了FormUrlEncoded或Multipart註解,則方法的引數中不能使用@Body 註解,否則會丟擲異常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)

四、為什麼要用Retrofit

1、咱們一起來看下Retrofit的優點:

  • 請求的方法引數註解可以定製
  • 支援同步、非同步和RxJava
  • 超級解耦(我最愛)
  • 可以配置不同的反序列化工具來解析資料,如json、xml等

2、為什麼使用Retrofit?

在處理HTTP請求的時候,因為不同場景或者邊界情況等比較難處理。你需要考慮網路狀態,需要在請求失敗後重試,需要處理HTTPS等問題,二這些事情讓你很苦惱,而Retrofit可以將你從這些頭疼的事情中解放出來。 當然你也可以選擇android-async-http和Volley,但為什麼選擇Retrofit?首先效率高,其次Retrofit強大且配置靈活,第三和OkHttp無縫銜接,第四Jack Wharton主導的(你懂的)。 在Retrofit2之前,OkHttp是一個可選的客戶端。二Retrofit2中,Retrofit與OkHttp強耦合,使得更好地利用OkHttp,包括使用OkHttp解決一些棘手的問題。

五、Form表單提交與multipart/form-data

由於後面涉及到Form表單提交資料的格式,為了方便部分人更好的理解,我先在這裡講解下。

(一)、Form表單

1、form表單常用屬性

  • action:url 地址,伺服器接收表單資料的地址
  • method:提交伺服器的http方法,一般為post和get
  • name:最好好吃name屬性的唯一性
  • enctype: 表單資料提交時使用的編碼型別,預設使用"pplication/x-www-form-urlencoded",如果是使用POST請求,則請求頭中的content-type指定值就是該值。如果表單中有上傳檔案,編碼型別需要使用"multipart/form-data",型別,才能完成傳遞檔案資料。

2、瀏覽器提交表單時,會執行如下步驟

  • 1、識別出表單中表單元素的有效項,作為提交項
  • 2、構建一個表單資料集
  • 3、根據form表單中的enctype屬性的值作為content-type對資料進行編碼
  • 4、根據form表單中的action屬性和method屬性向指定的地址傳送資料

3、提交方式

  • 1、get:表單資料會被encodeURIComponent後以引數的形式:name1=value1&name2=value2 附帶在url?後面,再發送給伺服器,並在url中顯示出來。
  • 2、post:content-type 預設"application/x-www-form-urlencoded"對錶單資料進行編碼,資料以鍵值對在http請求體重發送給伺服器;如果enctype 屬性為"multipart/form-data",則以訊息的形式傳送給伺服器。

4、POST請求

HTTP/1.1 協議規定的HTTP請求方法有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 這幾種。其中POST一般用於向伺服器提交資料。

大家知道,HTTP協議是以ASCII 碼傳輸,建立在TCP/IP協議之上的應用層規範。規範把HTTP請求分為3大塊:狀態行、請求頭、訊息體。類似於如下:

<method> <request-URL> <version>
<headers>
<entity-body>

協議規定POST提交的資料必須放在訊息主題(entity-body)中,但協議並沒有規定資料必須使用什麼編碼方式。實際上,開發者可以自己決定訊息體的格式,只要後面傳送的HTTP請求滿足上面的格式就可以了。

但是,資料傳送出去後,還要伺服器解析成功才有意義。一般伺服器都內建了自動解析常見資料格式的功能。服務端通常是根據請求頭(headers)中的Content-Type欄位來獲知請求中的訊息主體是用何種方式編碼,再對主體進行解析。所以說到POST提交資料方法,包含了Content-Type和訊息主題編碼方式兩部分。

5、enctype 指定的 content-type

  • application/x-www-form-urlencoded
  • application/json
  • text/xml
  • multipart/form-data

下面我們就詳細的介紹下它們。

(一)、application/x-www-form-urlencoded

這應該是最常見的POST提交資料的方式了。瀏覽器的原生<form>表單,如果不設定enctype屬性,那麼最終會以application/x-www-form-urlencoded方法提交資料。請求類似於如下內容(省略了部分無關的內容):

POST http://www.hao123.com/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
  • 1、Content-Type 被指定為 application/x-www-form-urlencoded。
  • 2、提交的資料按照key-value的格式,也就是key1=value1,key2=value2這種方式進行編碼,key和val都進行URL轉碼。大部分伺服器都對這種方式支援。

(二)、application/json

application/json 這個Content-Type作為響應頭大家肯定不陌生。事實上現在已經基本都是都是這種方式了,來通知伺服器訊息體是序列化後的JSON字串。由於JSON規範的流行,除了低版本的IE之外的現在主流瀏覽器都原生支援JSON。當然伺服器也有處理JSON的函式。

JSON格式支援比鍵值對更復雜的結構化資料,這樣點也很有用,在需要提交資料層次非常深的資料時,用JSON序列化之後提交,非常方便。

例如:

POST http://www.hao123.com/ HTTP/1.1 
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}

這種方案,可以很方便的提交複雜的結構化的資料,特別適合RESTful的介面。而且各大抓包工具如chrome自帶的開發者工具,Firebug、Fidder,都會以樹形結構展示JSON資料,非常友好。

(三)、text/xml

它是一種使用HTTP作為傳輸協議,XML作為編碼方式的遠端呼叫規範。典型的XML-RPC是這樣的:

POST http://www.example.com HTTP/1.1 
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
    <methodName>examples.getStateName</methodName>
    <params>
        <param>
            <value><i4>41</i4></value>
        </param>
    </params>
</methodCall>

XML-RPC 協議很簡單、功能夠用,各種語言的實現都有。它的使用也很廣泛,但是我還是比較傾向於JSON,因為相比於JSON,XML太過於臃腫。

(四)、multipart/form-data

在最初的http協議中,沒有定義上傳檔案的Method, 為了實現這個功能,http協議組改造了post請求,新增一種post規範,設定這種規範的Content-Type為multipart/form-data;boundary=${bound},其中${bound}是定義分割符,用於分割各項內容(檔案,key-value對),不然伺服器無法正確識別各項內容。post body裡需要用到,儘量保證隨機唯一。

這又是一個常見的POST資料提交的方式。我們使用表單上傳檔案時,必須讓form表單enctype等於multipart/form-data。 例如: 假設 form 如下:

<form action="/upload" enctype="multipart/form-data" method="post">
    Username: <input type="text" name="username">
    Password: <input type="password" name="password">
    File: <input type="file" name="file">
    <input type="submit">
</form>

header

Content-Type: multipart/form-data; boundary={boundary}\r\n

body

普通 input 資料

--{boundary}\r\n
Content-Disposition: form-data; name="username"\r\n
\r\n
Tom\r\n

檔案上傳 input 資料

--{boundary}\r\n
Content-Disposition: form-data; name="file"; filename="myfile.txt"\r\n
Content-Type: text/plain\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
hello word\r\n

結束標誌

--{boundary}--\r\n

資料示例

POST /upload HTTP/1.1
Host: 172.16.100.128:5000
Content-Length: 394 
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLumpDpF3AwbRwRBn
Referer: http://172.16.100.128:5000/

------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="username"

Tom
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="password"

passwd
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain

hello world
------WebKitFormBoundaryUNZIuug9PIVmZWuw--

這個例子稍微複雜點。首先生成了一個boundary用於分割不同的欄位,為了避免與正文內容重複,boundary很長很複雜。然後Content-Type裡指明瞭資料以multipart/form-data來編碼,本次請求的boundary是什麼內容。訊息主體裡按照欄位個數又分為多個結構型別的部分,每個部分都以---boundary開始,緊接著是內容描述資訊,然後是回車,然後是欄位的具體內容(文字和二進位制)。如果傳輸的是檔案,還要包含檔名和檔案型別資訊。訊息主體最後以----boundary----標誌結束。

(五)、MIME型別

大家已經知道了目前市場上主流上四個方式

  • application/x-www-form-urlencoded
  • application/json
  • text/xml
  • multipart/form-data

其實還有一種型別是text/plain,text/plain是純文字傳輸的意思,在發郵件的時候要設定這種編碼型別,否則會出現接受時編碼混亂的問題。網路上經常拿text/plain和text/html做比較,其實這兩個很好區分,前者用來傳輸純文字檔案,後者則是傳遞html程式碼的編碼型別,在傳送標頭檔案時才用得上。

上面提到的MIME,它的英文全稱是"Multipurpose Internet Mail Extensions"多功能Internet郵件擴充服務,它是一種多用途網際郵件擴充協議,在1992年最早應用於電子郵件系統,但是後來也應用到瀏覽器。伺服器會將它們傳送的多媒體資料的型別告訴瀏覽器,而通知手段就是說明該多媒體的MIME型別,從而讓瀏覽器知道接受到的資訊哪些是MP3,哪些是Shockwave檔案等等。伺服器將MIME識別符號放入傳送的資料中來告訴瀏覽器使用哪個外掛讀取相關檔案。

每個MIME型別由兩部分組成,前面是資料的大類別,例如聲音audio、圖象image等,後面定義具體的種類。 常見的MIME型別

  • 超文字標記語言文字 .html,.html text/html
  • 普通文字 .txt text/plain
  • RTF文字 .rtf application/rtf
  • GIF圖形 .gif image/gif
  • JPEG圖形 .jpeg,.jpg image/jpeg
  • 聲音檔案 .au audio/basic
  • MIDI音樂檔案 mid,.midi audio/midi,audio/x-midi
  • RealAudio音樂檔案 .ra, .ram audio/x-pn-realaudio
  • MPEG檔案 .mpg,.mpeg video/mpeg
  • AVI檔案 .avi video/x-msvideo
  • GZIP檔案 .gz application/x-gzip
  • TAR檔案 .tar application/x-tar

Internet中有一個專門組織IANA來確定標準的MIME型別,但是Internet發展的太快,很多應用程式等不及IANA來確認他們使用的MIME型別為標準型別。因此它們使用在類別中以x-開頭的方法標示這個類別還沒有成為標準,例如:x-gzip,x-tar等。事實上這些型別運用的很廣泛,已經成為了實際標準。只要客戶端和伺服器共同承認合格MIME型別,即使它不是標準的型別也沒有關係,客戶程式就能根據MIME型別,採用具體的處理手段來處理資料。而伺服器和客戶端,預設都設定了標準和常見的MIME型別,只有對不常見的MIME型別,才需要同時設定伺服器和客戶端,以進行識別。

六、Retrofit2 對multipart/form-data的支援

我們知道Retrofit其實是一個網路代理框架,負責封裝請求,然後把請求分發給http協議,具體實現者是okhttpclient。

(一)、如何使用Retrofit和okHttp封裝multipart/form-data

1、在retrofit2中:

  • 使用** @retrofit2.http.Multipart : 標記一個請求是multipart/form-data型別,需要和 @retrofit2.http.POST **一同使用,並且方法引數必須是 ** @retrofit2.http.Part **註解。
  • ** @retrofit2.http.Part **: 代表Multipart裡的一項資料,即用${bound}分隔的內容塊。

2、在okhttp3中:

  • okhttp3.MultipartBody :multipart/form-data 的抽象封裝,繼承okhttp3.RequestBody
  • okhttp3.MultipartBody.Part:multipart/form-data裡的一項資料。

3、Service介面定義

假設伺服器上傳介面返回資料型別為application/json,欄位如下

{
data: {},
msg: "上傳成功",
code: 200
}

因此需要對返回資料封裝成一個物件:

public class NetResponse<T> {
    public int code;
    public String msg;
    public T data;
}

訪問介面的定義

public interface uploadFileService {
    /**
     * 通過 List<MultipartBody.Part> 傳入多個part實現多檔案上傳
     * @param parts 每個part代表一個
     * @return 狀態資訊
     */
    @Multipart
    @POST("users/image")
    Call<NetResponse<Object>> uploadFilesWithParts(@Part() List<MultipartBody.Part> parts);


    /**
     * 通過 MultipartBody和@body作為引數來上傳
     * @param multipartBody MultipartBody包含多個Part
     * @return 狀態資訊
     */
    @POST("users/image")
    Call<NetResponse<Object>> uploadFileWithRequestBody(@Body MultipartBody multipartBody);
}

所以,我們知道,有兩種方式可以實現上傳

  • 1、使用@Multipart註解方法,並用@Part註解方法引數,型別是List<okhttp3.MultipartBody.Part>,或者
  • 2、不使用@Multipart註解方法,直接使用@Body註解方法引數,型別是okhttp3.MultipartBody

可以看到,無論方法引數型別是MultipartBody.Part還是MultipartBody,這些類都不是Retrofit的類,而是okhttp實現上傳的源生類。

為什麼可以這樣寫:

Retrofit會判斷@Body的引數型別,如果引數型別是okhttp3.RequestBody,則Retrofit不做包裝處理,直接丟給okhttp3處理。而MultipartBody是繼承 RequestBody,因此Retrofit不會自動包裝這個物件。同理,Retrofit會判斷@Part的引數型別,如果引數為okhttp3.MultipartBody.Part,則Retrofit會把RequestBody封裝成MultipartBody,再把Part新增到MultipartBody。

七、總結

Retrofit將REST API抽象成Java介面,使用註解來描述每一個API地址和請求,支援URL引數替換(包括查詢引數和路徑引數),以及表單編碼和多部分請求功能。

作者:隔壁老李頭 連結:https://www.jianshu.com/p/345304325511 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。