Android使用Retrofit上传图片到服务器

上传头像的问题

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) {
                //。。。
            }
        });
    }

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--

看到请求体里面,分隔两张图片的二进制流和描述信息的是一段长长的横线和一段十六进制表示的数字,这个东西称作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
selection is sometimes done probabilisticly.)
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.

可以看到这串数字就是为了分隔不同的part的,这串数字可以是随机生成的,但是不能出现在提交的表单数据里(这个很好理解,如果跟表单数据中的某一部分冲突了,就起不到分隔的作用了)。

回归那段代码

在用Retrofit上传文件的那段代码中,使用@Multipart说明将使用Multipart格式提交数据,而@Part这个注解后面跟的参数则是以下请求头中加粗的部分:

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

这段加粗的部分放到Java代码中需要进行转义(对双引号和分号转义),就得到了如下的一个参数声明:

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

所以@Part后面跟的参数虽然看起来很奇怪,但如果知道了实际传输的数据格式,这一写法就很好理解了。



文/JimmieZhou(简书作者)
原文链接:http://www.jianshu.com/p/44231545fd9a
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值