前面我們講了如何通過 volley 實現表單的提交,而這篇文章跟上1篇銜接很大,如果沒有看上1篇 blog 的朋友,建議先去看看 Android Volley解析(2)之表單提交篇
由于文件上傳實質就是表單的提交,只不過它提交的數據包括文件類型,接下來還是依照表單提交的套路來分析。
這里我們通過圖片上傳的案例來分析,其他文件也是一樣的實現方式;以下是我在傳圖網傳圖時,上傳的數據格式,先來分析1下
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--
不難發現,這類格式跟表單提交的格式非常接近,不過還是有所差別,這里仔細看還是能看出來總共有加上結尾行,有5行,由于亂碼部份,其實就是圖片的2進制數,全部算1行;下面來分析下:
1、第1行:"--" + boundary + "
"
;
前面也說了文件上傳,其實就是表單提交,所以在提交數據的開始標志不變;
2、第2行:Content-Disposition: form-data; name="參數的名稱"; filename="上傳的文件名" + "
"
這里比普通的表單多了1個filename=”上傳的文件名”;
3、第3行:Content-Type: 文件的 mime 類型 + "
"
這1行是文件上傳必須要的,而普通的文字提交可有可無,mime 類型需要根據文檔查詢;
4、第4行:"
"
5、第5行文件的2進制數據 + "
"
:
這里跟普通表單提交1樣;
結尾行:"--" + boundary + "--" + "
"
可以看到,文件上傳的詩句格式跟我們上1篇博文中講到的表單提交只有兩個地方不同,1、第2行的時候增加了1個文件名變量,2、增加了1行Content-Type: 文件的 mime 類型 + "
"
;
文件也能夠同時上傳多個文件,上傳多個文件的時候重復1、2、3、4、5步,在最后的1個文件的末尾加上統1的結束行。
這里是對圖片操作所以我建了1個FormImg.java
/**
* Created by moon.zhong on 2015/3/3.
*/
public class FormImage {
//參數的名稱
private String mName ;
//文件名
private String mFileName ;
//文件的 mime,需要根據文檔查詢
private String mMime ;
//需要上傳的圖片資源,由于這里測試為了方便起見,直接把 bigmap 傳進來,真正在項目中1般不會這般做,而是把圖片的路徑傳過來,在這里對圖片進行2進制轉換
private Bitmap mBitmap ;
public FormImage(Bitmap mBitmap) {
this.mBitmap = mBitmap;
}
public String getName() {
// return mName;
//測試,把參數名稱寫死
return "uploadimg" ;
}
public String getFileName() {
//測試,直接寫死文件的名字
return "test.png";
}
//對圖片進行2進制轉換
public byte[] getValue() {
ByteArrayOutputStream bos = new ByteArrayOutputStream() ;
mBitmap.compress(Bitmap.CompressFormat.JPEG,80,bos) ;
return bos.toByteArray();
}
//由于我知道是 png 文件,所以直接根據文檔查的
public String getMime() {
return "image/png";
}
}
/**
* Created by gyzhong on 15/3/1.
*/
public class PostUploadRequest extends Request<String> {
/**
* 正確數據的時候回掉用
*/
private ResponseListener mListener ;
/*要求 數據通過參數的情勢傳入*/
private List<FormImage> mListItem ;
private String BOUNDARY = "-------------⑸20⑴3⑴4"; //數據分隔線
private String MULTIPART_FORM_DATA = "multipart/form-data";
public PostUploadRequest(String url, List<FormImage> listItem, ResponseListener listener) {
super(Method.POST, url, listener);
this.mListener = listener ;
setShouldCache(false);
mListItem = listItem ;
//設置要求的響應事件,由于文件上傳需要較長的時間,所以在這里加大了,設為5秒
setRetryPolicy(new DefaultRetryPolicy(5000,DefaultRetryPolicy.DEFAULT_MAX_RETRIES,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
/**
* 這里開始解析數據
* @param response Response from the network
* @return
*/
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
try {
String mString =
new String(response.data, HttpHeaderParser.parseCharset(response.headers));
Log.v("zgy", "====mString===" + mString);
return Response.success(mString,
HttpHeaderParser.parseCacheHeaders(response));
} 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() ;
FormImage formImage ;
for (int i = 0; i < N ;i++){
formImage = mListItem.get(i) ;
StringBuffer sb= new StringBuffer() ;
/*第1行*/
//`"--" + BOUNDARY + "
"`
sb.append("--"+BOUNDARY);
sb.append("
") ;
/*第2行*/
//Content-Disposition: form-data; name="參數的名稱"; filename="上傳的文件名" + "
"
sb.append("Content-Disposition: form-data;");
sb.append(" name="");
sb.append(formImage.getName()) ;
sb.append(""") ;
sb.append("; filename="") ;
sb.append(formImage.getFileName()) ;
sb.append(""");
sb.append("
") ;
/*第3行*/
//Content-Type: 文件的 mime 類型 + "
"
sb.append("Content-Type: ");
sb.append(formImage.getMime()) ;
sb.append("
") ;
/*第4行*/
//"
"
sb.append("
") ;
try {
bos.write(sb.toString().getBytes("utf⑻"));
/*第5行*/
//文件的2進制數據 + "
"
bos.write(formImage.getValue());
bos.write("
".getBytes("utf⑻"));
} catch (IOException e) {
e.printStackTrace();
}
}
/*結尾行*/
//`"--" + BOUNDARY + "--" + "
"`
String endLine = "--" + BOUNDARY + "--" + "
" ;
try {
bos.write(endLine.toString().getBytes("utf⑻"));
} catch (IOException e) {
e.printStackTrace();
}
Log.v("zgy","=====formImage====
"+bos.toString()) ;
return bos.toByteArray();
}
//Content-Type: multipart/form-data; boundary=---------⑻888888888888
@Override
public String getBodyContentType() {
return MULTIPART_FORM_DATA+"; boundary="+BOUNDARY;
}
}
由于代碼中注解寫的比較詳細,加上很多東西在前面幾篇 blog 已講過了,所以這里直接上代碼。
/**
* Created by moon.zhong on 2015/3/3.
*/
public class UploadApi {
/**
* 上傳圖片接口
* @param bitmap 需要上傳的圖片
* @param listener 要求回調
*/
public static void uploadImg(Bitmap bitmap,ResponseListener listener){
List<FormImage> imageList = new ArrayList<FormImage>() ;
imageList.add(new FormImage(bitmap)) ;
Request request = new PostUploadRequest(Constant.UploadHost,imageList,listener) ;
VolleyUtil.getRequestQueue().add(request) ;
}
}
上傳類PostUploadActivity.java
/**
* Created by moon.zhong on 2015/3/2.
*/
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();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.logo) ;
UploadApi.uploadImg(bitmap,new ResponseListener<String>() {
@Override
public void onErrorResponse(VolleyError error) {
Log.v("zgy","===========VolleyError========="+error) ;
mShowResponse.setText("ErrorResponse
"+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="http://www.vxbq.cn/uploadfile/cj/20150307/));
response = response.substring(8,response.indexOf("/>")) ;
Log.v("zgy","===========onResponse========="+response) ;
mShowResponse.setText("圖片地址:
"+response);
mDialog.dismiss();
Toast.makeText(PostUploadActivity.this,"上傳成功",Toast.LENGTH_SHORT).show();
}
}) ;
}
}
測試結果以下:
上傳圖片頁面:
圖片上傳中
圖片上傳成功,地址為http://www.chuantu.biz/t/67/1425474351x⑴376440163.png
通過網頁要求
可以看到,volley 實現文件上傳的操作還是很方便的,不過,不知道大家看到這里有無覺得哪里有問題呢?其實 volley 實現文件上傳是有1個很大的問題,甚么問題呢,大家自己先想一想,我將會在后續的文章中講到這個問題,并提供解決方案(是后續,不是下1篇)。volley 講到這里為止,對它的功能也講了1大部份,不過還有1個非常有用的知識點沒有講到,那就是volley緩存機制,下1節,將開啟 volley 的緩存之旅,敬請期待!
點擊下載源碼