You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:55:04