Android實現模擬表單上傳


很久以前,寫過一篇關於下載的文章:基於HTTP協議的下載功能實現,今天對於Android上的文件上傳,也簡單的提兩筆。在Android上,一般使用Http 模擬表單或者FTP來進行文件上傳,使用FTP協議,可以直接使用Appache的FTPClient,使用方法很簡單,不再贅述。這里主要說明一下Http模擬表單上傳的實現。

模擬表單上傳,其實也很簡單,主要需要在Http post 的數據體中構建表單信息(multipart/form),表單數據格式的規范,可以參考REC標准。下面是一個格式示例:

       ...
Content-Type: multipart/form-data; boundary=
------WebKitFormBoundaryK7Ck1eEROPVUf1De
       Content-Length: 145000
...
------WebKitFormBoundaryK7Ck1eEROPVUf1De Content-Disposition: form-data; name="fileKey"; filename="bg_entry.png" Content-Type: image/png DATA OF FILE
       ------WebKitFormBoundaryK7Ck1eEROPVUf1De--
 
        

表單請求重點在兩部分:

Header

1.通過Content-Type告知Server這是一個表單提交請求,並聲明自己使用的Boundary。Boundary相當於一個分隔符,用於標志表單數據的開始和結束。

2.通過Content-Length告訴本次請求的數據長度,Post Body的長度(包括上傳文件長度)。

 

Body:

1.以Boundary分割表單數據。

2.表單參數相當於簡單的Header,一般包括Content-Disposition(文件信息)和Content-Type(數據類型)兩個字段。

3.各部分、各字段之間都要以CRLF分割。

4.最后以Boundary加上“--”結束表單請求。

 

核心代碼如下:

    protected String doUpload(HttpURLConnection connection, UploadParam param) throws Exception {
        String path = param.getPath();
        String fileKey = TextUtils.isEmpty(param.getFileKey()) ? "file" : param.getFileKey();
        String fileName = param.getFileName();
        String fileType = TextUtils.isEmpty(param.getContentType()) ? MIME_TYPE_ALL : param.getContentType();

        DataOutputStream outs = null;
        BufferedReader ins = null;
        FileInputStream fouts = null;
        String response = null;
        try {
            //    Content-Disposition: form-data; name="fileKey"; filename="bg_entry.png"
           //            Content-Type: image/png
            StringBuilder builder = new StringBuilder(buildParams(param.getParams()));
            builder.append(getBoundaryPrefixed())
                    .append(CRLF)
                    .append(String.format(HEADER_CONTENT_DISPOSITION + COLON_SPACE + FORM_DATA + SEMICOLON_SPACE + FILENAME, fileKey, fileName))
                    .append(CRLF)
                    .append(HEADER_CONTENT_TYPE).append(fileType)
                    .append(CRLF)
                    //Must jump to new line to indicate the beginning of data.
                    .append(CRLF);
            byte[] headBuf = builder.toString().getBytes(CHARSET_UTF8);
            //Must jump to new line to indicate the end of data.
            byte[] tailBuf = (CRLF + getBoundaryPrefixed() + BOUNDARY_PREFIX + CRLF).getBytes(CHARSET_UTF8);
            long currentBytes = 0;
            File file = new File(path);
            long totalSize = file.length() + headBuf.length + tailBuf.length;
            //Generally speaking,Files larger than 4M should use streaming mode.
            if (totalSize > 4 * 1024 * 1024) {
                //Avoid oom when post large file.Ether way is ok.
                connection.setChunkedStreamingMode(1024);
//                connection.setFixedLengthStreamingMode(totalSize);
            }
            connection.setRequestProperty(HEADER_CONTENT_LENGTH, String.valueOf(totalSize));
            connection.connect();

            outs = new DataOutputStream(connection.getOutputStream());
            outs.write(headBuf);
            currentBytes += headBuf.length;
            updateProgress(currentBytes, totalSize);
            fouts = new FileInputStream(file);
            byte[] buffer = new byte[1024];
            int length = -1;
            long startTime = System.currentTimeMillis();
            long now = 0;
            while ((length = fouts.read(buffer)) != -1) {
                if (length > 0) {
                    outs.write(buffer, 0, length);
                    currentBytes += length;
                    now = System.currentTimeMillis();
                    if (now - startTime >= PROGRESS_RATE) {
                        updateProgress(currentBytes, totalSize);
                        startTime = now;
                    }
                }
                if (!canRun()) {
                    throw new Exception("Upload cancelled");
                }
            }
            outs.write(tailBuf);
            outs.flush();
            updateProgress(totalSize, totalSize);

            fouts.close();
            fouts = null;

            //Response.
            if (connection.getResponseCode() != 200) {
                throw new IllegalStateException(String.format("Error upload response: code:%s  msg:%s", connection.getResponseCode(), connection.getResponseMessage()));
            }
            ins = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            StringBuffer b = new StringBuffer();
            while ((line = ins.readLine()) != null) {
                b.append(line);
                if (!canRun()) {
                    throw new Exception("Upload cancelled");
                }
            }

            response = b.toString();
            if (TextUtils.isEmpty(response)) {
                throw new NullPointerException("Null response: " + response);
            }
            outs.close();
            outs = null;
            ins.close();
            ins = null;
        } finally {
            if (fouts != null) {
                fouts.close();
                fouts = null;
            }
            if (outs != null) {
                outs.close();
                outs = null;
            }
            if (ins != null) {
                ins.close();
                ins = null;
            }
        }
        return response;
    }

主要步湊為:

1.配置Header參數

2.構建表單參數

3.讀取和發送文件內容

4.獲取響應碼

其中值得注意的是,一般情況下,上傳會把所有的文件內容讀取到內存中再統一發送,如果文件過大,將可能導致內存溢出。所以在判斷文件內容大於4MB時,使用Chunked模式或Stream模式來避免OOM。

            if (totalSize > 4 * 1024 * 1024) {
                //Avoid oom when post large file.Ether way is ok.
                connection.setChunkedStreamingMode(1024);
                //connection.setFixedLengthStreamingMode(totalSize);
            }

 

更多代碼詳情請參考:TransferLibrary——一個Android文件傳輸庫,主要實現基於Http的文件上傳和下載,簡單方便,支持多任務下載,斷點續傳等等,歡迎小伙伴們使用交流:D

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM