Android中怎么利用Http实现图片上传和表单提交
这篇文章将为大家详细讲解有关Android中怎么利用Http实现图片上传和表单提交,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
服务器主要是Web居多,客户端一般通过http上传文件到web服务器,最开始的设想很简单,直接将图片转化为字节流,写入到http的outstream,随后发送出去即可。
但当这种方法出现问题,服务器根据文件名这个表单中的字段来判定是否接收到文件,我上面那种简单的方法从而使得每次服务器反馈说没有接收到图片文件,从而发送失败。由此推断是表单传输出了问题,Android由于历史原因,有很多表单传输的方法。当前官方推荐的是HttpURLConnection,但是利用HttpURLConnection构建表单的方式,没有成型的form封装方法。比如对于C#的表单提交,简简单单几句话搞定:
WWWForm form = new WWWForm(); form.AddField("frameCount", Time.frameCount.ToString()); form.AddBinaryData("fileUpload", bytes); // Upload to a cgi script WWW w = new WWW("http://localhost/cgi-bin/env.cgi?post", form);
Java的HttpURLConnection没有这么简单的封装形式,需要完整的请求体模拟,用起来相对不方便,不过这样能够对单提交的本质原理有更加清晰的理解。
web端demo
在Android端上传图片总是失败的情况下,后台开发哥们帮忙实现了web端的请求demo,是可以正常处理请求的,页面如下:
选择文件之后,按浏览器的F12,便可出现开发者工具界面,在Network一栏可以看到具体的请求和响应, 分析其请求头和请求体,来构造Android中相同的参数,就可以实现文件的正常上传。 下面就根据web端的请求demo来模拟实现Android的post提交方法。
Http请求头分析
首先来看请求的消息头:
Accept:*/*Accept-Encoding:gzip, deflateAccept-Language:zh-CN,zh;q=0.8,en;q=0.6Content-Length:38275Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryCjkbdjmUGD4QJISLHost:118.69.25.90Origin:http://118.69.25.90Proxy-Connection:keep-aliveReferer:http://118.69.25.90/User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36Request Payload
根据请求头,去构建Android的HttpURLConnection相关参数:
URL url = new URL("http://118.69.25.90/uploadpicture.php");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setDoInput(true);connection.setDoOutput(true);connection.setUseCaches(true);// 启动post方法connection.setRequestMethod("POST");// 设置请求头内容connection.setRequestProperty("connection", "Keep-Alive");connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundaryCjkbdjmUGD4QJISL");
Http请求体分析
下面来分析消息体(Request Payload),内容如下:
------boundary=----WebKitFormBoundaryCjkbdjmUGD4QJISLContent-Disposition: form-data; name="file"; filename="ouba.jpg"Content-Type: image/jpegÿØÿà 2! !22222222222222222222222222222222222222222222222222ÿÂyÛ"{qX½?WóþÔ¾ÅæÑÏRiÙ' ˺h Í'Ÿ>¡&@˜AÈ"L"æÒ²Ä'` z•« ©Ä[x ¯ eu"k1ÑÏ-³¶¬'næZYT4H¶‹ ´tp؃•Rô)ÁÕ1åêå2«ee-fŒ]¬¢kd«'ú~ï'õøÔ™$ Ò-°¢$IË"->ÊNw1S…………………………………………Ü'-È w1…ª(,RÅ·IȪ•'Z~Yô ë7U<»ÄçV‹+V3.¬ÛR‹cBËF=…™n²Zò[*ÇqEÇCg Ìë«Ž™µaCMj¼ÉÛçNÙ®-´ù¿²šôí´C ¦"CÃm>Ò j5…§Ñ*ÙWvĪÙúÜÉ?K),GŽ½)Ì,Xj ‰@ gªˆAMêrªÙe 'Ô7 -Ý_´3à^ ƒÔÿÙ------WebKitFormBoundaryCjkbdjmUGD4QJISL--
从消息体内容可以看出,请求消息体本质上就是字节数组或字符串。内容主要分为三部分:
1. 开始和结束字段 开始和结束都有明确的字段 boudary字段的具体内容是由消息头中Content-Type字段进行定义的:
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryCjkbdjmUGD4QJISL
这里面设置的boundary和消息体中的boundary必须保持完全一致,才可以确保消息能够得到服务端的正常解析。
2. 表单信息 包含Content-Disposition、name、filename和Content-Type等四个表单变量,必须要填写正确的字段,web服务器才可以对相关变量进行正确解析
3. 图片 payload中的乱码数据,就是文件的二进制表示了
4. 换行回车\r\n 所以Java构造payload的原理,就是按照这种顺序和特定的字段,进行模拟即可,java代码如下:
DataOutputStreamdos = new DataOutputStream(connection.getOutputStream());FileInputStream fin = new FileInputStream(filePath);File file = new File(filePath);dos.writeBytes("------boundary=----WebKitFormBoundaryCjkbdjmUGD4QJISL");dos.writeBytes("\r\n");dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename="+file.getName());dos.writeBytes("\r\n");dos.writeBytes("Content-Type: image/jpeg");dos.writeBytes("\r\n");dos.writeBytes("\r\n"); // 取得本地图片的字节流,向url流中写入图片字节流bytesAvailable = fin.available();bufferSize = Math.min(bytesAvailable, maxBufferSize);buffer = new byte[bufferSize];bytesRead = fin.read(buffer, 0, bufferSize);while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = fin.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fin.read(buffer, 0, bufferSize);}dos.writeBytes("\r\n"); dos.writeBytes("------WebKitFormBoundaryCjkbdjmUGD4QJISL--");dos.writeBytes("\r\n"); dos.writeBytes("\r\n");
完整上传图片的java代码
package com.youreye.tts.qq;import java.io.ByteArrayOutputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import java.net.URLEncoder;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;import org.apache.http.HttpEntity;import org.apache.http.NameValuePair;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.message.BasicNameValuePair;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;public class UploadPicture { String serverUrl = "http://118.89.25.65/upload-and-detect.php"; HttpURLConnection connection = null; DataOutputStream dos = null; int bytesAvailable, bufferSize, bytesRead; int maxBufferSize = 1 * 1024 * 512; byte[] buffer = null; String boundary = "-----------------------------1954231646874"; Map<String, String> formParams = new HashMap<String, String>(); FileInputStream fin = null; // 对包含中文的字符串进行转码,此为UTF-8。服务器那边要进行一次解码 private String encode(String value) throws Exception { return URLEncoder.encode(value, "UTF-8"); } public String uploadPicToWebServer(String filePath) { try { URL url = new URL(serverUrl); connection = (HttpURLConnection) url.openConnection(); // 允许向url流中读写数据 connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(true); // 启动post方法 connection.setRequestMethod("POST"); // 设置请求头内容 connection.setRequestProperty("connection", "Keep-Alive"); connection .setRequestProperty("Content-Type", "multipart/form-data; boundary=---------------------------1954231646874"); dos = new DataOutputStream(connection.getOutputStream()); fin = new FileInputStream(filePath); File file = new File(filePath); dos.writeBytes(boundary); dos.writeBytes("\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename="+file.getName()); dos.writeBytes("\r\n"); dos.writeBytes("Content-Type: image/jpeg"); dos.writeBytes("\r\n"); dos.writeBytes("\r\n"); // 取得本地图片的字节流,向url流中写入图片字节流 bytesAvailable = fin.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; bytesRead = fin.read(buffer, 0, bufferSize); while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = fin.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fin.read(buffer, 0, bufferSize); } dos.writeBytes("\r\n"); dos.writeBytes("-----------------------------1954231646874--"); dos.writeBytes("\r\n"); dos.writeBytes("\r\n"); // Server端返回的信息 int code = connection.getResponseCode(); if (code == 200) { InputStream inStream = connection.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = -1; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); return new String(outSteam.toByteArray()); } if (dos != null) { dos.flush(); dos.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }}
遇到的主要的坑:
这个问题花了五个小时时间,花费时间长主要原因如下:
Android的多种表单提交方案 有HttpClient、httpmine.jar和HttpURLConnection,前两种方案,官方已不在推荐,而且很容易出现版本兼容性问题。所以需要采用HttpURLConnection,但是这种方案没有成型的表单提交接口,所以在上传图片时,服务器对表单解析很容易出问题。
chrome的F12工具,requestload中的图片内容看不到,影响了对图片http上传的理解。 最后采用Firefox浏览器来分析请求协议:
图片中requestload的内容一目了然,所以就知道如何去构造图片+表单提交的request内容了,所以这次非常感谢FireFox这种强大的工具,帮忙定位核心问题。
关于Android中怎么利用Http实现图片上传和表单提交就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。