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
相关产品推荐
相关产品推荐

