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

AWS S3文件下载优化及AmazonS3Client、Retrofit技术问题咨询

问题背景

我正在开发一个使用com.amazonaws:aws-android-sdk-s3依赖从服务器下载文件的项目,当前下载实现代码如下:

fun downloadFile(context: Context, bucketName: String, fileName: String, file: File, transferListener: TransferListener) {
    val transferUtility =
        TransferUtility.builder().s3Client(AmazonS3Client(AnonymousAWSCredentials()))
            .context(context.applicationContext).build()!!
    val observer =
        transferUtility.download(bucketName, fileName, file)
    observer.setTransferListener(transferListener)
}
核心需求

应用需定期获取新文件(本文以JSON文件为例,支持任意文件类型),目前通过Firebase-RemoteConfig触发检查,若检测到新值则执行下载,该实现方式繁琐冗余,希望弃用RemoteConfig来管理文件更新。

已尝试方案

我了解到Retrofit可通过Etag或Last-Modified等机制判断文件是否更新,但未找到AWS S3的对应实现方案;同时发现当前使用的AmazonS3Client构造方法已过时,官方提示需使用AmazonS3Client#AmazonS3Client(AWSCredentials, com.amazonaws.regions.Region),但不清楚参数配置方式。

后续尝试改用Retrofit实现下载,代码如下:

interface FileDownloadService {
    @GET
    @Streaming
     fun downloadFile(@Url fileUrl:String): Call<ResponseBody>
}

@WorkerThread
fun test(context: App, url: String) {
    Log.d("AppLog", "test")
    val cacheSizeBytes = 1024L * 1024L * 2L
    val builder = OkHttpClient().newBuilder()
    builder.cache(Cache(context.cacheDir, cacheSizeBytes))
    val client: OkHttpClient = builder.build()
    val retrofitBuilder = Retrofit.Builder()
    retrofitBuilder.baseUrl("https://s3.amazonaws.com").client(client)
    val retrofit = retrofitBuilder.build()
    val service: FileDownloadService = retrofit.create(FileDownloadService::class.java)
    val response = service.downloadFile(url).execute()
    Log.d("AppLog", "isSuccessful?${response.isSuccessful} ${response.code()} ${response.raw().code} ${response.headers()["Last-Modified"]}")
}

但该方案存在以下问题:

  • 模拟器上始终返回响应码200(预期为HttpURLConnection.HTTP_NOT_MODIFIED),且耗时较长(约4秒),仅能正常获取Last-Modified头部信息;
  • 真机上出现UnknownHostException: Unable to resolve host "s3.amazonaws.com": No address associated with hostname异常;
  • 应用已在其他场景使用Retrofit,担心新实例的配置、线程等与现有实例冲突。
咨询问题
  1. 如何检测AWS S3中的文件是否变更,从而避免重复下载?
  2. 已过时的AmazonS3Client构造方法应如何替换?
  3. 是否可以按上述方式使用Retrofit下载AWS S3文件?若可行,当前实现存在哪些问题?

解决方案

1. 检测AWS S3文件变更的两种方案

方案一:使用S3 SDK的headObject方法

通过headObject获取文件元数据(Etag、Last-Modified),与本地存储的历史值对比,判断是否需要下载:

fun checkFileUpdated(s3Client: AmazonS3, bucketName: String, fileName: String, localLastModified: Long?): Boolean {
    val headObjectRequest = HeadObjectRequest(bucketName, fileName)
    val metadata = s3Client.headObject(headObjectRequest)
    val remoteLastModified = metadata.lastModified.time
    return localLastModified == null || remoteLastModified > localLastModified
}

使用逻辑:

  • 首次下载后,将Last-Modified时间戳或Etag存入SharedPreferences/本地文件;
  • 定期调用上述方法对比,返回true时再执行下载。

方案二:添加条件请求头

不管用TransferUtility还是Retrofit,都可以在请求中携带If-Modified-Since或If-None-Match头:

  • If-Modified-Since:仅文件在指定时间后修改才返回内容,否则返回304;
  • If-None-Match:仅文件Etag与指定值不同才返回内容,否则返回304。

以TransferUtility为例配置请求:

val request = GetObjectRequest(bucketName, fileName)
localLastModified?.let {
    request.setModifiedSinceDate(Date(it))
}
val observer = transferUtility.download(request, file)

2. 替换过时的AmazonS3Client构造方法

官方推荐使用AmazonS3ClientBuilder构建客户端,替代过时构造方法:

// 匿名访问场景
val s3Client = AmazonS3ClientBuilder.standard()
    .withCredentials(AnonymousAWSCredentialsProvider())
    .withRegion(Regions.US_EAST_1) // 替换为你的Bucket所在区域,例如Regions.CN_NORTH_1
    .build()

// 构建TransferUtility
val transferUtility = TransferUtility.builder()
    .s3Client(s3Client)
    .context(context.applicationContext)
    .build()

注意:必须指定Bucket所在的正确区域,否则会出现连接错误或性能问题。

3. Retrofit下载S3文件的可行性及问题修复

可以用Retrofit下载S3文件,当前实现的问题及修复方法如下:

问题1:模拟器始终返回200而非304

原因是未添加条件请求头,手动给接口添加If-Modified-Since参数即可:

interface FileDownloadService {
    @GET
    @Streaming
    fun downloadFile(
        @Url fileUrl: String,
        @Header("If-Modified-Since") lastModified: String? // 传入本地存储的Last-Modified字符串
    ): Call<ResponseBody>
}

调用时传入之前保存的Last-Modified值,文件未修改时服务器会返回304。

问题2:真机出现UnknownHostException

  • 检查应用是否添加网络权限:在AndroidManifest.xml中添加<uses-permission android:name="android.permission.INTERNET"/>;
  • 确认Bucket的Endpoint是否正确:部分AWS区域的S3域名不是s3.amazonaws.com,比如中国区为s3.cn-north-1.amazonaws.com.cn,需替换baseUrl为对应区域的Endpoint;
  • 排查真机网络是否正常,能否访问目标域名。

问题3:Retrofit实例冲突

复用项目中已有的OkHttpClient实例,避免创建新的Retrofit实例:

// 假设项目已有全局的okHttpClient实例
val retrofit = Retrofit.Builder()
    .baseUrl("https://s3.amazonaws.com") // 或对应区域的Endpoint
    .client(GlobalOkHttpClient.getInstance())
    .build()

若需要特殊配置,可基于现有OkHttpClient构建新实例:

val newClient = GlobalOkHttpClient.getInstance().newBuilder()
    .cache(Cache(context.cacheDir, 2 * 1024 * 1024))
    .build()

内容的提问来源于stack exchange,提问作者android developer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:17:51