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

如何修复Android Kotlin下载文件时的Runtime Error问题

问题分析与解决方案

核心错误排查

  1. 路径传递错误:你在simpleDownload中拼接了带文件名的完整路径paths,但调用download时却传入了原始的path(仅文件夹路径),导致FileOutputStream尝试写入文件夹而非文件,引发异常。
  2. 主线程网络操作:不使用线程时,下载逻辑运行在Android主线程,而Android从API 11开始禁止主线程执行网络请求,会抛出NetworkOnMainThreadException,表现为无额外信息的Runtime Error。
  3. 未处理异常:线程内的未捕获异常会直接导致应用崩溃,且下载过程中未处理网络、文件IO等可能的异常。

修正后的代码

1. 修正下载调用逻辑(含异常捕获)

@SimpleFunction(description = "Simple download block")
fun simpleDownload(url: String, path: String) {
    // 拼接带文件名的完整路径
    val fullPath = if (path.endsWith("/")) {
        "$path${getFileName(url)}"
    } else {
        "$path/${getFileName(url)}"
    }
    // 后台线程执行下载,捕获所有异常避免崩溃
    thread(start = true, isDaemon = true) {
        try {
            val bytesCopied = download(url, fullPath)
            // 触发下载成功事件(需提前定义)
            downloadSuccess(bytesCopied, fullPath)
        } catch (e: Exception) {
            // 触发下载失败事件(需提前定义)
            downloadFailed(e.message ?: "Unknown download error")
        }
    }
}

2. 优化下载函数(含异常处理与连接管理)

fun download(link: String, fullPath: String): Long {
    val url = URL(link)
    // 明确使用HttpURLConnection并配置超时
    val connection = url.openConnection() as HttpURLConnection
    try {
        connection.connectTimeout = 10000 // 10秒连接超时
        connection.readTimeout = 10000    // 10秒读取超时
        connection.connect()

        // 校验服务器响应码,确保请求成功
        if (connection.responseCode != HttpURLConnection.HTTP_OK) {
            throw IOException("Server error: ${connection.responseCode}")
        }

        val file = File(fullPath)
        // 确保文件父目录存在,避免创建文件失败
        file.parentFile?.mkdirs()

        // 自动关闭流的use语法,避免资源泄漏
        connection.inputStream.use { input ->
            FileOutputStream(file).use { output ->
                val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
                var bytesRead = input.read(buffer)
                var bytesCopied = 0L
                while (bytesRead >= 0) {
                    output.write(buffer, 0, bytesRead)
                    bytesCopied += bytesRead
                    bytesRead = input.read(buffer)
                }
                return bytesCopied
            }
        }
    } finally {
        connection.disconnect() // 强制关闭连接,释放资源
    }
}

3. 添加MIT App Inventor回调事件

用于向App Inventor传递下载结果:

@SimpleEvent(description = "Triggered when download succeeds")
fun downloadSuccess(bytesCopied: Long, filePath: String) {
    EventDispatcher.dispatchEvent(this, "DownloadSuccess", bytesCopied, filePath)
}

@SimpleEvent(description = "Triggered when download fails")
fun downloadFailed(errorMessage: String) {
    EventDispatcher.dispatchEvent(this, "DownloadFailed", errorMessage)
}

必要配置与注意事项

  • 权限声明:在AndroidManifest.xml中添加必要权限:
    <!-- 网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Android 12及以下存储权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- Android 13+媒体文件权限(按需添加) -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    
    针对Android 6.0+设备,还需通过MIT App Inventor的PermissionUtil动态申请存储权限。
  • 线程异常处理:必须在后台线程内用try-catch包裹所有逻辑,避免未捕获异常导致应用崩溃。
  • 路径合法性:确保传入的path是应用有权限访问的路径,比如外部存储的应用私有目录(getExternalFilesDir(null)),避免权限问题。

内容的提问来源于stack exchange,提问作者Tushar Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 15:10:24