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

基于Jetpack Compose与PdfRenderer实现PDF查看器的优化咨询

Compose PDF查看器优化方案

现有问题根因

  1. 位图加载后不自动刷新:你通过remember存储的Bitmap是普通不可观察对象,协程中完成位图渲染后Compose无法感知内容变化,不会触发重组刷新Image组件,只有滚动触发Item重绑定才会读取到最新的Bitmap内容
  2. 未实现可见区懒加载:现有逻辑只要Item被预组合就会触发加载,不会判断是否进入可视区域

优化后实现代码

@Composable
fun PdfViewer(
    modifier: Modifier = Modifier,
    uri: Uri,
    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp)
) {
    val context = LocalContext.current
    val mutex = remember { Mutex() }
    val imageLoader = LocalContext.current.imageLoader

    // 初始化PdfRenderer,增加资源释放逻辑
    val renderer = remember(uri) {
        // 兼容Content Uri的写法:val input = context.contentResolver.openFileDescriptor(uri, "r")
        val input = ParcelFileDescriptor.open(uri.toFile(), ParcelFileDescriptor.MODE_READ_ONLY)
        PdfRenderer(input)
    }
    DisposableEffect(renderer) {
        onDispose {
            renderer.close()
        }
    }

    BoxWithConstraints(modifier = modifier.fillMaxWidth()) {
        val width = with(LocalDensity.current) { maxWidth.toPx() }.toInt()
        val height = (width * sqrt(2f)).toInt()

        LazyColumn(
            verticalArrangement = verticalArrangement
        ) {
            items(
                count = renderer.pageCount,
                key = { it }
            ) { index ->
                val cacheKey = MemoryCache.Key("$uri-$index")
                // 用State持有Bitmap,更新自动触发重组
                var bitmap by remember(uri, index) {
                    mutableStateOf<Bitmap?>(imageLoader.memoryCache[cacheKey] as? Bitmap)
                }

                // 只有Item进入可视区时才会执行加载逻辑
                LaunchedEffect(uri, index) {
                    if (bitmap != null) return@LaunchedEffect
                    val targetBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
                    mutex.withLock {
                        Timber.d("Loading $uri - page $index")
                        renderer.openPage(index).use {
                            it.render(targetBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                        }
                    }
                    // 存入内存缓存
                    imageLoader.memoryCache[cacheKey] = targetBitmap
                    // 更新状态触发重绘
                    bitmap = targetBitmap
                }

                val request = ImageRequest.Builder(context)
                    .size(width, height)
                    .memoryCacheKey(cacheKey)
                    .data(bitmap)
                    // 可自定义加载中占位符
                    .placeholder(Color.White)
                    .build()

                Image(
                    modifier = Modifier
                        .background(Color.White)
                        .aspectRatio(1f / sqrt(2f))
                        .fillMaxWidth(),
                    contentScale = ContentScale.Fit,
                    painter = rememberImagePainter(request),
                    contentDescription = "Page ${index + 1} of ${renderer.pageCount}"
                )
            }
        }
    }
}

核心优化点

  • 采用mutableStateOf持有Bitmap实例,渲染完成后更新状态自动触发UI重组,彻底解决加载完成不显示的问题
  • 用LaunchedEffect包裹加载逻辑,只有LazyColumn的Item进入可视区域时才会触发加载,Item滑出可视区时未完成的加载协程会自动取消,避免无用资源消耗
  • 新增DisposableEffect处理PdfRenderer的资源释放,避免内存泄漏
  • 支持扩展加载中占位符、加载失败重试等自定义UI逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 23:48:00