安卓端使用AmazonS3Client向S3上传文件弱网失败的解决方案是什么
弱网环境AWS S3多图上传失败解决方案
现有代码的核心问题
- 使用
putObject单段全量上传,没有断点续传能力,弱网下连接中断就需要重传整个文件,失败概率极高 - 无自动重试策略,临时网络波动直接触发上传失败
- 每次调用上传函数都新建
AmazonS3Client实例,重复进行TLS握手,增加额外网络开销,也浪费连接池资源 - 未针对弱网调整重试、超时、并发控制策略,20张图片同时上传时容易出现连接抢占、集体超时的情况
具体优化方案
- 改用官方提供的
TransferUtility组件实现上传:该组件原生支持分片上传、断点续传、自动重试、后台传输,是AWS移动端上传的推荐实现 - 全局复用
AmazonS3Client实例,避免重复初始化的额外开销 - 开启指数退避重试策略,配置合理的重试次数
- 控制并发上传数量,弱网下建议同时上传的图片不超过3张,避免连接资源抢占
- 上传前对图片进行压缩,降低单文件体积,缩短上传周期,减少失败概率
优化后代码示例
首先全局初始化S3客户端和TransferUtility,不要每次上传都重复创建实例,建议放在Application类中执行:
lateinit var transferUtility: TransferUtility fun initS3(context: Context, accessKey:String, secretKey:String, region:String) { val clientConfiguration = ClientConfiguration() clientConfiguration.connectionTimeout = 120000 // 弱网下适当延长超时时间 clientConfiguration.socketTimeout = 120000 clientConfiguration.maxConnections = 10 // 降低最大连接数,避免弱网下拥堵 // 配置指数退避重试策略 clientConfiguration.retryPolicy = RetryPolicy( PredefinedRetryPolicies.DEFAULT_RETRY_CONDITION, 5, // 最多重试5次 PredefinedRetryPolicies.DEFAULT_BACKOFF_STRATEGY, true ) val credentials = BasicAWSCredentials(accessKey, secretKey) val s3Client = AmazonS3Client(credentials, Region.getRegion(region), clientConfiguration) // 初始化TransferUtility transferUtility = TransferUtility.builder() .context(context) .s3Client(s3Client) .build() }
单文件上传函数实现:
// 单文件上传函数,返回可观察的上传结果 suspend fun uploadFile(file: File, bucketName: String, distributionUrl: String): String? { // 提前压缩图片,比如把分辨率限制在1920px,质量压缩到80%,大幅降低文件体积,需自行实现压缩逻辑 val compressedFile = compressImage(file) val observer = transferUtility.upload( PutObjectRequest(bucketName, compressedFile.name, compressedFile) .withCannedAcl(CannedAccessControlList.PublicRead) // 根据实际权限需求调整 ) // 等待上传完成 return suspendCancellableCoroutine { cont -> observer.setTransferListener(object : TransferListener { override fun onStateChanged(id: Int, state: TransferState) { if (state == TransferState.COMPLETED) { // 生成访问链接 val override = ResponseHeaderOverrides() override.contentType = "image/jpeg" val urlRequest = GeneratePresignedUrlRequest(bucketName, compressedFile.name) urlRequest.responseHeaders = override val url = transferUtility.s3Client.generatePresignedUrl(urlRequest) val finalUrl = url.toString().replace("https://${bucketName}.s3.amazonaws.com/", distributionUrl) cont.resume(finalUrl) } else if (state == TransferState.FAILED) { cont.resume(null) } } override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) { // 可在此处处理上传进度展示逻辑 } override fun onError(id: Int, ex: Exception) { cont.resumeWithException(ex) } }) } }
批量上传20张图片的并发控制逻辑:
// 批量上传,弱网下控制并发数为3,避免拥堵 suspend fun batchUploadFiles(files: List<File>, bucketName: String, distributionUrl: String): List<String?> { return files.chunked(3).flatMap { chunk -> // 同一批次3张同时上传,批次之间串行执行 chunk.map { file -> async { uploadFile(file, bucketName, distributionUrl) } }.awaitAll() } }
额外注意事项
- 需要在AndroidManifest.xml中注册
TransferService,组件才能正常工作 - 上传失败的文件可以记录下来,等网络恢复后自动续传,TransferUtility会自动读取未完成的分片,不需要重传整个文件
- 如果需要后台上传能力,可以配合前台Service使用,避免应用被系统杀死后上传中断
- 确保应用已申请
INTERNET、ACCESS_NETWORK_STATE等必要权限
内容的提问来源于stack exchange,提问作者Sanket Bhangale
相关产品推荐
相关产品推荐

