博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Retrofit 实现文字(参数)和多张图片一起上传
阅读量:5872 次
发布时间:2019-06-19

本文共 5999 字,大约阅读时间需要 19 分钟。

背景

在有心课堂《自己动手写HTTP框架》课程中有下列课程:

自拍要发朋友圈如何实现

通过自己写的HTTP框架实现将图片和文字等内容在一个接口中提交到后台。

如果你对http请求不是很熟悉,参考:

分析

课程中上传图片相关代码如下图所示:

这里写图片描述

从上面的代码中可以看出,把图片放在了列表中,图片描述放在了request.content中。

通过对该方法运行时的网络请求抓包分析如下:

--7d4a6d158c9Content-Disposition: form-data; name="data"Content-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: 8bitstay4it--7d4a6d158c9Content-Disposition: form-data; name="file0"; filename="test.png"Content-Type: image/png

返回结果抓包分析如下:

这里写图片描述

从上图Contents项中可以看到有两个关键字段,分别是data和file0字段。

这两个字段是怎么产生的呢?

通过查看《自己动手写HTTP框架》相关代码,有如下方法:

public static void upload(OutputStream out, String filePath) throws AppException {        String BOUNDARY = "7d4a6d158c9"; // 数据分隔线        DataOutputStream outStream = new DataOutputStream(out);        try {            outStream.writeBytes("--" + BOUNDARY + "\r\n");            outStream.writeBytes("Content-Disposition: form-data; name=\"file0\"; filename=\""                    + filePath.substring(filePath.lastIndexOf("/") + 1) + "\"" + "\r\n");            outStream.writeBytes("\r\n");            byte[] buffer = new byte[1024];            FileInputStream fis = new FileInputStream(filePath);            while (fis.read(buffer, 0, 1024) != -1) {                outStream.write(buffer, 0, buffer.length);            }            fis.close();            outStream.write("\r\n".getBytes());            byte[] end_data = ("--" + BOUNDARY + "--\r\n").getBytes();// 数据结束标志            outStream.write(end_data);            outStream.flush();        } catch (Exception e) {            throw new AppException(AppException.ErrorType.UPLOAD, e.getMessage());        }    }

这个是单张图片上传,紧接着看多张图片上传,代码如下:

/**     * @param out     * @param postContent     * @param entities     */    public static void upload(OutputStream out, String postContent, ArrayList
entities) throws AppException { String BOUNDARY = "7d4a6d158c9"; // 数据分隔线 String PREFIX = "--", LINEND = "\r\n"; String CHARSET = "UTF-8"; DataOutputStream outStream = new DataOutputStream(out); try { StringBuilder sb = new StringBuilder(); sb.append(PREFIX); sb.append(BOUNDARY); sb.append(LINEND); sb.append("Content-Disposition: form-data; name=\"" + "data" + "\"" + LINEND); sb.append("Content-Type: text/plain; charset=" + CHARSET + LINEND); sb.append("Content-Transfer-Encoding: 8bit" + LINEND); sb.append(LINEND);// post content sb.append(postContent); sb.append(LINEND); outStream.write(sb.toString().getBytes()); int i = 0; for (FileEntity entity : entities) { StringBuilder sb1 = new StringBuilder(); sb1.append(PREFIX); sb1.append(BOUNDARY); sb1.append(LINEND); sb1.append("Content-Disposition: form-data; name=\"file" + (i++) + "\"; filename=\"" + entity.getFileName() + "\"" + LINEND); sb1.append("Content-Type: " + entity.getFileType() + LINEND); sb1.append(LINEND); outStream.write(sb1.toString().getBytes()); InputStream is = new FileInputStream(entity.getFilePath()); byte[] buffer = new byte[1024]; int len = 0; while ((len = is.read(buffer)) != -1) { outStream.write(buffer, 0, len); } is.close(); outStream.write(LINEND.getBytes()); } byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes(); outStream.write(end_data); outStream.flush(); } catch (Exception e) { throw new AppException(AppException.ErrorType.UPLOAD, e.getMessage()); } }

通过对上面两段代码的比较,发现主区别在这个地方:

这里写图片描述

这个地方也是我们使用Retrofit上传的关键点所在,后面我们会再提到。

上面分析了这么多,我们来看看怎么使用retrofit来实现。

Retrofit实现文件和图片一起上传

如果对retrofit不是很了解,参考:

定义接口

在码小白的博客 中有下面内容:

图片和字符串同时上报

@Multipart@POST("upload/")Call
register( @Query("name") String cardName, @Query("phone") String callphone, @Query("address") String address, @Part("avatar\\"; filename=\\"avatar.jpg") RequestBody avatar, );

这种接口应该也是可以的,具体要怎么实现,都要与服务器接口保持一致,所以不能照搬照抄了。

根据对有心课堂提供的上传图片接口的大量抓包和测试总结,接口定义如下:

@Multipart@POST("v1/public/core/?service=user.updateAvatar")Observable
>> uploadMultipleTypeFile(@Part("data") String des, @PartMap Map
params);

这里用到了@Partmap注解,将图片文件信息放入map中。

准备图片

在sdcard根目录存放两张图片,分别为test.png和test.jpg(不要是gif图片啊,服务器不支持)

代码实现

这里就不贴代码了,截图如下(如果看不清,鼠标右键在新窗口打开就可以看到原图了):

这里写图片描述

关键代码在于:

bodyMap.put("file"+i+"\"; filename=\""+file.getName(), RequestBody.create(MediaType.parse("image/png"),file));

看到这个是不是想起了上面我们提到的关键代码呢?下面再贴出来我们对比下。

sb1.append("Content-Disposition: form-data; name=\"file" + (i++) + "\"; filename=\"" + entity.getFileName() + "\""                        + LINEND);

只要将对应的http请求头信息填写正确,就能上传成功。

那么问题又来了,怎么分析和正确拼写这个请求头呢?

在文章开头的时候有个抓包信息:

Content-Disposition: form-data; name="file0"; filename="test.png"

实质上上传文件Requestbody对应的请求头就是 name="file0"; filename="test.png",只要拼对了就没有问题了。

注意:

  1. name="file0"; filename="test.png"这个请求头是根据有心课堂提供的上传接口写的,不适用其他上传接口,但原理是类似的;

  2. 单张图片上传通用的请求头是:name="file"; filename="test.png"

  3. filename="test.png"这个一般是指(你希望)保存在服务器的文件名字。

举例说明

比如我们这样写请求头信息,如下代码所示:

bodyMap.put("name=\"file"+i+"\"; filename=\""+file.getName()+"\"", RequestBody.create(MediaType.parse("image/png"),file));

运行请求抓包请求头信息如下图所示:

这里写图片描述

出现了name="name="file1"这样的字段,拼接错误(不用加name字段),服务器也毫不留情的返回了错误:

{    "ret": 403,    "data": [],    "msg": "\u975e\u6cd5\u8bf7\u6c42\uff1a\u6587\u4ef6\u4e0a\u4f20\u51fa\u9519"}

这个问题我当初没有发现,后来还是请教了有心课堂的Stay才搞明白了。

好了,不知道我讲的大家明白了没有,最后来个成功运行的请求抓包截图吧:

这里写图片描述

关于文字类参数上传

写到最后忘了说文字参数了,文字参数相对文件来说容易些。

在接口中,我们有一个文字参数 @Part("data") String des ,如果你需要多个,增加就行了。需要注意的是这个参数的名字比如"data",不是前端自定义,而是后台接口定义的。

代码托管地址:

2016.8.19 凌晨

转载地址:http://yqhnx.baihongyu.com/

你可能感兴趣的文章
[转] Lazy evaluation
查看>>
常用查找算法总结
查看>>
被神话的大数据——从大数据(big data)到深度数据(deep data)思维转变
查看>>
修改校准申请遇到的问题
查看>>
Linux 进程中 Stop, Park, Freeze【转】
查看>>
文件缓存
查看>>
远程协助
查看>>
Scrum实施日记 - 一切从零开始
查看>>
关于存储过程实例
查看>>
配置错误定义了重复的“system.web.extensions/scripting/scriptResourceHandler” 解决办法...
查看>>
AIX 7.1 install python
查看>>
PHP盛宴——经常使用函数集锦
查看>>
重写 Ext.form.field 扩展功能
查看>>
Linux下的搜索查找命令的详解(locate)
查看>>
福利丨所有AI安全的讲座里,这可能是最实用的一场
查看>>
开发完第一版前端性能监控系统后的总结(无代码)
查看>>
Python多版本情况下四种快速进入交互式命令行的操作技巧
查看>>
MySQL查询优化
查看>>
【Redis源码分析】如何在Redis中查找大key
查看>>
关于链接文件的探讨
查看>>