前面我们讲了如何通过 volley 实现表单的提交,而这篇文章跟上一篇衔接很大,如果没有看上一篇 blog 的朋友,建议先去看看 Android Volley文件上传(一)之表单提交
因为文件上传实质就是表单的提交,只不过它提交的数据包含文件类型,接下来还是按照表单提交的套路来分析
数据格式
这里我们通过图片上传的案例来分析,其他文件也是同样的实现方式;以下是我在传图网传图时,上传的数据格式,先来分析一下
POST http://chuantu.biz/upload.php HTTP/1.1
Host: chuantu.biz
Connection: keep-alive
Content-Length: 4459
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://chuantu.biz
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryS4nmHw9nb2Eeusll
Referer: http://chuantu.biz/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: __cfduid=d9215d649e6e648e0eac7688b406a3d911425089350
------WebKitFormBoundaryS4nmHw9nb2Eeusll
Content-Disposition: form-data; name="uploadimg"; filename="spark_bg.png"
Content-Type: image/png
JFIFC
%# , #&')*)-0-(0%()(C
((((((((((((((((((((((((((((((((((((((((((((((((((("
}!1AQa"q2#BR$3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
w!1AQaq"2B #3Rbr
$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz?PNG
------WebKitFormBoundaryS4nmHw9nb2Eeusll--
不难发现,这种格式跟表单提交的格式非常接近,不过还是有所差别,这里仔细看还是能看出来总共有加上结尾行,有五行,因为乱码部分,其实就是图片的二进制数,整个算一行;下面来分析下:
1、第一行:”–” + boundary + “\r\n” ;
前面也说了文件上传,其实就是表单提交,所以在提交数据的开始标志不变;
2、第二行:Content-Disposition: form-data; name=”参数的名称”; filename=”上传的文件名” + “\r\n”
这里比普通的表单多了一个filename=”上传的文件名”;
3、第三行:Content-Type: 文件的 mime 类型 + “\r\n”
这一行是文件上传必须要的,而普通的文字提交可有可无,mime 类型需要根据文档查询;
4、第四行:”\r\n”
5、第五行文件的二进制数据 + “\r\n”:
这里跟普通表单提交一样;
结尾行:”–” + boundary + “–” + “\r\n”
可以看到,文件上传的诗句格式跟我们上一篇博文中讲到的表单提交只有两个地方不同,1、第二行的时候增加了一个文件名变量,2、增加了一行Content-Type: 文件的 mime 类型 + “\r\n”;
文件也可以同时上传多个文件,上传多个文件的时候重复1、2、3、4、5步,在最后的一个文件的末尾加上统一的结束行。
文件实体类
/**
* 文件实体类
* Created by Hlh on 2016/4/14.
*/
public class FileParams {
//参数名称
private String name;
//文件名称
private String fileName;
//文件类型, 即上传类型
private String mime;
//文件路径
private String url;
public FileParams(String url) {
this.url = url;
}
public String getFileName() {
return fileName;
}
/**
* 讲文件转化为二进制
*/
public byte[] getValue() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Bitmap bitmap = BitmapFactory.decodeFile(url);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
return bos.toByteArray();
}
/**
* 这里根据需要自己设置上传类型, octet-stream:接收所有类型文件 image/png:代码png格式图片
* @return
*/
public String getMime() {
return "application/octet-stream; charset=\" + \"utf-8\"";
}
public String getName() {
return name;
}
}
Volley 对文件数据的封装
/**
* Created by Hlh on 2016/4/11.
*/
public class PostUploadRequest extends Request<String> {
private ResponseListener mListener;
/**请求 数据通过参数的形式传入*/
private List<FileParams> mListItem;
private String BOUNDARY = "--------------hlh"; //数据分隔线
private String MULTIPART_FORM_DATA = "multipart/form-data";
/**
* @param url 上传接口API
* @param listItem 文件路径集合
* @param listener 监听接口
*/
public PostUploadRequest(String url, List<FileParams> listItem, ResponseListener listener) {
super(Method.POST, url, listener);
this.mListener = listener;
this.mListItem = listItem;
//设置请求的响应事件,因为文件上传需要较长的时间,所以在这里加大了,设为10秒
setRetryPolicy(new DefaultRetryPolicy(10000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
/**
* 这里开始解析数据
*
* @param networkResponse Response from the network
* @return
*/
@Override
protected Response<String> parseNetworkResponse(NetworkResponse networkResponse) {
try {
String mString = new String(networkResponse.data, HttpHeaderParser.parseCharset(networkResponse.headers));
Log.v("Hlh", "====mString===" + mString);
return Response.success(mString,
HttpHeaderParser.parseCacheHeaders(networkResponse));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
/**
* 回调正确的数据
* @param response The parsed response returned by
*/
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
@Override
public byte[] getBody() throws AuthFailureError {
if (mListItem == null||mListItem.size() == 0){
return super.getBody() ;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream() ;
int N = mListItem.size() ;
FileParams FileParams ;
for (int i = 0; i < N; i++) {
FileParams = mListItem.get(i);
StringBuilder sb = new StringBuilder();
/**
* 第一行
* "--" + BOUNDARY + "\r\n"
*/
sb.append("--" + BOUNDARY);
sb.append("\r\n");
/**
* 第二行
* Content-Disposition: form-data; name="参数的名称"; filename="上传的文件名" + "\r\n"
*/
sb.append("Content-Disposition: form-data;");
sb.append(" name=\"");
sb.append(FileParams.getName()) ;
sb.append("\"");
sb.append("; filename=\"") ;
sb.append(FileParams.getFileName()) ;
sb.append("\"");
sb.append("\r\n");
/**
* 第三行
* Content-Type: 文件的 mime 类型 + "\r\n"
*/
sb.append("Content-Type: ");
sb.append(FileParams.getMime()) ;
sb.append("\r\n");
/**
* 第四行
* "\r\n"
*/
sb.append("\r\n");
try {
bos.write(sb.toString().getBytes("utf-8"));
/**
* 第五行
* 文件的二进制数据 + "\r\n"
*/
bos.write(FileParams.getValue());
bos.write("\r\n".getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
/**
* 结尾行
* "--" + BOUNDARY + "--" + "\r\n"
*/
String endLine = "--" + BOUNDARY + "--" + "\r\n";
try {
bos.write(endLine.toString().getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
Log.v("Hlh","=====FileParams====\n"+bos.toString()) ;
return bos.toByteArray();
}
return super.getBody();
}
//Content-Type: multipart/form-data; boundary=----------Hlh
@Override
public String getBodyContentType() {
return MULTIPART_FORM_DATA+"; boundary="+BOUNDARY;
}
}
文件上传接口
public class UploadApi {
/**
* 上传图片接口
* @param bitmap 需要上传的图片
* @param listener 请求回调
*/
public static void uploadImg(String fileUrl,ResponseListener listener){
List<FileParams> imageList = new ArrayList<FileParams>() ;
imageList.add(new FileParams(fileUrl)) ;
Request request = new PostUploadRequest(Constant.UploadHost,imageList,listener) ;
VolleyUtil.getRequestQueue().add(request) ;
}
}
图片上传验证
public class PostUploadActivity extends ActionBarActivity {
private TextView mShowResponse ;
private ImageView mImageView ;
private ProgressDialog mDialog ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload_img);
mShowResponse = (TextView) findViewById(R.id.id_show_response) ;
mImageView = (ImageView) findViewById(R.id.id_show_img) ;
mDialog = new ProgressDialog(this) ;
mDialog.setCanceledOnTouchOutside(false);
}
public void uploadImg(View view){
mDialog.setMessage("图片上传中...");
mDialog.show();
UploadApi.uploadImg(R.string.fileUrl,new ResponseListener<String>() {
@Override
public void onErrorResponse(VolleyError error) {
mShowResponse.setText("ErrorResponse\n"+error.getMessage());
Toast.makeText(PostUploadActivity.this,"上传失败",Toast.LENGTH_SHORT).show() ;
mDialog.dismiss();
}
@Override
public void onResponse(String response) {
response = response.substring(response.indexOf("img src="));
response = response.substring(8,response.indexOf("/>")) ;
mShowResponse.setText("图片地址:\n"+response);
mDialog.dismiss();
Toast.makeText(PostUploadActivity.this,"上传成功",Toast.LENGTH_SHORT).show();
}
}) ;
}
}
volley 实现文件上传的操作还是很方便的