Android网络编程(六) 之 Volley实战,上传文件到腾讯云

我们在上一博文《Android网络编程(五) 之 Volley框架的使用》中简单介绍了利用Volley进行网络请求的GET和POST的基本使用。今天我们进一步探讨怎样使用Volley来进行文件的上传。

1 表单结构

文件上传其实就是进行表单的提交,只不过表单提交中有某个字段是该文件的二进制值。我们就拿腾讯云上传API接入来讲解Volley上传的使用步聚,开始前我们来看看表单提交的数据格式是怎么样的。下面是腾讯云上传文件的一个示例抓包数据:

POST /files/v2/<向腾讯云申请的appid>/<appid里头对应的bucket_name>/[文件夹]/<文件名> HTTP/1.1
Accept: */*
Authorization: XXX这里是腾讯云appid等一系列值生成的签名XXX
Content-Type: multipart/form-data;boundary=------------------------------分界字符
User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.0.2; MI2 MIUI/6.10.20)
Host: gz.file.myqcloud.com
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 778
 
--------------------------------分界字符
Content-Disposition: form-data;name="insertOnly"
 
0
--------------------------------分界字符
Content-Disposition: form-data; name="op"
 
upload
--------------------------------分界字符
Content-Disposition: form-data; name="sha"
 
XXX这里是文件的SHA-1值XXX
--------------------------------分界字符
Content-Disposition: form-data;name="biz_attr"
 
 
--------------------------------分界字符
Content-Disposition: form-data;name="filecontent"
Content-Type: text/plain
 
XXX这里是文件转二进制的byte[]值XXX
--------------------------------分界字符—

我们来分析下上面的数据包,它分两部分,第1行到第9行是headers,第10行到最后是body(提交的数据)。在Volley中headers一般大多都可自动生成,除了几个点要指定,如第1行POST到HTTP/1.1之间的是提交的URL;第4行Content-Type后面是指定提交参数的类型,而boundary后面的是自定义的分界字符,要跟下面的保持一致;腾讯云还要特别加上签名校验,那就是第3行的。接下来继续分析body部分,body部分的所有数据都是要手动拼接来生成的,欣慰的是它们都是有规律。

 

2 UploadRequest

我们在《Android网络编程(五) 之 Volley框架的使用》中介绍过StringRequestJsonRequestImageRequest的使用,它们都是继承于Request类,所以我们完全可以模仿它们来写一个UploadRequest。

新建UploadRequest类,如下:

public class UploadRequest extends Request<JSONObject> {

    private final static int TIME_OUT = 5 *60 * 1000;                                                  // 5分钟

    private Map<String,Object> mParams;
    private byte[] mFileByte;
    private String mAuthorization;
    private finalResponse.Listener<JSONObject> mListener;

    public UploadRequest(String url, Map<String, Object> params, String authorization, byte[]fileByte, Response.Listener<JSONObject> listener, Response.ErrorListenererrorListener) {
        super(Request.Method.POST,url, errorListener);
        mParams =params;
        mAuthorization= authorization;
        mFileByte =fileByte;
        this.mListener= listener;

        setShouldCache(false);
        setRetryPolicy(newDefaultRetryPolicy(TIME_OUT,DefaultRetryPolicy.DEFAULT_MAX_RETRIES,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
    }

    @Override
    protected Response<JSONObject>parseNetworkResponse(NetworkResponse response) {
        try {
            String je = newString(response.data,HttpHeaderParser.parseCharset(response.headers,"utf-8"));
            return Response.success(newJSONObject(je), HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException var3) {
            return Response.error(newParseError(var3));
        } catch (JSONException var4) {
            return Response.error(newParseError(var4));
        }
    }

    @Override
    protected void deliverResponse(JSONObject jsonObject) {
        if(mListener != null){
            mListener.onResponse(jsonObject);
        }
    }
}

第一步,使UploadRequest类继承Request,因为腾讯云上传后返回是JSON类型的数据,所以指定为JSONObject类型;

第二步,定义构造函数,构造函数接收请求的url、参数、腾讯云签名、要上传文件的二进制值、上传成功回调和上传失败回调。函数实体中设置了Volley的缓存情况为false和请求的超时时间,这里定5分钟。当然这两个值也可以从构造函数中传入来指定;

第三步,重写必要的两个方法parseNetworkResponse和deliverResponse,parseNetworkResponse我们就把JsonRequest类内的parseNetworkResponse方法照搬即可,deliverResponse是指定上传成功的回调;

接下来,继续重写三个非常重要的方法,它们是getBodyContentType、getHeaders和getBody:

private final static String BOUNDARY = "------------------------------lyz_zyx";   // 数据分界线

@Override
public String getBodyContentType() {
    return "multipart/form-data;boundary=" + BOUNDARY;
}

@Override
public Map<String, String> getHeaders() throwsAuthFailureError {
    Map<String, String> headers = newHashMap<>();
    headers.putAll(super.getHeaders());
   if (mAuthorization!= null && mAuthorization.isEmpty()){
        headers.put("Authorization",mAuthorization);
    }
    headers.put("Accept","*/*");             //这句一定要加上,否则上传的腾讯云上的文件会是0B,若加sha1校验,则上传失败
    return headers;
}

@Override
public byte[] getBody() throwsAuthFailureError {
    if (mParams== null) {
        return null;
    }
    ByteArrayOutputStream bos = newByteArrayOutputStream() ;
    String enterNewline = "\r\n";
    String fix = "--";
    StringBuffer sb= newStringBuffer() ;
    try {
        Iterator iter = mParams.entrySet().iterator();
        while (iter.hasNext()){
            Map.Entry entry = (Map.Entry)iter.next();
            Object key = entry.getKey();
            Object val =entry.getValue();

            sb.append(fix + BOUNDARY+ enterNewline);
            sb.append("Content-Disposition:form-data; name=\"" +key + "\"" +enterNewline);
            sb.append(enterNewline);
            sb.append(val +enterNewline);
        }
        sb.append(fix + BOUNDARY+ enterNewline);
        sb.append("Content-Disposition:form-data; name=\"filecontent\""+ enterNewline);
        sb.append("Content-Type:text/plain" + enterNewline);
        sb.append(enterNewline);

        bos.write(sb.toString().getBytes("utf-8"));
        bos.write(mFileByte);

        StringBuffer sbEnd= newStringBuffer() ;
        sbEnd.append(enterNewline) ;
        sbEnd.append(fix + BOUNDARY+ fix + enterNewline);

        bos.write(sbEnd.toString().getBytes("utf-8"));
    } catch (IOExceptione) {
        e.printStackTrace();
    }

    return bos.toByteArray();
}

getBodyContentType方法返回的字符串,就是我们上面提到的headers中的第4行内容;

getHeaders方法添加了两项值,分别是Accept和Authorization。也就是我们上面提到在headers中的第2行和第3行的内容;

getBody方法就是我们上面提到的需要手动拼接生成的body部分,它的前面做了一个针对构造函数传入的mParams值的循环,循环完后再接定拼构造函数传入的mFileByte部分。

就这样整个UploadRequest类就大功告成。虽然我们的初衷是针对腾讯云上传功能做的设计,但其实UploadRequest类也是完全通用于其它普通表单提交上传文件的,只要不传入签名即可。

最后,就是如何调用了:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button btn1 =(Button)findViewById(R.id.btn1);
    btn1.setOnClickListener(newView.OnClickListener() {
        @Override
        public void onClick(Viewv) {
            uploadFile("/sdcard/upload_log.zip");
        }
    });
}

/**
 * 执行上传文件
 * @param filePath
 */
private void uploadFile(String filePath) {
    File file = newFile(filePath);
    if (!file.exists()){
        return;
    }
    UploadFileData uploadFileData = newUploadFileData();
   uploadFileData.setFileName(file.getName());
    Common.getFileByteAndSha1(file,uploadFileData);

    String url = String.format(REQUEST_UPLOAD_URL,uploadFileData.getFileName());

    Map<String, Object> params = newHashMap<>();
    params.put("op","upload");
    params.put("sha",uploadFileData.getFileSha1().toLowerCase()); // 注意,sha1值必须小写,这点腾讯云API没提到
    params.put("biz_attr","");
    params.put("insertOnly",0);

    String authorization = getSign();

    RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext());
    UploadRequest uploadRequest = new UploadRequest(url, params, authorization,uploadFileData.getFileByte(),
            new Response.Listener<JSONObject>(){
                @Override
                publicvoid onResponse(JSONObject jsonObject) {
                    //TODO 上传成功
                }
            },
            new Response.ErrorListener(){
                @Override
                publicvoid onErrorResponse(VolleyError volleyError) {
                    //TODO 上传失败
                }
            });
    requestQueue.add(uploadRequest);
}

对应代码,调用过程是先指定一个手机文件目录,然后对应文件并算出它的sha1值和二进制值,接着通过腾讯云提供的资料算出本次上传的签名,最后一步就是构造一个我们准备好的UploadRequest类的对象然后交给RequestQueue就好了。还有一点,别忘记了声明相应的权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

3 总结

本博文主要是描述了表单的结构和利用Volley怎样生成表单来请求网络。关于腾讯云的使用,我们示例为了解析表单的提交,所以给出了最原始的使用方法。如果您项目不介意包的大小,完全可以使用腾讯云API中出提供了Android SDK包,只要简单地在项目中引用其jar后,对应API介绍方法可以非常简单的十来行代码便可完成整个上传功能,这里就不作过多的介绍了,详细可见腾讯去APIhttps://www.qcloud.com/document/api/436/6066,另外本项目完整源码可点击这里下载。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章