Android使用Retrofit上傳圖片到服務器

DorMKFG 8年前發布 | 51K 次閱讀 Retrofit Android開發 移動開發

上傳頭像的問題

8月份的時候曾經在項目中遇到要上傳圖片到服務器的問題,其實需求很典型:就是用戶需要上傳自己的頭像。我們的項目使用的網絡框架是很流行的Retrofit,而網絡上常見的Retrofit的教程告訴我們正確的定義服務的姿勢是像這樣的:

public interface MusicListService {       
    @GET("music/listMusic.do")      
    Observable<List<Music>> getMusicList(@Query("start") int start, @Query("size") int size, @Query("categoryId") long parent_id);                                               
}

這樣形式的接口明顯不能用來上傳頭像,所以我就需要琢磨怎么實現圖片的上傳。實際上關于 用Retrofit上傳圖片的博客在網上實在太多 ,為什么我還要單獨寫一篇,主要是記錄一下踩過的坑。而且,很多時候我們只知道怎么做,卻不知道為什么要這樣做,實在不應該。 只說怎么做的博客太多,我想指出來為什么這么做

上傳實踐

以下給出一個同時傳遞字符串參數和一張圖片的服務接口的定義:

public interface UploadAvatarService {    
    @Multipart    
    @POST("user/updateAvatar.do")
    Call<Response> updateAvatar (@Query("des") String description, @Part("uploadFile\"; filename=\"test.jpg\"") RequestBody imgs );
}

然后在實例化UploadAvatarService的地方,調用以下代碼實現圖片上傳。以下函數被我刪改過,可能無法一次完美運行,但大體是沒錯的。

private void uploadFile(final String filename) {
        UploadAvatarService service = RetrofitUtil.createService(getContext(), UploadAvatarService.class);
        final File file = new File(filename);
        RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        Call<Response> call = service.updateInfo(msg, requestBody );
        call.enqueue(new Callback<Response>() {
            @Override
            public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
                //。。。。。
            }

        @Override
        public void onFailure(Call<Response> call, Throwable t) {
            //。。。
        }
    });
}</code></pre> 

POST實際提交的內容

歷史上(1995年之前),POST上傳數據的方式是很單一的,就像我們用GET方法傳參一樣,參數名=參數值,參數和參數之間用&隔開。就像這樣:

param1=abc&param2=def

只不過GET的參數列表放在URL里面,像這樣:

http://url:port?param1=abc&param2=def

而用POST傳參時,參數列表放到HTTP報文的請求體里了,此時的請求頭長這樣。

POST http://www.test.org HTTP/1.1
Content-Type:application/x-www-form-urlencoded; charset=UTF-8

以前POST只支持純文本的傳輸,上傳文件就很惱火,奇技淫巧應該也可以實現上傳文件,比如將二進制流當成文本傳輸。后來互聯網工程任務組( IETF )在1995年11月推出了 RFC1867 。在RFC1867中提出了基于表單的文件上傳標準。

This proposal makes two changes to HTML:

1) Add a FILE option for the TYPE attribute of INPUT.

2) Allow an ACCEPT attribute for INPUT tag, which is a list of

media types or type patterns allowed for the input.

In addition, it defines a new MIME media type, multipart/form-data,

and specifies the behavior of HTML user agents when interpreting a

form with

ENCTYPE="multipart/form-data" and/or <INPUT type="file">

tags.

簡而言之,就是要增加文件上傳的支持,另外還為<input>標簽添加一個叫做accept的屬性,同時定義了一個新的MIME類型,叫做multipart/form-data。而multipart,則是在我們傳輸非純文本的數據時采用的數據格式,比如我們上傳一個文件時,HTTP請求頭像這樣:

POST http://www.test.org HTTP/1.1
Content-Type:multipart/form-data;  boundary=---------------------------7d52b13b519e2

如果要傳輸兩張圖片,請求體長這樣:

-----------------------------7d52b13b519e2
Content-Disposition: form-data; name="upload1"; filename="test.jpg"
Content-Type: image/jpeg

/此處應是test.jpg的二進制流/

-----------------------------7d52b13b519e2 Content-Disposition: form-data; name="upload2"; filename="test2.jpg" Content-Type: image/jpeg

/此處應是test2.jpg的二進制流/

-----------------------------7d52b13b519e2--</code></pre>

看到請求體里面,分隔兩張圖片的二進制流和描述信息的是一段長長的橫線和一段十六進制表示的數字,這個東西稱作boundary,在RFC1867中提到了:

3.3 use of multipart/form-data

The definition of multipart/form-data is included in section 7.A

boundary is selected that does not occur in any of the data. (This

Each field of the form

is sent, in the order in which it occurs in the form, as a part of

the multipart stream. Each part identifies the INPUT name within the

original HTML form. Each part should be labelled with an appropriate

content-type if the media type is known (e.g., inferred from the file

extension or operating system typing information) or as

application/octet-stream.</code></pre>

可以看到這串數字就是為了分隔不同的part的,這串數字可以是隨機生成的,但是不能出現在提交的表單數據里(這個很好理解,如果跟表單數據中的某一部分沖突了,就起不到分隔的作用了)。

回歸那段代碼

在用Retrofit上傳文件的那段代碼中,使用@Multipart說明將使用Multipart格式提交數據,而@Part這個注解后面跟的參數則是以下請求頭中加粗的部分:

-----------------------------7d52b13b519e2

Content-Disposition: form-data; name=" upload1"; filename="test.jpg"

Content-Type: image/jpeg</code></pre>

這段加粗的部分放到Java代碼中需要進行轉義(對雙引號和分號轉義),就得到了如下的一個參數聲明:

@Part("uploadFile\"; filename=\"test.jpg\"") RequestBody imgs

所以@Part后面跟的參數雖然看起來很奇怪,但如果知道了實際傳輸的數據格式,這一寫法就很好理解了。

 

來自:http://www.jianshu.com/p/44231545fd9a

 

 本文由用戶 DorMKFG 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!