刚刚开通博客,赶紧把第一篇博客献给刚刚学习中遇到的一个小问题,由于是第一次写,练手,写得差,请轻喷。
retrofit作为现在最流行的几个android网络请求库之一,最近也升级到了2.0版本,增加和改进了很多地方,也改进了retrofit1.x的很多缺陷,比如取消正在进行的一些事务。对于2.0的 一些新特性,我就不详细介绍了,想要了解的童鞋请 点击这里 。
不得不说,retrofit升级到2.0后,里面的一个特性我特别喜欢,就是增加了对rxjava的支持,大大缩减了编程的复杂度。如果想了解rxjava的童鞋,请传送到网上一位大神写的在国内算得上非常详细的 教程。
好了,扯了这么久,还是先来看看我在开发中遇到的retrofit2.0 beta中的一个小小的问题吧, 就是在上传图片的时候,我们原本需要的是这样的一个包:
POST ******** HTTP/1.1
Content-Type: multipart/form-data; boundary=9783403d-4d2a-438d-ab0f-a62670215098
Content-Length: 60141
Host: ********
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.5.0
User-Agent: ********
Content-Type: application/x-www-form-urlencoded
Accept: */*
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="login_name"
Content-Transfer-Encoding: binary
Content-Type: text/plain; charset=utf-8
Content-Length: 5
qhung
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="file"; filename="head_icon.png"
Content-Transfer-Encoding: binary
Content-Type: image/png
Content-Length: 59699
********
but 现实总是残酷的,当我们还是和retrofit1.x一样上传的时候:
@Multipart
@POST("/upload/headicon")
Observable<HeadIcon> updateHeadIcon(@Part("login_name") String loginName, @Part("file") RequestBody file);
抓包的结果却是这样的:
POST ******** HTTP/1.1
Content-Type: multipart/form-data; boundary=9783403d-4d2a-438d-ab0f-a62670215098
Content-Length: 60141
Host: ********
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.5.0
User-Agent: ********
Content-Type: application/x-www-form-urlencoded
Accept: */*
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="login_name"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=utf-8
Content-Length: 5
qhung
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="file"
Content-Transfer-Encoding: binary
Content-Type: image/png
Content-Length: 59699
********
嗯,和我们的目标不同的有两个地方:
一个是login_name的Content-Type,还有一个是我们要上传的图片部分的,似乎没有filename。这可不行,login_name的类型应该是String,也就是说应该是Content-Type:text/plain,不然的话,要是服务器没有处理,就会出现可恶的500服务器内部错误。 所以我们还是得要解决这个问题,这时候我们就会想到图片上传的时候用的类是RequestBody,这个类根据英文单词字面意思就能知道是什么意思,然而根据官方文档里面的说法,可以在创建这个类的时候调用这个构造器
public static RequestBody create(final MediaType contentType, final File file)
然而此时需要一个叫做MediaType的类,这个类可以指定参数的类型,所以我们在提交login_name的时候也试着用下这个类:
RequestBody userNameBody = RequestBody.create(MediaType.parse("text/plain"), userName);
然后:
@Multipart
@POST("/upload/headicon")
Observable<HeadIcon> updateHeadIcon(@Part("login_name") RequestBody loginName, @Part("file") RequestBody file);
果然不出所料,此时抓包的结果是这样的:
POST ******** HTTP/1.1
Content-Type: multipart/form-data; boundary=9783403d-4d2a-438d-ab0f-a62670215098
Content-Length: 60141
Host: ********
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.5.0
User-Agent: ********
Content-Type: application/x-www-form-urlencoded
Accept: */*
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="login_name"
Content-Transfer-Encoding: binary
Content-Type: text/plain; charset=utf-8
Content-Length: 5
qhung
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="file"
Content-Transfer-Encoding: binary
Content-Type: image/png
Content-Length: 59699
********
好了,现在解决下一个问题,就是缺少filename的问题,对于这个问题,我google了很久都没有找到解决的方法,可能是我英文太差, 在官方文档里面也没有找到解决的办法,但是如果缺少filename的话,服务器总是说参数不对。。这时候没办法了,只要硬着头皮看源码了,然后最后翻出来retrofit的源码一看,原来square公司在定义@Part注解的时候有两个参数:
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Part {
String value();
/** The {@code Content-Transfer-Encoding} of this part. */
String encoding() default "binary";
}
value和encoding,value不就是我们用@Part的时候传的name值么?
即@Part(“file”)在最后网络请求的时候会转化成Multipart数据里面name = “file”,既然这样的话,我们何不把value的值设置成 file”;filename=”file.png呢?说干就干:
@Multipart
@POST("/upload/headicon")
Observable<HeadIcon> updateHeadIcon(@Part("login_name") RequestBody loginName, @Part("file\"; filename=\"head_icon.png") RequestBody file);
OK,现在再现抓包试试:
POST ******** HTTP/1.1
Content-Type: multipart/form-data; boundary=9783403d-4d2a-438d-ab0f-a62670215098
Content-Length: 60141
Host: ********
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.5.0
User-Agent: ********
Content-Type: application/x-www-form-urlencoded
Accept: */*
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="login_name"
Content-Transfer-Encoding: binary
Content-Type: text/plain; charset=utf-8
Content-Length: 5
qhung
--9783403d-4d2a-438d-ab0f-a62670215098
Content-Disposition: form-data; name="file"; filename="head_icon.png"
Content-Transfer-Encoding: binary
Content-Type: image/png
Content-Length: 59699
********
OK,现在大功告成,总算达到了想要的结果。。。。当然在解决这个问题的时候,还看到有人想改变Content-Transfer-Encoding的值,在微软上面看到是 这样的 。似乎是关于内容传输编码的东西,当然这个玩意儿可以通过刚刚在retrofity源码里面看到的@Part里面除了有一个value外,还有一个encoding,这个encoding就是用来指定Content-Transfer-Encoding的类型的,默认值为binary,我们也可以这样指定:
@Part(value = "login_name",encoding = "你想要的编码") RequestBody loginName
OK,第一次学习笔记就也以到此结束了。如果有对retrofit源码感兴趣的童鞋可以看下Retrofit2.0源码简要分析,希望能对大家有所帮助,如果有不正确的地方,也希望大家能指正出来。