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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:05:27