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

Android 11+ Kotlin中PDF路径获取及渲染为Bitmap问题排查

在LazyColumn中通过Bitmap展示PDF页面的解决方案

问题根源

通过ActivityResultContracts.OpenDocument获取的Uri是内容Uri(content://),并非直接的本地文件路径。直接调用uri.toFile()或通过uri.path创建File对象必然失败——系统不会授予直接访问文件路径的权限,只能通过ContentResolver操作内容Uri对应的资源。


1. 依赖准备

在Module级别的build.gradle中添加必要依赖:

dependencies {
    // 其他已有依赖
    implementation "androidx.graphics.pdf:pdf-renderer:1.0.0"
    implementation "androidx.core:core-ktx:1.12.0"
}

2. 修改ViewModel的PDF转Bitmap逻辑

绕过直接转File的操作,通过ContentResolver获取输入流并写入临时文件,再用PdfRenderer处理:

import android.content.Context
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.net.Uri
import android.os.ParcelFileDescriptor
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream

class ViewModel_Main : ViewModel() {
    private val _pdfBitmaps = MutableStateFlow<List<Bitmap>>(emptyList())
    val pdfBitmaps: StateFlow<List<Bitmap>> = _pdfBitmaps

    suspend fun convertPdfToBitmaps(context: Context, pdfUri: Uri) = withContext(Dispatchers.IO) {
        val tempPdfFile = createTempFileFromUri(context, pdfUri) ?: return@withContext

        ParcelFileDescriptor.open(tempPdfFile, ParcelFileDescriptor.MODE_READ_ONLY).use { pfd ->
            PdfRenderer(pfd).use { renderer ->
                val bitmaps = mutableListOf<Bitmap>()
                for (pageIndex in 0 until renderer.pageCount) {
                    renderer.openPage(pageIndex).use { page ->
                        // 可根据屏幕尺寸缩放Bitmap优化内存
                        val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
                        page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                        bitmaps.add(bitmap)
                    }
                }
                _pdfBitmaps.value = bitmaps
            }
        }
        // 临时文件可在页面销毁时手动删除,避免缓存堆积
        // tempPdfFile.delete()
    }

    private fun createTempFileFromUri(context: Context, uri: Uri): File? {
        return try {
            context.contentResolver.openInputStream(uri)?.use { input ->
                val tempFile = File(context.cacheDir, "temp_pdf_${System.currentTimeMillis()}.pdf")
                FileOutputStream(tempFile).use { output ->
                    input.copyTo(output)
                }
                tempFile
            }
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }
}

3. MainActivity中对接文件选择与LazyColumn展示

import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.graphics.asImageBitmap
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.lifecycleScope
import com.example.yourpackage.ui.theme.YourAppTheme

class MainActivity : ComponentActivity() {
    private val openDocumentLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
        uri?.let {
            val viewModel = viewModel<ViewModel_Main>()
            lifecycleScope.launch {
                viewModel.convertPdfToBitmaps(this@MainActivity, it)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourAppTheme {
                val viewModel = viewModel<ViewModel_Main>()
                val pdfBitmaps = viewModel.pdfBitmaps.collectAsState()

                // 触发文件选择的按钮(按需添加)
                // Button(onClick = { openDocumentLauncher.launch(arrayOf("application/pdf")) }) {
                //     Text("选择PDF文件")
                // }

                LazyColumn {
                    items(pdfBitmaps.value) { bitmap ->
                        Image(
                            bitmap = bitmap.asImageBitmap(),
                            contentDescription = "PDF页面"
                        )
                    }
                }
            }
        }
    }
}

关键注意事项

  • 权限无需额外申请:ActivityResultContracts.OpenDocument通过系统文件选择器自动授予临时访问权限,无需添加READ_EXTERNAL_STORAGE权限
  • 内存优化:若PDF页数多、尺寸大,建议按屏幕分辨率缩放Bitmap,或采用分页加载策略避免OOM
  • 临时文件清理:临时文件存储在应用缓存目录,可在页面销毁时调用tempFile.delete()清理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:33:11