Kotlin实现带超时的多字体文件下载及默认字体降级方案
解决方案
问题出在你的download函数是阻塞式IO操作,协程的取消是协作式的,普通阻塞IO不会响应协程的取消信号,所以withTimeout无法中断正在执行的下载任务。下面是修正后的实现方案:
1. 改造下载函数为可取消的挂起函数
将阻塞IO包装在runInterruptible中,这样协程取消时能中断阻塞操作,同时切换到IO调度器执行:
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.net.URL suspend fun download(url: String, path: String) = withContext(Dispatchers.IO) { runInterruptible { URL(url).openStream().use { input -> FileOutputStream(File(path)).use { output -> input.copyTo(output) } } } }
2. 并行下载加超时处理
根据你的需求,提供两种超时策略:
策略一:整体超时(所有下载任务总时长超过5秒则全部放弃)
如果希望只要整个并行下载过程耗时超过5秒,就直接放弃所有下载,降级使用默认字体:
import kotlinx.coroutines.async import kotlinx.coroutines.withTimeout import kotlinx.coroutines.TimeoutCancellationException // 定义超时常量 private const val FONT_DOWNLOAD_TIMEOUT = 5000L // 5秒 suspend fun loadFonts(appFonts: List<AppFont>) { try { withTimeout(FONT_DOWNLOAD_TIMEOUT) { appFonts.map { font -> async { download(font.url, font.path) } }.awaitAll() // 所有字体下载成功,加载自定义字体 setupCustomFonts() } } catch (e: TimeoutCancellationException) { // 超时,切换到默认字体 setupDefaultFonts() } catch (e: Exception) { // 其他下载异常(如网络错误、文件写入失败),同样使用默认字体 setupDefaultFonts() } } // 自定义字体初始化逻辑 fun setupCustomFonts() { // ... 加载自定义字体的代码 } // 默认字体初始化逻辑 fun setupDefaultFonts() { // ... 加载默认字体的代码 }
策略二:单个任务超时(每个字体下载超时则单独降级,其他成功的保留)
如果希望单个字体下载超时/失败时仅该字体用默认,其他成功下载的继续使用:
import kotlinx.coroutines.async import kotlinx.coroutines.withTimeoutOrNull suspend fun loadFonts(appFonts: List<AppFont>) { val downloadResults = appFonts.map { font -> async { // 单个任务超时5秒,同时捕获其他异常 runCatching { withTimeoutOrNull(FONT_DOWNLOAD_TIMEOUT) { download(font.url, font.path) } }.isSuccess } }.awaitAll() // 遍历结果处理每个字体 appFonts.forEachIndexed { index, font -> if (downloadResults[index]) { // 下载成功,加载该自定义字体 loadCustomFont(font.path) } else { // 超时或失败,加载默认字体 loadDefaultFont() } } } fun loadCustomFont(path: String) { // ... 加载单个自定义字体的代码 } fun loadDefaultFont() { // ... 加载默认字体的代码 }
关键说明
runInterruptible的作用是将阻塞IO操作转化为可中断的挂起函数,当协程因超时被取消时,会中断底层线程,终止阻塞的IO操作,避免资源浪费。- 一定要处理除
TimeoutCancellationException外的其他异常(如网络错误、文件权限问题),确保异常情况下也能降级到默认字体。
内容的提问来源于stack exchange,提问作者Chris B
相关产品推荐
相关产品推荐

