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,另外本項目完整源碼可點擊這裏下載。

 

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