OSS 移动端SDK 上传文件的方式可以分为:简单上传,追加上传,分片上传和断点续传。

简单上传本地文件

  • 调用同步接口上传:
    // 构造上传请求
    PutObjectRequest put = new PutObjectRequest("<bucketName>", "<objectKey>", "<uploadFilePath>");
    
    // 文件元信息的设置是可选的
    // ObjectMetadata metadata = new ObjectMetadata();
    // metadata.setContentType("application/octet-stream"); // 设置content-type
    // metadata.setContentMD5(BinaryUtil.calculateBase64Md5(uploadFilePath)); // 校验MD5
    // put.setMetadata(metadata);
    
    try {
    
    	PutObjectResult putResult = oss.putObject(put);
    
    	Log.d("PutObject", "UploadSuccess");
    
    	Log.d("ETag", putResult.getETag());
    	Log.d("RequestId", putResult.getRequestId());
    } catch (ClientException e) {
    	// 本地异常如网络异常等
    	e.printStackTrace();
    } catch (ServiceException e) {
    	// 服务异常
    	Log.e("RequestId", e.getRequestId());
    	Log.e("ErrorCode", e.getErrorCode());
    	Log.e("HostId", e.getHostId());
    	Log.e("RawMessage", e.getRawMessage());
    }
    
    注意 在Android中,不能在UI线程调用同步接口,只能在子线程调用,否则将出现异常。如果希望直接在UI线程中上传,请使用异步接口。
  • 调用异步接口上传:
    // 构造上传请求
    PutObjectRequest put = new PutObjectRequest("<bucketName>", "<objectKey>", "<uploadFilePath>");
    
    // 异步上传时可以设置进度回调
    put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
    	@Override
    	public void onProgress(PutObjectRequest request, long currentSize, long totalSize) {
    		Log.d("PutObject", "currentSize: " + currentSize + " totalSize: " + totalSize);
    	}
    });
    
    OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
    	@Override
    	public void onSuccess(PutObjectRequest request, PutObjectResult result) {
    		Log.d("PutObject", "UploadSuccess");
    
    		Log.d("ETag", result.getETag());
    		Log.d("RequestId", result.getRequestId());
    	}
    
    	@Override
    	public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
    		// 请求异常
    		if (clientExcepion != null) {
    			// 本地异常如网络异常等
    			clientExcepion.printStackTrace();
    		}
    		if (serviceException != null) {
    			// 服务异常
    			Log.e("ErrorCode", serviceException.getErrorCode());
    			Log.e("RequestId", serviceException.getRequestId());
    			Log.e("HostId", serviceException.getHostId());
    			Log.e("RawMessage", serviceException.getRawMessage());
    		}
    	}
    });
    
    // task.cancel(); // 可以取消任务
    // task.waitUntilFinished(); // 可以等待任务完成
    

简单上传二进制byte[]数组

byte[] uploadData = new byte[100 * 1024];
new Random().nextBytes(uploadData);

// 构造上传请求
PutObjectRequest put = new PutObjectRequest(testBucket, testObject, uploadData);

try {
	PutObjectResult putResult = oss.putObject(put);

	Log.d("PutObject", "UploadSuccess");

	Log.d("ETag", putResult.getETag());
	Log.d("RequestId", putResult.getRequestId());
} catch (ClientException e) {
	// 本地异常如网络异常等
	e.printStackTrace();
} catch (ServiceException e) {
	// 服务异常
	Log.e("RequestId", e.getRequestId());
	Log.e("ErrorCode", e.getErrorCode());
	Log.e("HostId", e.getHostId());
	Log.e("RawMessage", e.getRawMessage());
}
  • 上传到文件目录

    OSS服务是没有文件夹这个概念的,所有元素都是以文件来存储,但给用户提供了创建模拟文件夹的方式。创建模拟文件夹本质上来说是创建了一个名字以“/”结尾的文件,这个文件可以上传下载,只是控制台会对以“/”结尾的文件以文件夹的方式展示。

    在上传文件时,如果把ObjectKey写为"folder/subfolder/file",即是模拟了把文件上传到folder/subfolder/下的file文件。

    说明 OSS路径默认是"根目录",不需要以'/'开头。
  • 上传Content-Type设置

    在Web服务中Content-Type用来设定文件的类型,决定以什么形式、什么编码读取这个文件。某些情况下,对于上传的文件需要设定Content-Type,否则文件不能以自己需要的形式和编码来读取。使用SDK上传文件时,如果不指定Content-Type,SDK会帮您根据后缀自动添加Content-Type。

    // 构造上传请求
    PutObjectRequest put = new PutObjectRequest("<bucketName>", "<objectKey>", "<uploadFilePath>");
    
    ObjectMetadata metadata = new ObjectMetadata();
    // 指定Content-Type
    metadata.setContentType("application/octet-stream");
    // user自定义metadata
    metadata.addUserMetadata("x-oss-meta-name1", "value1");
    put.setMetadata(metadata);
    
    OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
    	...
    });
    
  • Append Object 追加上传

    Append Object以追加写的方式上传文件。通过Append Object操作创建的Object类型为Appendable Object,而通过Put Object上传的Object是Normal Object。

    AppendObjectRequest append = new AppendObjectRequest(testBucket, testObject, uploadFilePath);
    
    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentType("application/octet-stream");
    append.setMetadata(metadata);
    
    // 设置追加位置
    append.setPosition(0);
    
    append.setProgressCallback(new OSSProgressCallback<AppendObjectRequest>() {
    	@Override
    	public void onProgress(AppendObjectRequest request, long currentSize, long totalSize) {
    		Log.d("AppendObject", "currentSize: " + currentSize + " totalSize: " + totalSize);
    	}
    });
    
    OSSAsyncTask task = oss.asyncAppendObject(append, new OSSCompletedCallback<AppendObjectRequest, AppendObjectResult>() {
    	@Override
    	public void onSuccess(AppendObjectRequest request, AppendObjectResult result) {
    		Log.d("AppendObject", "AppendSuccess");
    		Log.d("NextPosition", "" + result.getNextPosition());
    	}
    
    	@Override
    	public void onFailure(AppendObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
    		// 异常处理
    	}
    });
    

    用户使用Append方式上传,关键要对Position这个参数进行正确的设置:

    • 当用户创建一个Appendable Object时,追加位置设为0。
    • 当对Appendable Object进行内容追加时,追加位置设为Object当前长度。有两种方式获取该Object长度:
      • 通过上传追加后的返回内容获取。
      • 通过head object获取文件长度。
  • 上传后回调通知

    客户端在上传Object时可以指定OSS服务端在处理完上传请求后,通知您的业务服务器,在该服务器确认接收了该回调后将回调的结果返回给客户端。因为加入了回调请求和响应的过程,相比简单上传,使用回调通知机制一般会导致客户端花费更多的等待时间。

    具体说明参考:Callback

    代码示例:

    PutObjectRequest put = new PutObjectRequest(testBucket, testObject, uploadFilePath);
    
    put.setCallbackParam(new HashMap<String, String>() {
    	{
    		put("callbackUrl", "110.75.82.106/callback");
            put("callbackHost", "oss-cn-hangzhou.aliyuncs.com");
            put("callbackBodyType", "application/json");
    		put("callbackBody", "{\"mimeType\":${mimeType},\"size\":${size}}");
    	}
    });
    
    OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
    	@Override
    	public void onSuccess(PutObjectRequest request, PutObjectResult result) {
    		Log.d("PutObject", "UploadSuccess");
    
    		// 只有设置了servercallback,这个值才有数据
    		String serverCallbackReturnJson = result.getServerCallbackReturnBody();
    
    		Log.d("servercallback", serverCallbackReturnJson);
    	}
    
    	@Override
    	public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
    		// 异常处理
    	}
    });
    

    如果需要支持自定义参数,参考如下设置:

    put.setCallbackParam(new HashMap<String, String>() {
        {
            put("callbackUrl", "http://182.92.192.125/leibin/notify.php");
            put("callbackHost", "oss-cn-hangzhou.aliyuncs.com");
            put("callbackBodyType", "application/json");
            put("callbackBody", "{\"object\":${object},\"size\":${size},\"my_var1\":${x:var1},\"my_var2\":${x:var2}}");
        }
    });
    
    put.setCallbackVars(new HashMap<String, String>() {
        {
            put("x:var1", "value1");
            put("x:var2", "value2");
        }
    });
    

分片上传

因为篇幅的原因,分片上传参考:分片上传

断点续传

注意
  • 断点续传暂时只支持上传本地文件。

  • 对于移动端来说,如果不是比较大的文件,不建议使用这种方式上传,因为断点续传是通过分片上传实现的,上传单个文件需要进行多次网络请求,效率不高。**

在无线网络下,上传比较大的文件持续时间长,可能会遇到因为网络条件差、用户切换网络等原因导致上传中途失败,整个文件需要重新上传。为此,SDK提供了断点上传功能。

在上传前,可以指定断点记录的保存文件夹。若不进行此项设置,断点上传只在本次上传生效,某个分片因为网络原因等上传失败时会进行重试,避免整个大文件重新上传,节省重试时间和耗用流量。如果设置了断点记录的保存文件夹,如果任务失败,在下次重新启动任务,上传同一文件到同一Bucket、Object时,如果用户设置取消时不删除断点记录。再次上传将从断点记录处继续上传。详见随后的范例。

断点续传失败时,如果同一任务一直得不到续传,可能会在OSS上积累无用碎片。对这种情况,可以为Bucket设置lifeCycle规则,定时清理碎片。参考:生命周期管理

说明
  • 断点续传的实现依赖InitMultipartUpload/UploadPart/ListParts/CompleteMultipartUpload/AbortMultipartUpload,如果采用STS鉴权模式,请注意加上这些API所需的权限。
  • 断点续传也支持上传后回调通知,用法和上述普通上传回调通知一致。
  • 断点续传已经默认开启每个分片上传时的Md5校验,请勿重复在request中设置Content-Md5头部。
  • 不在本地持久保存断点记录的调用方式:
    // 创建断点上传请求
    ResumableUploadRequest request = new ResumableUploadRequest("<bucketName>", "<objectKey>", "<uploadFilePath>");
    // 设置上传过程回调
    request.setProgressCallback(new OSSProgressCallback<ResumableUploadRequest>() {
    	@Override
    	public void onProgress(ResumableUploadRequest request, long currentSize, long totalSize) {
    		Log.d("resumableUpload", "currentSize: " + currentSize + " totalSize: " + totalSize);
    	}
    });
    // 异步调用断点上传
    OSSAsyncTask resumableTask = oss.asyncResumableUpload(request, new OSSCompletedCallback<ResumableUploadRequest, ResumableUploadResult>() {
    	@Override
    	public void onSuccess(ResumableUploadRequest request, ResumableUploadResult result) {
    		Log.d("resumableUpload", "success!");
    	}
    
    	@Override
    	public void onFailure(ResumableUploadRequest request, ClientException clientExcepion, ServiceException serviceException) {
    		// 异常处理
    	}
    });
    
    // resumableTask.waitUntilFinished(); // 可以等待直到任务完成
    
  • 在本地持久保存断点记录的调用方式:
    String recordDirectory = Environment.getExternalStorageDirectory().getAbsolutePath() + "/oss_record/";
    
    File recordDir = new File(recordDirectory);
    
    // 要保证目录存在,如果不存在则主动创建
    if (!recordDir.exists()) {
    	recordDir.mkdirs();
    }
    
    // 创建断点上传请求,参数中给出断点记录文件的保存位置,需是一个文件夹的绝对路径
    ResumableUploadRequest request = new ResumableUploadRequest("<bucketName>", "<objectKey>", "<uploadFilePath>", recordDirectory);
    // 设置上传过程回调
    request.setProgressCallback(new OSSProgressCallback<ResumableUploadRequest>() {
    	@Override
    	public void onProgress(ResumableUploadRequest request, long currentSize, long totalSize) {
    		Log.d("resumableUpload", "currentSize: " + currentSize + " totalSize: " + totalSize);
    	}
    });
    
    
    OSSAsyncTask resumableTask = oss.asyncResumableUpload(request, new OSSCompletedCallback<ResumableUploadRequest, ResumableUploadResult>() {
    	@Override
    	public void onSuccess(ResumableUploadRequest request, ResumableUploadResult result) {
    		Log.d("resumableUpload", "success!");
    	}
    
    	@Override
    	public void onFailure(ResumableUploadRequest request, ClientException clientExcepion, ServiceException serviceException) {
    		// 异常处理
    	}
    });
    
    // resumableTask.waitUntilFinished();
    
  • 断点续传功能实现:
    //调用OSSAsyncTask cancel()方法时是否需要删除断点记录文件的设置
    String recordDirectory = Environment.getExternalStorageDirectory().getAbsolutePath() + "/oss_record/";
    
    File recordDir = new File(recordDirectory);
    
    // 要保证目录存在,如果不存在则主动创建
    if (!recordDir.exists()) {
    	recordDir.mkdirs();
    }
    
    // 创建断点上传请求,参数中给出断点记录文件的保存位置,需是一个文件夹的绝对路径
    ResumableUploadRequest request = new ResumableUploadRequest("<bucketName>", "<objectKey>", "<uploadFilePath>", recordDirectory);
    //设置false,取消时,不删除断点记录文件,如果不进行设置,默认true,是会删除断点记录文件,下次再进行上传时会重新上传。
    request.setDeleteUploadOnCancelling(false);
    // 设置上传过程回调
    request.setProgressCallback(new OSSProgressCallback<ResumableUploadRequest>() {
    	@Override
    	public void onProgress(ResumableUploadRequest request, long currentSize, long totalSize) {
    		Log.d("resumableUpload", "currentSize: " + currentSize + " totalSize: " + totalSize);
    	}
    });
    
    
    OSSAsyncTask resumableTask = oss.asyncResumableUpload(request, new OSSCompletedCallback<ResumableUploadRequest, ResumableUploadResult>() {
    	@Override
    	public void onSuccess(ResumableUploadRequest request, ResumableUploadResult result) {
    		Log.d("resumableUpload", "success!");
    	}
    
    	@Override
    	public void onFailure(ResumableUploadRequest request, ClientException clientExcepion, ServiceException serviceException) {
    		// 异常处理
    	}
    });
    
    // resumableTask.waitUntilFinished();
    

数据完整性校验

因为移动端网络环境的复杂性,OSS SDK提供了基于MD5和CRC64的端到端的数据完整性验证功能。

  • MD5校验

    需要在上传文件时提供文件的Content-MD5值,OSS服务器会帮助用户进行MD5校验,只有在OSS服务器计算接收到的文件得到的MD5值和上传提供的MD5一致时才可以上传成功,从而保证上传数据的一致性。

    // 构造上传请求
    PutObjectRequest put = new PutObjectRequest("<bucketName>", "<objectKey>", "<uploadFilePath>");
    
    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentType("application/octet-stream");
    try {
    	// 设置Md5以便校验
    	metadata.setContentMD5(BinaryUtil.calculateBase64Md5("<uploadFilePath>")); // 如果是从文件上传
    	// metadata.setContentMD5(BinaryUtil.calculateBase64Md5(byte[])); // 如果是上传二进制数据
    } catch (IOException e) {
    	e.printStackTrace();
    }
    put.setMetadata(metadata);
    
    OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
    	...
    });
    
  • CRC校验

    与MD5相比,CRC64可以边上传边计算CRC值。

    // 构造上传请求
    PutObjectRequest put = new PutObjectRequest("<bucketName>", "<objectKey>", "<uploadFilePath>");
    // 开启crc效验后。如果在传输中数据不一致,会直接抛出ClientException异常。提示InconsistentException: inconsistent object
    put.setCRC64(OSSRequest.CRC64Config.YES);
    
    OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
         @Override
         public void onSuccess(PutObjectRequest request, PutObjectResult result) {
    	 //.....
         }
    
    	@Override
        public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
    	//.....
        	if (clientExcepion != null) {
            	// client side exception,  such as network exception
    			// 检查是否有InconsistentException: inconsistent object信息
                clientExcepion.getMessage();
             }
          }
    });