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

Android如何通过带签名参数的URL下载文件并监听进度

问题描述

我是Android平台新手,正在开发一款从网络下载文件的应用。网上的示例均为下载无参数的URL(如http/https:sample_address/file_to_download.xyz),但我的应用需要下载带AWS签名参数的URL(如http/https:sample_address/file_to_download.xyz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20221025%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221025T093017Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3D%2220_2022-24-10.apk%22&X-Amz-Signature=4968bbf4093b5d1fa6f0368ac1cfa445480bc301b132e740a7cf38fe81d9d2b5)。我使用的下载代码无法正常工作,在获取inputStream时抛出java.io.FileNotFoundException异常。我不想使用DownloadManager,因为需要展示下载进度。

错误原因
  1. URL参数处理错误:你把URL中的查询参数(?后的部分)拆出来当成HTTP请求头设置,但AWS签名参数是URL的核心组成部分,必须作为查询参数传递,不能放到请求头里。这导致实际请求的是不带签名参数的原始URL,AWS服务器验证签名失败后返回404错误,HttpURLConnection会将4xx/5xx响应转换为FileNotFoundException抛出。
  2. 请求配置错误:GET请求不需要设置urlConnection.doOutput = true,该参数仅用于POST/PUT等需要向服务器写入数据的请求,开启后会自动将请求方法转为POST,与你设置的requestMethod = "GET"冲突。
修复方案
  1. 直接使用完整的带参数URL创建连接,无需拆分参数。
  2. 移除urlConnection.doOutput = true配置。
  3. 添加响应码检查,非200响应时读取错误流排查问题。
  4. 确保传入的apkPath是正确URL编码后的字符串(你的示例URL已编码,无需额外处理)。
修正后的代码
private fun downloadUpdate(apkPath: String, versionCode: Int) =
    serviceScope.launch {
        try {
            val apkFileName = "${versionCode}.apk"
            val appDataRoot = File(AppConstants.EXTERNAL_STORAGE_PATCH)
            val file = File(appDataRoot, apkFileName)
            GLog.d(UpdateSoftwareViewModel.TAG, "AppUpdate apkFileName =${apkFileName}")
            GLog.d(UpdateSoftwareViewModel.TAG, "AppUpdate apkFileName exist =${file.exists()}")
            var updatedApkExist = file.exists()

            if (!updatedApkExist) {
                // 直接使用完整带参数URL创建连接
                val url = URL(apkPath)
                val urlConnection = url.openConnection() as HttpURLConnection

                urlConnection.requestMethod = "GET"
                // 移除无效的doOutput配置
                // urlConnection.doOutput = true

                urlConnection.connect()

                // 检查响应码,处理非成功响应
                val responseCode = urlConnection.responseCode
                if (responseCode != HttpURLConnection.HTTP_OK) {
                    val errorStream = urlConnection.errorStream
                    val errorMsg = errorStream.bufferedReader().use { it.readText() }
                    GLog.e(UpdateSoftwareViewModel.TAG, "Download failed, code: $responseCode, error: $errorMsg")
                    throw IOException("Download failed with code $responseCode")
                }

                val fileOutput = FileOutputStream(file)
                val inputStream = urlConnection.inputStream
                // 改用Long避免大文件整数溢出
                val totalSize: Long = urlConnection.contentLength.toLong()
                var downloadedSize = 0L
                // 增大缓冲区提升下载效率
                val buffer = ByteArray(4096)
                var bufferLength: Int

                while (inputStream.read(buffer).also { bufferLength = it } > 0 && isActive) {
                    fileOutput.write(buffer, 0, bufferLength)
                    downloadedSize += bufferLength

                    // 计算正确的进度百分比(避免整数除法导致的0值)
                    val progress = if (totalSize > 0) (downloadedSize * 100 / totalSize).toInt() else 0
                    val message = Message()
                    message.what = AppConstants.DOWN_LOAD_PROGRESS
                    message.arg1 = progress
                    downloadHandlerList.forEach {
                        it.sendMessage(message)
                    }
                }

                // 关闭所有流资源
                fileOutput.close()
                inputStream.close()
                updatedApkExist = downloadedSize == totalSize
            }

            if (updatedApkExist) {
                GLog.d(VerifyUserViewModel.TAG, "AppUpdate do install")
                installApplication(this@UpdateSoftwareService, file.absolutePath)
            }
            stopSelf()
        } catch (e: MalformedURLException) {
            e.printStackTrace()
            GLog.d(UpdateSoftwareViewModel.TAG, "MalformedURLException: ${e.message}")
        } catch (e: IOException) {
            e.printStackTrace()
            GLog.d(UpdateSoftwareViewModel.TAG, "IOException: ${e.message}")
        }
    }
额外优化点
  • 缓冲区从1024字节改为4096字节,提升大文件下载效率。
  • 用Long类型存储文件大小和已下载字节数,避免超过2GB文件的整数溢出问题。
  • 修复进度计算逻辑,原来的downloadedSize / totalSize会因整数除法始终返回0,改为百分比计算后能正确展示进度。

内容的提问来源于stack exchange,提问作者Zappy.Mans

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:15:46