Android Retrofit 实现文字(参数)和多张图片一起上传

daben 4年前
   <h3>需求</h3>    <p>Retrofit普及后,最近好多人都在问,如何实现Retrofit上传<strong>多文件+文字</strong>需求(朋友圈发图片+文字)</p>    <h3>解决方案</h3>    <p>google: retrofit upload multiple files</p>    <h3>说重点</h3>    <p>与其直接说答案,不如我们花点时间说说多文件上传的原理,这样,以后就算出了其他的http框架,你也能快速实现。</p>    <p>HTTP协议就不讲了吧?反正copy一段过来也不会有人看。我们就直接跳到文件上传去。想看也可以,<a href="/misc/goto?guid=4959677220780881805">传送门</a></p>    <h3>post form 表单</h3>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/0c5393618f41a45aba2c419c9078934d.png"></p>    <p> </p>    <p>上图是不是很常见,在网页里选一个文件,点击上传。上传到哪里?服务器咯。web和移动端本质上有区别嘛?木有啊,就是一个前端展示的client。那服务器会为移动端创造一套独立的API嘛?显然他没那么傻。</p>    <p>这个文件上传经常会伴随着其他fields一起上传。可以简单理解为表单上传。</p>    <p>先来看下,如果没有文件,也不用json,单独上传一些key value怎么做?在Postman里可以这样模拟。</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/c11fc6f920dadf75b0f263ea05076d0d.jpg"></p>    <p> </p>    <p>表单上传要注意的是</p>    <ol>     <li><code>content-type</code>设为<code>application/x-www-form-urlencoded</code></li>     <li>form表单在streaming时是<code>"weibo=stay4it&wechat=stay4it&qq=104816053"</code></li>    </ol>    <h3>多文件上传</h3>    <p>实际上,多文件上传与form表单是一回事,一个key对应一个value。文件上传就是文件名key对应文件byte[] value,如下图postman模拟请求</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/64dd294c65a25d787e013702412cf5e1.png"></p>    <p> </p>    <p>只是如何标记一个key value的<strong>开始</strong>与<strong>结束</strong>呢?用<code>&</code>分割肯定不够用啦。那就得用个特殊的<code>boudary</code>来做为分隔符。</p>    <p>另外,这个<code>content-type</code>得为<code>multiple/form-data</code></p>    <p>好了。科普到此结束。简单理解HTTP协议以及form表单概念,相信接下来的代码你就不只是会调用,还能明白为什么。</p>    <h3>最原始的上传方式</h3>    <p>以前大家都用HttpUrlConnection,Stay在<a href="/misc/goto?guid=4959677220870407038">自己动手写HTTP框架-19课时</a>详细讲过如何上传多文件以及进度更新。这里贴下核心代码:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/c11fc6f920dadf75b0f263ea05076d0d.jpg"></p>    <p> </p>    <p>以上这个UploadUtil,拿到outputstream,分别写入<code>postContent</code>以及List<FileEntity>,代码不多,相信大家都能看明白。</p>    <h3>抓个包看看</h3>    <p><strong>请求数据抓包</strong></p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/e1b2c6476f7550e83443b1e55dc7d0c2.png"></p>    <p>上传的有两个form,<br> 一个<code>content-type</code>为<code>text/plain</code> key为<code>data</code>, value为<code>stay4it</code>。<br> 一个<code>content-type</code>为<code>image/png</code> key为<code>file0</code>, value为文件的bytes</p>    <p><strong>服务器如何接收的?(PHP版)</strong></p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/f2742ce6b007ec5862d02bf8a978ed2d.png"></p>    <p> </p>    <p>代码还算好懂,$_FILES就是请求上传的多文件,只要<code>content-type</code>设置为<code>multiple/form-data</code>,服务器接收是就会将其当成文件处理,将文件接收在<code>$_FILES</code>中,等待处理(存数据库,存硬盘或转七牛云等等),$this->data是表单中key为<code>data</code>所对应的value<code>stay4it</code>。(以后再有服务器er告诉你分两个API上传,你就可以这么怼他了: )</p>    <p><strong>返回结果抓包</strong></p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/3a3f15cd6b99c520fd6fefed27288fe5.png"></p>    <p>好了,原始的方式聊的差不多了,虽然代码看起来很多,但已经是个util类了,倒不是那么难用。但是我们还是希望在写代码时能尽可能少的去关注内部实现啊。什么<code>multiple/form-data</code>,什么<code>boundary</code>。真是很麻烦嘛。</p>    <h3>鸟枪换大炮吧</h3>    <p>以下Retrofit多文件上传内容由<a href="/misc/goto?guid=4959677220780881805">一叶飘舟</a>大神提供。</p>    <h3>Retrofit实现文件和图片一起上传</h3>    <p>如果对retrofit不是很了解,参考:<a href="/misc/goto?guid=4959677220972513028">初识Retrofit</a></p>    <p>定义接口</p>    <p>根据对Stay<a href="/misc/goto?guid=4959677220870407038">自己动手写HTTP框架-19课时</a>提供的上传图片接口的大量抓包和测试总结,接口定义如下:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/6f2ef6f310d62c47ea003eff369e3dc7.png"></p>    <p>这里用到了@Partmap注解,将图片文件信息放入map中。</p>    <p>准备图片</p>    <p>在sdcard根目录存放两张图片,分别为test.png和test.jpg(不要是gif图片啊,服务器不支持)</p>    <p>代码实现</p>    <p>这里就不贴代码了,截图如下(如果看不清,鼠标右键在新窗口打开就可以看到原图了):</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/64f73e357fb6ee7e838178533cd58179.png"></p>    <p>关键代码在于:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/127936bb25aa02f8ef5e2c731df9261c.png"></p>    <p>看到这个是不是想起了上面我们提到的关键代码呢?下面再贴出来我们对比下。</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/b9a4d163fce199727d25b0efa94ce12a.png"></p>    <p>只要将对应的http请求头信息填写正确,就能上传成功。</p>    <p>那么问题又来了,怎么分析和正确拼写这个请求头呢?</p>    <p>在文章开头的时候有个抓包信息:</p>    <pre>  <code>Content-Disposition: form-data; name="file0"; filename="test.png"</code></pre>    <p>实质上上传文件Requestbody对应的请求头就是 <strong>name="file0"; filename="test.png"</strong>,只要拼对了就没有问题了。</p>    <p>注意:</p>    <blockquote>     <ol>      <li><strong>name="file0"; filename="test.png"</strong>这个请求头是根据有心课堂提供的上传接口写的,不适用其他上传接口,但原理是类似的;</li>      <li>单张图片上传通用的请求头是:<strong>name="file"; filename="test.png"</strong></li>      <li>filename="test.png"这个一般是指(你希望)保存在服务器的文件名字。</li>     </ol>    </blockquote>    <p>举例说明</p>    <p>比如我们这样写请求头信息,如下代码所示:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/02c01f020c95a9a89b2b3dad6d81e862.png"></p>    <p>运行请求抓包请求头信息如下图所示:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/0ab1183bde530e6e9cd79a988660e695.png"></p>    <p>出现了name="name="file1"这样的字段,拼接错误(不用加name字段),服务器也毫不留情的返回了错误:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/cff63bc1775581a5d2dc5b613910ed8a.png"></p>    <p>这个问题我当初没有发现,后来还是请教了Stay才搞明白了。</p>    <p>好了,不知道我讲的大家明白了没有,最后来个成功运行的请求抓包截图吧:</p>    <p><img alt="Android Retrofit 实现文字(参数)和多张图片一起上传" src="https://simg.open-open.com/show/afc3d537f45b11927bbc8d401cfc5de8.jpg"></p>    <p>关于文字类参数上传</p>    <p>写到最后忘了说文字参数了,文字参数相对文件来说容易些。</p>    <p>在接口中,我们有一个文字参数 <code>@Part("data") String des</code>,如果你需要多个,增加就行了。需要注意的是这个参数的名字比如"data",不是前端自定义,而是后台定义的。</p>    <p>代码托管地址:<a href="/misc/goto?guid=4959677221067869061">https://github.com/stay4it/RetrofitTutorial</a></p>    <p> </p>    <p><br> 来自:http://www.jianshu.com/p/3b8b2a0c0f30</p>    <p> </p>