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

如何在Jetpack Compose中用PdfRenderer高效渲染PDF并在LazyColumn展示

性能问题核心原因
  • 所有PDF页面一次性全量渲染:下载成功后直接循环遍历所有页面生成Bitmap,页数较多时会占用巨量内存,且整个渲染过程默认在主线程执行,直接导致页面长时间冻结
  • 逻辑无重组保护:下载、渲染逻辑直接写在Composable根作用域,每次组件重组都会触发重复下载、重复渲染,额外消耗大量系统资源
  • 无Bitmap复用与缓存机制:大量Bitmap频繁创建销毁会触发频繁GC,是滚动卡顿的核心诱因
  • 固定尺寸渲染不灵活:如果预设的Constants宽高远大于实际显示尺寸,会造成不必要的内存浪费
优化方案

基础优化(原生实现)

  1. 用LaunchedEffect包裹下载、渲染逻辑,绑定obraId、autorId作为唯一key,避免逻辑重复执行
  2. 放弃全量预渲染,改为懒加载:只在LazyColumn对应项即将进入可视区域时,才触发对应PDF页的渲染,渲染逻辑放到后台协程执行,不阻塞主线程
  3. 接入LruCache实现Bitmap复用,限制内存占用上限,避免频繁GC
  4. 按实际Image组件的显示尺寸渲染Bitmap,不要使用固定的全局宽高参数,减少单张Bitmap的内存占用

推荐Compose适配库

优先使用Coil图片加载库,3.0及以上版本内置支持PDF页面解码,自动处理后台渲染、内存缓存、懒加载逻辑,无需自己封装PdfRenderer相关逻辑,改造成本极低。

优化后代码示例

Coil实现方案

@Composable
fun PDFScreen(obraId: ObraId, autorId: AuthorId) {
    val storageRef = FirebaseStorage.getInstance().reference
    val pathReference = storageRef.child("obras/${autorId}/${obraId}.pdf")
    // 记录本地PDF文件路径,下载成功后再渲染
    var localPdfFile by remember { mutableStateOf<File?>(null) }
    // 总页数,读取PDF后赋值
    var pageCount by remember { mutableIntStateOf(0) }

    // 仅当obraId、autorId变化时才触发下载
    LaunchedEffect(obraId, autorId) {
        val tempFile = File.createTempFile("obra", "pdf")
        pathReference.getFile(tempFile).await() // 用协程await替代回调,后台执行
        if (tempFile.exists()) {
            localPdfFile = tempFile
            // 读取PDF总页数
            val input = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_ONLY)
            val renderer = PdfRenderer(input)
            pageCount = renderer.pageCount
            renderer.close()
            input.close()
        }
    }

    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(pageCount) { pageIndex ->
            localPdfFile?.let { file ->
                AsyncImage(
                    model = ImageRequest.Builder(LocalContext.current)
                        .data(file)
                        .decoderFactory(PdfDecoder.Factory(page = pageIndex))
                        .size(Size.ORIGINAL) // 可按需设置尺寸降低内存占用
                        .build(),
                    contentDescription = "PDF页${pageIndex+1}",
                    modifier = Modifier.fillMaxWidth()
                )
            } ?: run {
                // 自定义下载中占位UI
            }
        }
    }
}

原生自定义实现核心逻辑

如果需要自己实现PdfRenderer逻辑,核心懒加载渲染逻辑如下:

// 单个PDF页项的Composable
@Composable
fun PdfPageItem(file: File, pageIndex: Int, modifier: Modifier = Modifier) {
    var bitmap by remember { mutableStateOf<ImageBitmap?>(null) }
    // 仅当项进入组合时才触发渲染,后台执行
    LaunchedEffect(pageIndex, file) {
        withContext(Dispatchers.IO) {
            val input = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
            val renderer = PdfRenderer(input)
            val page = renderer.openPage(pageIndex)
            // 按需计算实际需要的Bitmap尺寸,不要硬编码
            val targetBitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
            page.render(targetBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
            page.close()
            renderer.close()
            input.close()
            bitmap = targetBitmap.asImageBitmap()
        }
    }

    bitmap?.let {
        Image(bitmap = it, contentDescription = null, modifier = modifier)
    } ?: // 自定义占位UI
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 10:45:03