好久之前,寫過一篇關於下載的文章:基於HTTP協議的下載功能實現,今天對於Android上的文件上傳,也簡單的提兩筆。在Android上,通常使用Http 模擬表單或者FTP來進行文件上傳,使用FTP協議,能夠直接使用Appache的FTPClient,使用方法很簡單,再也不贅述。這裏主要說明一下Http模擬表單上傳的實現。html
模擬表單上傳,其實也很簡單,主要須要在Http post 的數據體中構建表單信息(multipart/form),表單數據格式的規範,能夠參考REC標準。下面是一個格式示例:git
...
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--
表單請求重點在兩部分:github
Header:app
1.經過Content-Type告知Server這是一個表單提交請求,並聲明本身使用的Boundary。Boundary至關於一個分隔符,用於標誌表單數據的開始和結束。post
2.經過Content-Length告訴本次請求的數據長度,Post Body的長度(包括上傳文件長度)。ui
Body:url
1.以Boundary分割表單數據。spa
2.表單參數至關於簡單的Header,通常包括Content-Disposition(文件信息)和Content-Type(數據類型)兩個字段。code
3.各部分、各字段之間都要以CRLF分割。orm
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