如何使用TransferUtility结合Content Uri上传文件至S3?
使用TransferUtility上传ACTION_GET_CONTENT返回的Content Uri到S3
我明白你已经用AmazonS3Client+PutObjectRequest搞定了上传,但想要切换到TransferUtility来享受便捷性和暂停续传功能——这确实是个好选择,毕竟TransferUtility封装了很多上传细节,断点续传也省心。
问题的核心是:TransferUtility的常规upload方法更倾向于本地文件路径,但ACTION_GET_CONTENT返回的content:// Uri并不是直接的文件系统路径,所以我们需要先把这个Uri转换成TransferUtility能处理的输入源,下面分两种可行方案来实现:
方案1:直接通过InputStream上传(轻量但断点续传受限)
如果你的文件不大,直接用InputStream上传是最快的方式,只需要从Content Uri获取流,再搭配ObjectMetadata来补充文件信息:
// 1. 从Content Uri获取InputStream和文件元数据 ContentResolver resolver = getContentResolver(); Uri contentUri = // 你从ACTION_GET_CONTENT拿到的Uri InputStream inputStream = null; ObjectMetadata metadata = new ObjectMetadata(); try { // 获取文件类型 String mimeType = resolver.getType(contentUri); metadata.setContentType(mimeType); // 获取文件大小(如果能拿到的话) Cursor cursor = resolver.query(contentUri, new String[]{OpenableColumns.SIZE}, null, null, null); if (cursor != null && cursor.moveToFirst()) { long fileSize = cursor.getLong(0); metadata.setContentLength(fileSize); cursor.close(); } // 打开输入流 inputStream = resolver.openInputStream(contentUri); // 2. 初始化TransferUtility TransferUtility transferUtility = TransferUtility.builder() .context(getApplicationContext()) .s3Client(yourAmazonS3Client) // 这里传入你已配置好的AmazonS3Client .build(); // 3. 开始上传,指定bucket名、S3上的key、输入流和元数据 TransferObserver observer = transferUtility.upload( "your-bucket-name", "s3-folder/your-file-name.jpg", // S3上的目标路径+文件名 inputStream, metadata ); // 4. 设置上传监听,处理状态变化 observer.setTransferListener(new TransferListener() { @Override public void onStateChanged(int id, TransferState state) { switch (state) { case COMPLETED: Log.d("S3Upload", "上传完成"); break; case FAILED: Log.e("S3Upload", "上传失败"); break; case PAUSED: Log.d("S3Upload", "上传暂停"); break; // 其他状态根据需要处理 } } @Override public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) { int progress = (int) ((bytesCurrent * 100) / bytesTotal); Log.d("S3Upload", "上传进度:" + progress + "%"); } @Override public void onError(int id, Exception ex) { Log.e("S3Upload", "上传出错:", ex); } }); // 暂停上传的调用(比如按钮点击事件里) // observer.pause(); // 继续上传的调用 // observer.resume(); } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
方案2:复制到应用内部临时文件再上传(推荐,断点续传更可靠)
如果文件较大,或者你想让断点续传更稳定,建议先把Content Uri的内容复制到应用内部存储的临时文件,再用TransferUtility上传这个文件——因为基于文件的上传,TransferUtility可以更好地记录上传进度,断点续传的可靠性更高:
// 1. 将Content Uri复制到应用内部临时文件 private File copyContentUriToTempFile(Uri contentUri) throws IOException { ContentResolver resolver = getContentResolver(); File tempFile = new File(getCacheDir(), "temp_upload_" + System.currentTimeMillis() + ".jpg"); try (InputStream inputStream = resolver.openInputStream(contentUri); OutputStream outputStream = new FileOutputStream(tempFile)) { byte[] buffer = new byte[1024 * 4]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } // 获取原文件名(可选,用来设置S3上的文件名) String originalFileName = null; Cursor cursor = resolver.query(contentUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); if (cursor != null && cursor.moveToFirst()) { originalFileName = cursor.getString(0); cursor.close(); } return tempFile; } // 2. 上传临时文件 public void uploadWithTempFile(Uri contentUri) { try { File tempFile = copyContentUriToTempFile(contentUri); String originalFileName = // 可从上面方法中同步获取 TransferUtility transferUtility = TransferUtility.builder() .context(getApplicationContext()) .s3Client(yourAmazonS3Client) .build(); TransferObserver observer = transferUtility.upload( "your-bucket-name", "s3-folder/" + originalFileName, tempFile ); // 设置监听和暂停/继续逻辑和方案1一致 observer.setTransferListener(new TransferListener() { @Override public void onStateChanged(int id, TransferState state) { if (state == TransferState.COMPLETED || state == TransferState.FAILED) { if (tempFile.exists()) { tempFile.delete(); // 上传完成/失败后清理临时文件 } } // 其他状态处理... } @Override public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) { int progress = (int) ((bytesCurrent * 100) / bytesTotal); Log.d("S3Upload", "上传进度:" + progress + "%"); } @Override public void onError(int id, Exception ex) { Log.e("S3Upload", "上传出错:", ex); } }); // 暂停/继续上传调用 // observer.pause(); // observer.resume(); } catch (IOException e) { e.printStackTrace(); } }
关键注意点
- 权限问题:Android 10及以上使用Scoped Storage时,
ACTION_GET_CONTENT返回的Uri不需要额外的外部存储权限;如果复制到应用内部缓存目录,也无需额外权限,推荐用这种方式。 - 断点续传:方案2基于文件的上传,TransferUtility会自动持久化上传进度,即使App重启,也可以通过
transferUtility.getTransferById(id)获取之前的TransferObserver来继续上传;方案1基于InputStream的上传,断点续传支持有限,因为流无法被持久化记录进度。 - 文件名处理:通过
OpenableColumns.DISPLAY_NAME可以获取原文件的名称,用来设置S3上的key,避免文件名混乱。
内容的提问来源于stack exchange,提问作者OBit
相关产品推荐
相关产品推荐

