OSS提供的分區上傳(Multipart Upload)功能,將要上傳的較大檔案(Object)分成多個分區(Part)來分別上傳,上傳完成後再調用CompleteMultipartUpload介面將這些Part組合成一個Object來達到斷點續傳的效果。
注意事項
使用本文樣本前您需要先通過自訂網域名、STS等方式建立OSSClient,具體請參見如何初始化Android端OSSClient執行個體。
分區上傳流程
分區上傳(Multipart Upload)分為以下三個步驟:
初始化一個分區上傳事件。
調用oss.initMultipartUpload方法返回OSS建立的全域唯一的uploadId。
上傳分區。
調用oss.uploadPart方法上傳分區資料。
說明對於同一個uploadId,分區號(PartNumber)標識了該分區在整個檔案內的相對位置。如果使用同一個分區號上傳了新的資料,則OSS上該分區已有的資料將會被覆蓋。
OSS將收到的分區資料的MD5值放在ETag頭內返回給使用者。
OSS計算上傳資料的MD5值,並與SDK計算的MD5值比較,如果不一致則返回InvalidDigest錯誤碼。
完成分區上傳。
所有分區上傳完成後,調用oss.CompleteMultipartUpload方法將所有Part合并成完整的檔案。
分區上傳完整樣本
以下通過一個完整的樣本對分區上傳的流程進行逐步解析:
// 填寫Bucket名稱,例如examplebucket。
String bucketName = "examplebucket";
// 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
String objectName = "exampledir/exampleobject.txt";
// 填寫本地檔案完整路徑,例如/storage/emulated/0/oss/examplefile.txt。
String localFilepath = "/storage/emulated/0/oss/examplefile.txt";
// 初始化分區上傳。
InitiateMultipartUploadRequest init = new InitiateMultipartUploadRequest(bucketName, objectName);
InitiateMultipartUploadResult initResult = oss.initMultipartUpload(init);
// 返回uploadId。
String uploadId = initResult.getUploadId();
// 根據uploadId執行取消分區上傳事件或者列舉已上傳分區的操作。
// 如果您需要根據您需要uploadId執行取消分區上傳事件的操作,您需要在調用InitiateMultipartUpload完成初始化分區之後擷取uploadId。
// 如果您需要根據您需要uploadId執行列舉已上傳分區的操作,您需要在調用InitiateMultipartUpload完成初始化分區之後,且在調用CompleteMultipartUpload完成分區上傳之前擷取uploadId。
// Log.d("uploadId", uploadId);
// 設定單個Part的大小,單位為位元組,取值範圍為100 KB~5 GB。
int partCount = 100 * 1024;
// 分區上傳。
List<PartETag> partETags = new ArrayList<>();
for (int i = 1; i < 5; i++) {
byte[] data = new byte[partCount];
RandomAccessFile raf = new RandomAccessFile(localFilepath, "r");
long skip = (i-1) * partCount;
raf.seek(skip);
raf.readFully(data, 0, partCount);
UploadPartRequest uploadPart = new UploadPartRequest();
uploadPart.setBucketName(bucketName);
uploadPart.setObjectKey(objectName);
uploadPart.setUploadId(uploadId);
// 設定分區號,從1開始標識。每一個上傳的Part都有一個分區號,取值範圍是1~10000。
uploadPart.setPartNumber(i);
uploadPart.setPartContent(data);
try {
UploadPartResult result = oss.uploadPart(uploadPart);
PartETag partETag = new PartETag(uploadPart.getPartNumber(), result.getETag());
partETags.add(partETag);
} catch (ServiceException serviceException) {
OSSLog.logError(serviceException.getErrorCode());
}
}
Collections.sort(partETags, new Comparator<PartETag>() {
@Override
public int compare(PartETag lhs, PartETag rhs) {
if (lhs.getPartNumber() < rhs.getPartNumber()) {
return -1;
} else if (lhs.getPartNumber() > rhs.getPartNumber()) {
return 1;
} else {
return 0;
}
}
});
// 完成分區上傳。
CompleteMultipartUploadRequest complete = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
// 上傳回調。完成分區上傳請求時可以設定CALLBACK_SERVER參數,請求完成後會向指定的Server Address發送回調請求。可通過返回結果的completeResult.getServerCallbackReturnBody()查看servercallback結果。
complete.setCallbackParam(new HashMap<String, String>() {
{
put("callbackUrl", CALLBACK_SERVER); //修改為您的伺服器位址。
put("callbackBody", "test");
}
});
CompleteMultipartUploadResult completeResult = oss.completeMultipartUpload(complete);
OSSLog.logError("-------------- serverCallback: " + completeResult.getServerCallbackReturnBody());
上述代碼調用uploadPart來上傳每一個Part。
每一個分區上傳請求均需指定uploadId和PartNumber。PartNumber的範圍是1~10000。如果超出該範圍,OSS將返回InvalidArgument的錯誤碼。
uploadPart要求除最後一個Part外,其他的Part大小都要大於100 KB。uploadPart僅在完成分區上傳時校正Part的大小。
每次上傳Part時都要將流定位至此次上傳片開頭所對應的位置。
每次上傳Part之後,OSS的返回結果會包含一個Part的ETag值,ETag值為Part資料的MD5值,您需要將ETag值和塊編號組合成PartEtag並儲存,用於後續完成分區上傳。
本地檔案分區上傳
您可以通過同步方式或者非同步方式分區上傳本地檔案到OSS。
分區上傳完整樣本是按照分區上傳流程逐步實現的完整代碼,本地檔案分區上傳的代碼是將分區上傳完整樣本中的代碼進行了封裝,您只需要使用MultipartUploadRequest即可實現分區上傳。
調用同步介面分區上傳本地檔案
以下代碼用於以同步方式分區上傳examplefile.txt檔案到目標儲存空間examplebucket中exampledir目錄下的exampleobject.txt檔案。
// 填寫Bucket名稱,例如examplebucket。 String bucketName = "examplebucket"; // 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。 String objectName = "exampledir/exampleobject.txt"; // 填寫本地檔案完整路徑,例如/storage/emulated/0/oss/examplefile.txt。 String localFilepath = "/storage/emulated/0/oss/examplefile.txt"; ObjectMetadata meta = new ObjectMetadata(); // 設定檔案中繼資料等。 meta.setHeader("x-oss-object-acl", "public-read-write"); MultipartUploadRequest rq = new MultipartUploadRequest(bucketName, objectName, localFilepath, meta); // 設定PartSize。PartSize預設值為256 KB,最小值為100 KB。 rq.setPartSize(1024 * 1024); rq.setProgressCallback(new OSSProgressCallback<MultipartUploadRequest>() { @Override public void onProgress(MultipartUploadRequest request, long currentSize, long totalSize) { OSSLog.logDebug("[testMultipartUpload] - " + currentSize + " " + totalSize, false); } }); CompleteMultipartUploadResult result = oss.multipartUpload(rq);
對於Android10及之後版本的分區儲存,您可以使用檔案的Uri上傳檔案到OSS。
// 填寫Bucket名稱,例如examplebucket。 String bucketName = "examplebucket"; // 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。 String objectName = "exampledir/exampleobject.txt"; ObjectMetadata meta = new ObjectMetadata(); // 設定檔案中繼資料等。 meta.setHeader("x-oss-object-acl", "public-read-write"); MultipartUploadRequest rq = new MultipartUploadRequest(bucketName, objectName, fileUri, meta); // 設定PartSize。PartSize預設值為256 KB,最小值為100 KB。 rq.setPartSize(1024 * 1024); rq.setProgressCallback(new OSSProgressCallback<MultipartUploadRequest>() { @Override public void onProgress(MultipartUploadRequest request, long currentSize, long totalSize) { OSSLog.logDebug("[testMultipartUpload] - " + currentSize + " " + totalSize, false); } }); CompleteMultipartUploadResult result = oss.multipartUpload(rq);
調用非同步介面分區上傳本地檔案
以下代碼用於以非同步方式分區上傳examplefile.txt檔案到目標儲存空間examplebucket中exampledir目錄下的exampleobject.txt檔案。
// 填寫Bucket名稱,例如examplebucket。 String bucketName = "examplebucket"; // 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。 String objectName = "exampledir/exampleobject.txt"; // 填寫本地檔案完整路徑,例如/storage/emulated/0/oss/examplefile.txt。 String localFilepath = "/storage/emulated/0/oss/examplefile.txt"; MultipartUploadRequest request = new MultipartUploadRequest(bucketName, objectName, localFilepath); request.setProgressCallback(new OSSProgressCallback<MultipartUploadRequest>() { @Override public void onProgress(MultipartUploadRequest request, long currentSize, long totalSize) { OSSLog.logDebug("[testMultipartUpload] - " + currentSize + " " + totalSize, false); } }); OSSAsyncTask task = oss.asyncMultipartUpload(request, new OSSCompletedCallback<MultipartUploadRequest, CompleteMultipartUploadResult>() { @Override public void onSuccess(MultipartUploadRequest request, CompleteMultipartUploadResult result) { OSSLog.logInfo(result.getServerCallbackReturnBody()); } @Override public void onFailure(MultipartUploadRequest request, ClientException clientException, ServiceException serviceException) { OSSLog.logError(serviceException.getRawMessage()); } }); //Thread.sleep(100); // 取消分區上傳。 //task.cancel(); task.waitUntilFinished();
對於Android10及之後版本的分區儲存,您可以使用檔案的Uri上傳檔案到OSS。
// 填寫Bucket名稱,例如examplebucket。 String bucketName = "examplebucket"; // 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。 String objectName = "exampledir/exampleobject.txt"; MultipartUploadRequest request = new MultipartUploadRequest(bucketName, objectName, fileUri); request.setProgressCallback(new OSSProgressCallback<MultipartUploadRequest>() { @Override public void onProgress(MultipartUploadRequest request, long currentSize, long totalSize) { OSSLog.logDebug("[testMultipartUpload] - " + currentSize + " " + totalSize, false); } }); OSSAsyncTask task = oss.asyncMultipartUpload(request, new OSSCompletedCallback<MultipartUploadRequest, CompleteMultipartUploadResult>() { @Override public void onSuccess(MultipartUploadRequest request, CompleteMultipartUploadResult result) { OSSLog.logInfo(result.getServerCallbackReturnBody()); } @Override public void onFailure(MultipartUploadRequest request, ClientException clientException, ServiceException serviceException) { OSSLog.logError(serviceException.getRawMessage()); } }); //Thread.sleep(100); // 取消分區上傳。 //task.cancel(); task.waitUntilFinished();
列舉已上傳分區
調用oss.listParts方法擷取某個上傳事件所有已上傳的分區。
以下代碼用於列舉已上傳分區。
// 填寫Bucket名稱,例如examplebucket。
String bucketName = "examplebucket";
// 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
String objectName = "exampledir/exampleobject.txt";
// 填寫uploadId。uploadId來源於調用InitiateMultipartUpload完成初始化分區之後,且在調用CompleteMultipartUpload完成分區上傳之前的返回結果。
String uploadId = "0004B999EF518A1FE585B0C9****";
// 列舉分區。
ListPartsRequest listParts = new ListPartsRequest(bucketName, objectName, uploadId);
ListPartsResult result = oss.listParts(listParts);
List<PartETag> partETagList = new ArrayList<PartETag>();
for (PartSummary part : result.getParts()) {
partETagList.add(new PartETag(part.getPartNumber(), part.getETag()));
}
預設情況下,如果儲存空間中的分區上傳事件的數量大於1000,則OSS僅返回1000個Multipart Upload資訊,且返回結果中IsTruncated的值為false,並返回NextPartNumberMarker作為下次讀取的起點。
取消分區上傳事件
調用oss.abortMultipartUpload方法來取消分區上傳事件。當一個分區上傳事件被取消後,無法再使用該uploadId進行任何操作,已上傳的分區資料會被刪除。
以下代碼用於取消分區上傳事件。
// 填寫Bucket名稱,例如examplebucket。
String bucketName = "examplebucket";
// 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
String objectName = "exampledir/exampleobject.txt";
// 填寫uploadId。uploadId來源於調用InitiateMultipartUpload完成初始化分區之後的返回結果。
String uploadId = "0004B999EF518A1FE585B0C9****";
// 取消分區上傳。
AbortMultipartUploadRequest abort = new AbortMultipartUploadRequest(bucketName, objectName, uploadId);
AbortMultipartUploadResult abortResult = oss.abortMultipartUpload(abort);
相關文檔
關於分區上傳的完整範例程式碼,請參見GitHub樣本。
分區上傳的完整實現涉及三個API介面,詳情如下:
關於初始化分區上傳事件的API介面說明,請參見InitiateMultipartUpload。
關於分區上傳Part的API介面說明,請參見UploadPart。
關於完成分區上傳的API介面說明,請參見CompleteMultipartUpload。
關於取消分區上傳事件的API介面說明,請參見AbortMultipartUpload。
關於列舉已上傳分區的API介面說明,請參見ListParts。
關於列舉所有執行中的分區上傳事件(即已初始化但尚未完成或已取消的分區上傳事件)的API介面說明,請參見ListMultipartUploads。
關於初始化OSSClient,請參見如何初始化Android端OSSClient執行個體。