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

Jetpack Compose中PDF Renderer加载PDF提示文件无效的问题求助

问题分析与解决方案

你遇到的IOException提示文件无效,核心原因是临时文件在Composable重组时被重新创建,导致渲染时读取的是空文件,同时代码存在未校验HTTP响应、内存占用过高的问题,以下是具体修正方案:

关键问题点

  1. 临时文件未持久化:Composable会因状态变化多次重组,直接在顶层创建的File实例每次都会生成新文件,下载写入的是旧文件,渲染时读取的却是新的空文件。
  2. 未校验HTTP响应:未检查请求是否成功(如404/500错误),此时响应体可能是错误页面的HTML而非PDF。
  3. 内存型下载方式:body.bytes()将整个PDF加载到内存,大文件易引发OOM,还可能导致文件写入不完整。

修正后的代码

@Composable
actual fun openFile(url: String): Boolean {
    var display by remember { mutableStateOf(false) }
    var error by remember { mutableStateOf<String?>(null) }

    // 用remember保存临时文件,避免重组时重复创建
    val pdfFile by remember {
        mutableStateOf(
            File.createTempFile("temp_pdf", ".pdf").apply {
                deleteOnExit()
            }
        )
    }

    LaunchedEffect(url) { // 以url为触发key,不同url重新下载
        error = null
        try {
            withContext(Dispatchers.IO) {
                val client = OkHttpClient()
                val request = Request.Builder().url(url).build()
                val response = client.newCall(request).execute()

                // 校验HTTP响应状态
                if (!response.isSuccessful) {
                    throw IOException("请求失败,状态码: ${response.code}")
                }

                val body = response.body ?: throw IOException("响应体为空")

                // 流式写入文件,降低内存占用
                body.source().use { source ->
                    pdfFile.outputStream().use { output ->
                        source.readAll(output)
                    }
                }
            }
            display = true
        } catch (e: Exception) {
            error = e.message ?: "下载或写入文件失败"
            e.printStackTrace()
        }
    }

    // 显示错误信息
    error?.let {
        Text(text = "错误: $it", color = Color.Red)
        return false
    }

    if (display) {
        val pdfRenderer = runCatching {
            val parcelFileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
            PdfRenderer(parcelFileDescriptor)
        }.getOrElse {
            Text(text = "无法渲染PDF: ${it.message}", color = Color.Red)
            return false
        }

        val pages = remember { mutableListOf<ImageBitmap>() }
        // 后台线程处理PDF页面渲染,避免阻塞主线程
        LaunchedEffect(pdfRenderer) {
            withContext(Dispatchers.IO) {
                for (i in 0 until pdfRenderer.pageCount) {
                    pdfRenderer.openPage(i).use { page -> // use自动关闭页面
                        val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
                        page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                        pages.add(bitmap.asImageBitmap())
                    }
                }
                pdfRenderer.close()
            }
        }

        LazyColumn {
            items(pages) { image ->
                Image(
                    bitmap = image,
                    contentDescription = "PDF页面 ${pages.indexOf(image) + 1}",
                    modifier = Modifier.fillMaxWidth()
                )
            }
        }

        return true
    } else {
        CircularProgressIndicator(color = Color.White)
        return true
    }
}

修正说明

  • 持久化临时文件:用remember包裹File实例,确保整个Composable生命周期内使用同一个文件,避免重组时丢失已下载内容。
  • HTTP响应校验:增加状态码检查,确保只有请求成功时才处理响应体。
  • 流式写入:用source.readAll(output)替代内存型下载,降低内存占用,保证文件写入完整。
  • 错误处理:新增错误状态,显示具体错误信息,便于排查问题。
  • 后台渲染:将PDF页面渲染逻辑放到LaunchedEffect的IO线程中,避免阻塞UI主线程。
  • 资源自动管理:用use函数自动关闭PdfRenderer.Page等资源,避免资源泄漏。

内容的提问来源于stack exchange,提问作者MYJ World

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 06:17:07