如何在Android Fragment中展示base64编码的PDF并实现保存打印功能
Android Base64格式PDF在Fragment内实现预览+保存/打印功能方案
效果预览

核心实现思路
采用Android原生PdfRenderer组件实现PDF渲染,无需提前将PDF写入公共存储即可完成预览,完全规避Android 10+分区存储权限限制,以及Android 7.0+的Uri暴露报错。预览逻辑和保存/打印逻辑完全解耦,依赖应用专属缓存目录完成临时存储,全程无需申请外部存储权限。
具体实现步骤
1. 布局配置(fragment_pdf_preview.xml)
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- PDF分页渲染容器 --> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/vp_pdf_pages" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- 功能触发按钮 --> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab_option" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="24dp" android:src="@drawable/ic_menu_more" /> </FrameLayout>
2. PDF预览逻辑实现
预览时仅将解码后的PDF字节写入应用专属缓存目录,该目录读写无需任何权限:
// 解码Base64字符串为PDF字节数组 val pdfByteArray = Base64.decode(rawBase64PdfStr, Base64.DEFAULT) // 写入应用专属缓存目录临时文件 val tempCacheFile = File(requireContext().cacheDir, "preview_temp.pdf").apply { outputStream().use { output -> output.write(pdfByteArray) } } // 加载PDF到PdfRenderer val fileDescriptor = ParcelFileDescriptor.open(tempCacheFile, ParcelFileDescriptor.MODE_READ_ONLY) val pdfRenderer = PdfRenderer(fileDescriptor) // 为ViewPager2配置适配器,逐页渲染PdfRenderer返回的Bitmap即可完成预览
3. 保存/打印功能实现
点击FloatingActionButton点击后弹出操作菜单,分别对应两个功能:
- 保存功能:调用系统存储访问框架(SAF)触发用户自主选择保存路径,无需申请存储权限:
// 触发保存逻辑 val saveIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/pdf" putExtra(Intent.EXTRA_TITLE, "exported_file.pdf") } startActivityForResult(saveIntent, REQUEST_SAVE_PDF) // onActivityResult回调中处理保存逻辑 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_SAVE_PDF && resultCode == Activity.RESULT_OK) { data?.data?.let { saveUri -> requireContext().contentResolver.openOutputStream(saveUri)?.use { output -> output.write(pdfByteArray) } } } }
- 打印功能:调用系统原生打印框架,无需将文件写入本地存储:
val printManager = requireContext().getSystemService(Context.PRINT_SERVICE) as PrintManager val printAdapter = object : PrintDocumentAdapter() { override fun onLayout( oldAttributes: PrintAttributes?, newAttributes: PrintAttributes?, cancellationSignal: CancellationSignal?, callback: LayoutResultCallback?, extras: Bundle? ) { if (cancellationSignal?.isCanceled == true) { callback?.onLayoutCancelled() return } val info = PrintDocumentInfo.Builder("print_doc.pdf") .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) .setPageCount(pdfRenderer.pageCount) .build() callback?.onLayoutFinished(info, true) } override fun onWrite( pages: Array<out PageRange>?, destination: ParcelFileDescriptor?, cancellationSignal: CancellationSignal?, callback: WriteResultCallback? ) { FileOutputStream(destination?.fileDescriptor).use { output -> output.write(pdfByteArray) } callback?.onWriteFinished(arrayOf(PageRange.ALL_PAGES)) } } printManager.print("PDF打印任务", printAdapter, PrintAttributes.Builder().build())
原有报错说明
之前遇到的exposed beyond app through Intent.getData()报错是Android 7.0+的系统限制,禁止应用直接对外暴露file://类型的Uri,和存储权限无关。原有方案需要调用第三方应用打开PDF才会触发该报错,本方案采用原生渲染PDF,完全规避该问题。WRITE_EXTERNAL_STORAGE权限在Android 10+分区存储机制下确实已失效,仅当应用适配兼容模式且目标Sdk版本低于29时才会生效,本方案不需要申请该权限。
内容的提问来源于stack exchange,提问作者Mehdi Satei
相关产品推荐
相关产品推荐

