OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个分片(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。
分片上传流程
分片上传(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,它是分片上传事件的唯一标识。您可以根据该uploadId发起相关操作,例如取消分片上传、查询分片上传等。
String uploadId = initResult.getUploadId();
// 设置单个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。
- 调用同步接口分片上传本地文件
以下代码用于以同步方式分片上传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。
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()));
}
取消分片上传事件
调用oss.abortMultipartUpload方法来取消分片上传事件。当一个分片上传事件被取消后,无法再使用该uploadId进行任何操作,已上传的分片数据会被删除。
以下代码用于取消分片上传事件。
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
String objectName = "exampledir/exampleobject.txt";
// 填写uploadId。
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。