Android分片上傳
OSS提供的分片上傳(Multipart Upload)功能,將要上傳的較大文件(Object)分成多個(gè)分片(Part)來分別上傳,上傳完成后再調(diào)用CompleteMultipartUpload接口將這些Part組合成一個(gè)Object來達(dá)到斷點(diǎn)續(xù)傳的效果。
注意事項(xiàng)
使用本文示例前您需要先通過自定義域名、STS等方式新建OSSClient,具體請參見如何初始化Android端OSSClient實(shí)例。
分片上傳流程
分片上傳(Multipart Upload)分為以下三個(gè)步驟:
初始化一個(gè)分片上傳事件。
調(diào)用oss.initMultipartUpload方法返回OSS創(chuàng)建的全局唯一的uploadId。
上傳分片。
調(diào)用oss.uploadPart方法上傳分片數(shù)據(jù)。
說明對于同一個(gè)uploadId,分片號(PartNumber)標(biāo)識了該分片在整個(gè)文件內(nèi)的相對位置。如果使用同一個(gè)分片號上傳了新的數(shù)據(jù),則OSS上該分片已有的數(shù)據(jù)將會被覆蓋。
OSS將收到的分片數(shù)據(jù)的MD5值放在ETag頭內(nèi)返回給用戶。
OSS計(jì)算上傳數(shù)據(jù)的MD5值,并與SDK計(jì)算的MD5值比較,如果不一致則返回InvalidDigest錯(cuò)誤碼。
完成分片上傳。
所有分片上傳完成后,調(diào)用oss.CompleteMultipartUpload方法將所有Part合并成完整的文件。
分片上傳完整示例
以下通過一個(gè)完整的示例對分片上傳的流程進(jìn)行逐步解析:
// 填寫B(tài)ucket名稱,例如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();
// 根據(jù)uploadId執(zhí)行取消分片上傳事件或者列舉已上傳分片的操作。
// 如果您需要根據(jù)您需要uploadId執(zhí)行取消分片上傳事件的操作,您需要在調(diào)用InitiateMultipartUpload完成初始化分片之后獲取uploadId。
// 如果您需要根據(jù)您需要uploadId執(zhí)行列舉已上傳分片的操作,您需要在調(diào)用InitiateMultipartUpload完成初始化分片之后,且在調(diào)用CompleteMultipartUpload完成分片上傳之前獲取uploadId。
// Log.d("uploadId", uploadId);
// 設(shè)置單個(gè)Part的大小,單位為字節(jié),取值范圍為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);
// 設(shè)置分片號,從1開始標(biāo)識。每一個(gè)上傳的Part都有一個(gè)分片號,取值范圍是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);
// 上傳回調(diào)。完成分片上傳請求時(shí)可以設(shè)置CALLBACK_SERVER參數(shù),請求完成后會向指定的Server Address發(fā)送回調(diào)請求??赏ㄟ^返回結(jié)果的completeResult.getServerCallbackReturnBody()查看servercallback結(jié)果。
complete.setCallbackParam(new HashMap<String, String>() {
{
put("callbackUrl", CALLBACK_SERVER); //修改為您的服務(wù)器地址。
put("callbackBody", "test");
}
});
CompleteMultipartUploadResult completeResult = oss.completeMultipartUpload(complete);
OSSLog.logError("-------------- serverCallback: " + completeResult.getServerCallbackReturnBody());
上述代碼調(diào)用uploadPart來上傳每一個(gè)Part。
每一個(gè)分片上傳請求均需指定uploadId和PartNumber。PartNumber的范圍是1~10000。如果超出該范圍,OSS將返回InvalidArgument的錯(cuò)誤碼。
uploadPart要求除最后一個(gè)Part外,其他的Part大小都要大于100 KB。uploadPart僅在完成分片上傳時(shí)校驗(yàn)Part的大小。
每次上傳Part時(shí)都要將流定位至此次上傳片開頭所對應(yīng)的位置。
每次上傳Part之后,OSS的返回結(jié)果會包含一個(gè)Part的ETag值,ETag值為Part數(shù)據(jù)的MD5值,您需要將ETag值和塊編號組合成PartEtag并保存,用于后續(xù)完成分片上傳。
本地文件分片上傳
您可以通過同步方式或者異步方式分片上傳本地文件到OSS。
分片上傳完整示例是按照分片上傳流程逐步實(shí)現(xiàn)的完整代碼,本地文件分片上傳的代碼是將分片上傳完整示例中的代碼進(jìn)行了封裝,您只需要使用MultipartUploadRequest即可實(shí)現(xiàn)分片上傳。
調(diào)用同步接口分片上傳本地文件
以下代碼用于以同步方式分片上傳examplefile.txt文件到目標(biāo)存儲空間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(); // 設(shè)置文件元數(shù)據(jù)等。 meta.setHeader("x-oss-object-acl", "public-read-write"); MultipartUploadRequest rq = new MultipartUploadRequest(bucketName, objectName, localFilepath, meta); // 設(shè)置PartSize。PartSize默認(rèn)值為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及之后版本的分區(qū)存儲,您可以使用文件的Uri上傳文件到OSS。
// 填寫Bucket名稱,例如examplebucket。 String bucketName = "examplebucket"; // 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。 String objectName = "exampledir/exampleobject.txt"; ObjectMetadata meta = new ObjectMetadata(); // 設(shè)置文件元數(shù)據(jù)等。 meta.setHeader("x-oss-object-acl", "public-read-write"); MultipartUploadRequest rq = new MultipartUploadRequest(bucketName, objectName, fileUri, meta); // 設(shè)置PartSize。PartSize默認(rèn)值為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);
調(diào)用異步接口分片上傳本地文件
以下代碼用于以異步方式分片上傳examplefile.txt文件到目標(biāo)存儲空間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及之后版本的分區(qū)存儲,您可以使用文件的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();
列舉已上傳分片
調(diào)用oss.listParts方法獲取某個(gè)上傳事件所有已上傳的分片。
以下代碼用于列舉已上傳分片。
// 填寫B(tài)ucket名稱,例如examplebucket。
String bucketName = "examplebucket";
// 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
String objectName = "exampledir/exampleobject.txt";
// 填寫uploadId。uploadId來源于調(diào)用InitiateMultipartUpload完成初始化分片之后,且在調(diào)用CompleteMultipartUpload完成分片上傳之前的返回結(jié)果。
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()));
}
默認(rèn)情況下,如果存儲空間中的分片上傳事件的數(shù)量大于1000,則OSS僅返回1000個(gè)Multipart Upload信息,且返回結(jié)果中IsTruncated的值為false,并返回NextPartNumberMarker作為下次讀取的起點(diǎn)。
取消分片上傳事件
調(diào)用oss.abortMultipartUpload方法來取消分片上傳事件。當(dāng)一個(gè)分片上傳事件被取消后,無法再使用該uploadId進(jìn)行任何操作,已上傳的分片數(shù)據(jù)會被刪除。
以下代碼用于取消分片上傳事件。
// 填寫B(tài)ucket名稱,例如examplebucket。
String bucketName = "examplebucket";
// 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
String objectName = "exampledir/exampleobject.txt";
// 填寫uploadId。uploadId來源于調(diào)用InitiateMultipartUpload完成初始化分片之后的返回結(jié)果。
String uploadId = "0004B999EF518A1FE585B0C9****";
// 取消分片上傳。
AbortMultipartUploadRequest abort = new AbortMultipartUploadRequest(bucketName, objectName, uploadId);
AbortMultipartUploadResult abortResult = oss.abortMultipartUpload(abort);
相關(guān)文檔
關(guān)于分片上傳的完整示例代碼,請參見GitHub示例。
分片上傳的完整實(shí)現(xiàn)涉及三個(gè)API接口,詳情如下:
關(guān)于初始化分片上傳事件的API接口說明,請參見InitiateMultipartUpload。
關(guān)于分片上傳Part的API接口說明,請參見UploadPart。
關(guān)于完成分片上傳的API接口說明,請參見CompleteMultipartUpload。
關(guān)于取消分片上傳事件的API接口說明,請參見AbortMultipartUpload。
關(guān)于列舉已上傳分片的API接口說明,請參見ListParts。
關(guān)于列舉所有執(zhí)行中的分片上傳事件(即已初始化但尚未完成或已取消的分片上傳事件)的API接口說明,請參見ListMultipartUploads。
關(guān)于初始化OSSClient,請參見如何初始化Android端OSSClient實(shí)例。