如何修复Android Kotlin下载文件时的Runtime Error问题
问题分析与解决方案
核心错误排查
- 路径传递错误:你在
simpleDownload中拼接了带文件名的完整路径paths,但调用download时却传入了原始的path(仅文件夹路径),导致FileOutputStream尝试写入文件夹而非文件,引发异常。 - 主线程网络操作:不使用线程时,下载逻辑运行在Android主线程,而Android从API 11开始禁止主线程执行网络请求,会抛出
NetworkOnMainThreadException,表现为无额外信息的Runtime Error。 - 未处理异常:线程内的未捕获异常会直接导致应用崩溃,且下载过程中未处理网络、文件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中添加必要权限:
针对Android 6.0+设备,还需通过MIT App Inventor的<!-- 网络权限 --> <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" />PermissionUtil动态申请存储权限。 - 线程异常处理:必须在后台线程内用
try-catch包裹所有逻辑,避免未捕获异常导致应用崩溃。 - 路径合法性:确保传入的
path是应用有权限访问的路径,比如外部存储的应用私有目录(getExternalFilesDir(null)),避免权限问题。
内容的提问来源于stack exchange,提问作者Tushar Sharma
相关产品推荐
相关产品推荐

