AndroidPdfViewer的打印功能集成:通过PrintManager实现PDF打印
前言:PDF打印的痛点与解决方案
在Android应用开发中,实现PDF文档的打印功能常常面临诸多挑战:系统API碎片化、打印预览加载缓慢、大文件内存溢出等问题。本文将详细介绍如何基于AndroidPdfViewer和系统PrintManager实现高效、稳定的PDF打印功能,帮助开发者快速解决PDF打印难题。
读完本文后,你将能够:
- 理解Android打印框架(Print Framework)的工作原理
- 掌握使用PrintManager实现PDF打印的完整流程
- 解决打印过程中的常见问题(如大文件处理、打印预览优化)
- 为AndroidPdfViewer添加自定义打印功能
一、Android打印框架核心组件解析
1.1 打印框架架构概览
Android打印框架(Print Framework)是Android 4.4(API 19)引入的系统级服务,主要由以下核心组件构成:
1.2 核心组件功能说明
| 组件名称 | 主要功能 | 关键方法 |
|---|---|---|
| PrintManager | 系统打印服务管理类,负责创建打印任务和管理打印服务 | print()、getPrintServices() |
| PrintDocumentAdapter | 打印内容适配器,处理打印文档的布局和写入 | onLayout()、onWrite()、onFinish() |
| PrintAttributes | 打印属性配置类,包含纸张大小、分辨率、颜色模式等 | Builder()、setMediaSize()、setResolution() |
| PrintDocumentInfo | 打印文档信息类,描述文档名称、类型、页数和大小 | Builder()、setPageCount()、setContentType() |
二、AndroidPdfViewer打印功能集成准备
2.1 项目环境配置
在开始集成打印功能前,请确保你的项目满足以下环境要求:
- 最低Android版本:API 19(Android 4.4)
- AndroidPdfViewer版本:2.8.2+
- Gradle版本:3.0+
2.2 权限配置
在AndroidManifest.xml中添加以下必要权限:
<!-- 读取PDF文件所需权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 打印功能所需权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2.3 依赖添加
确保在build.gradle中添加了AndroidPdfViewer依赖:
dependencies {
implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1'
}
三、基于PrintManager的打印实现方案
3.1 打印功能实现完整流程图
3.2 创建自定义PrintDocumentAdapter
创建PdfPrintDocumentAdapter类,继承自PrintDocumentAdapter,实现PDF文档的打印适配:
import android.content.Context;
import android.graphics.pdf.PdfDocument;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.util.Log;
import com.github.barteksc.pdfviewer.PDFView;
import com.github.barteksc.pdfviewer.scroll.DefaultScrollHandle;
import com.github.barteksc.pdfviewer.util.FitPolicy;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class PdfPrintDocumentAdapter extends PrintDocumentAdapter {
private static final String TAG = "PdfPrintDocumentAdapter";
private final Context context;
private final String pdfPath;
private final PDFView pdfView;
private PdfDocument pdfDocument;
private int pageCount;
public PdfPrintDocumentAdapter(Context context, String pdfPath, PDFView pdfView) {
this.context = context;
this.pdfPath = pdfPath;
this.pdfView = pdfView;
}
@Override
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
CancellationSignal cancellationSignal, LayoutResultCallback callback,
Bundle extras) {
if (cancellationSignal.isCanceled()) {
callback.onLayoutCancelled();
return;
}
// 初始化PDF文档
loadPdfForPrinting();
// 创建打印文档信息
PrintDocumentInfo info = new PrintDocumentInfo.Builder("document.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(pageCount)
.build();
callback.onLayoutFinished(info, true);
}
@Override
public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback) {
try (OutputStream outputStream = new FileOutputStream(destination.getFileDescriptor())) {
// 将PDF内容写入打印输出流
writePdfToOutputStream(outputStream, pages);
callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
} catch (IOException e) {
Log.e(TAG, "Error writing PDF to output stream: " + e.getMessage());
callback.onWriteFailed(e.getMessage());
}
}
private void loadPdfForPrinting() {
// 加载PDF文件,准备打印
pdfView.fromFile(new File(pdfPath))
.defaultPage(0)
.enableAnnotationRendering(true)
.pageFitPolicy(FitPolicy.BOTH)
.load();
// 获取总页数
pageCount = pdfView.getPageCount();
}
private void writePdfToOutputStream(OutputStream outputStream, PageRange[] pages) throws IOException {
// 实现PDF内容写入逻辑
// ...
}
@Override
public void onFinish() {
super.onFinish();
if (pdfDocument != null) {
pdfDocument.close();
}
}
}
3.3 实现打印管理器调用
在PDFViewActivity中添加打印功能,通过PrintManager调用系统打印服务:
@OptionsItem(R.id.print)
void printDocument() {
if (pdfFileName == null || pdfFileName.isEmpty()) {
Toast.makeText(this, "没有可打印的文档", Toast.LENGTH_SHORT).show();
return;
}
// 检查是否有读取文件权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_CODE);
return;
}
// 获取PrintManager实例
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
// 创建打印文档适配器
String jobName = getString(R.string.app_name) + " Document Print";
PrintDocumentAdapter printAdapter = new PdfPrintDocumentAdapter(this, getPdfPath(), pdfView);
// 执行打印
printManager.print(jobName, printAdapter, null);
}
private String getPdfPath() {
// 获取当前PDF文件路径
if (uri != null) {
return uri.getPath();
} else {
// 处理asset中的文件
File file = new File(getFilesDir(), SAMPLE_FILE);
if (!file.exists()) {
copyAssetToFile(SAMPLE_FILE, file);
}
return file.getAbsolutePath();
}
}
private void copyAssetToFile(String assetFileName, File outputFile) {
try (InputStream inputStream = getAssets().open(assetFileName);
OutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
} catch (IOException e) {
Log.e(TAG, "Error copying asset file: " + e.getMessage());
}
}
3.4 添加打印菜单选项
在res/menu/options.xml中添加打印菜单按钮:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/pickFile"
android:title="@string/menu_pick_file"
app:showAsAction="ifRoom" />
<item
android:id="@+id/print"
android:title="@string/menu_print"
app:showAsAction="ifRoom" />
</menu>
四、高级功能实现与优化
4.1 大文件打印优化方案
处理大型PDF文件时,直接加载整个文件可能导致内存溢出。实现分页加载和打印优化:
private void writePdfToOutputStream(OutputStream outputStream, PageRange[] pages) throws IOException {
// 获取打印页面范围
Set<Integer> pagesToPrint = new HashSet<>();
for (PageRange pageRange : pages) {
for (int i = pageRange.getStart(); i <= pageRange.getEnd(); i++) {
pagesToPrint.add(i);
}
}
// 分页写入PDF内容
for (int page : pagesToPrint) {
if (page >= pageCount) continue;
// 渲染单页内容
Bitmap bitmap = renderPageToBitmap(page);
// 将单页Bitmap写入PDF文档
addBitmapToPdfDocument(bitmap, page);
}
// 将最终PDF文档写入输出流
pdfDocument.writeTo(outputStream);
}
private Bitmap renderPageToBitmap(int page) {
// 获取页面尺寸
int width = pdfView.getOptimalPageWidth(page);
int height = pdfView.getOptimalPageHeight(page);
// 创建Bitmap
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// 渲染页面内容到Bitmap
pdfView.drawPage(canvas, page, width, height);
return bitmap;
}
4.2 打印进度监听与取消功能
实现打印进度监听和取消功能,提升用户体验:
private CancellationSignal cancellationSignal;
@OptionsItem(R.id.print)
void printDocument() {
// ...
// 创建取消信号
cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(() -> {
runOnUiThread(() -> {
Toast.makeText(this, "打印已取消", Toast.LENGTH_SHORT).show();
});
});
// 执行打印
printManager.print(jobName, printAdapter, null);
}
// 在Activity的onDestroy方法中取消打印任务
@Override
protected void onDestroy() {
super.onDestroy();
if (cancellationSignal != null && !cancellationSignal.isCanceled()) {
cancellationSignal.cancel();
}
}
4.3 自定义打印属性设置
允许用户自定义打印属性(如纸张大小、方向、颜色模式):
private PrintAttributes getCustomPrintAttributes() {
// 创建自定义打印属性
PrintAttributes attributes = new PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
.setResolution(new PrintAttributes.Resolution("pdf", "PDF", 300, 300))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build();
return attributes;
}
// 使用自定义属性执行打印
printManager.print(jobName, printAdapter, getCustomPrintAttributes());
五、常见问题解决方案
5.1 打印乱码或内容缺失问题
| 问题原因 | 解决方案 |
|---|---|
| PDF渲染不完整 | 确保使用最新版本的AndroidPdfViewer,实现onRender监听确认渲染完成 |
| 字体缺失 | 在application标签中添加android:allowBackup="true" |
| 权限不足 | 检查并申请WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限 |
5.2 内存溢出问题处理
大文件打印时容易出现内存溢出,可采用以下解决方案:
// 优化Bitmap内存使用
private Bitmap renderPageToBitmap(int page) {
// ...
// 使用RGB_565代替ARGB_8888,减少内存占用
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
// ...
}
// 实现Bitmap复用
private Bitmap reusableBitmap;
private Bitmap renderPageToBitmap(int page) {
// ...
// 检查是否可以复用已有Bitmap
if (reusableBitmap != null &&
reusableBitmap.getWidth() == width &&
reusableBitmap.getHeight() == height) {
// 复用Bitmap
return reusableBitmap;
}
// 创建新Bitmap
reusableBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
return reusableBitmap;
}
5.3 打印预览加载缓慢优化
优化打印预览加载速度的关键技术:
- 异步加载:在后台线程加载PDF内容,避免阻塞UI线程
- 缓存机制:缓存已渲染的页面,避免重复渲染
- 渐进式加载:先加载低分辨率预览,再逐步提升清晰度
// 异步加载PDF文件
private void loadPdfAsync(String path) {
new AsyncTask<String, Integer, Void>() {
@Override
protected Void doInBackground(String... params) {
// 后台加载PDF
pdfView.fromFile(new File(params[0]))
.defaultPage(0)
.enableAnnotationRendering(true)
.pageFitPolicy(FitPolicy.BOTH)
.load();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// 加载完成,更新UI
Toast.makeText(PDFViewActivity.this, "PDF加载完成,可进行打印", Toast.LENGTH_SHORT).show();
}
}.execute(path);
}
六、完整集成代码与使用指南
6.1 集成步骤总结
- 添加打印菜单按钮和权限
- 创建自定义
PrintDocumentAdapter - 实现
PrintManager调用逻辑 - 添加打印属性配置功能
- 实现进度监听和取消功能
- 优化大文件处理和内存使用
6.2 完整代码示例
以下是在PDFViewActivity中集成打印功能的完整代码:
// 添加打印相关权限
private static final int PRINT_PERMISSION_REQUEST_CODE = 1001;
@OptionsItem(R.id.print)
void printDocument() {
if (pdfFileName == null || pdfFileName.isEmpty()) {
Toast.makeText(this, "没有可打印的文档", Toast.LENGTH_SHORT).show();
return;
}
// 检查打印权限
checkPrintPermission();
}
private void checkPrintPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PRINT_PERMISSION_REQUEST_CODE);
return;
}
}
startPrintProcess();
}
private void startPrintProcess() {
// 获取PrintManager实例
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
// 创建打印任务名称
String jobName = getString(R.string.app_name) + " - " + pdfFileName;
// 创建自定义打印适配器
String pdfPath = getPdfPath();
PdfPrintDocumentAdapter printAdapter = new PdfPrintDocumentAdapter(this, pdfPath, pdfView);
// 执行打印
printManager.print(jobName, printAdapter, getCustomPrintAttributes());
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PRINT_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startPrintProcess();
} else {
Toast.makeText(this, "打印功能需要存储权限", Toast.LENGTH_SHORT).show();
}
}
}
七、总结与扩展
7.1 功能回顾
本文详细介绍了如何基于AndroidPdfViewer和PrintManager实现PDF打印功能,包括:
- Android打印框架核心组件的工作原理
- 自定义PrintDocumentAdapter的实现方法
- 大文件打印优化和内存管理
- 常见问题的解决方案
通过这些步骤,你可以为应用添加稳定、高效的PDF打印功能,提升用户体验。
7.2 功能扩展建议
- 打印预览功能:实现自定义打印预览界面,支持缩放、旋转等操作
- 批量打印:支持多PDF文件批量打印功能
- 云打印集成:集成第三方云打印服务(如Google Cloud Print)
- 打印样式设置:允许用户设置页眉页脚、水印等打印样式
7.3 版本兼容性处理
为了确保在不同Android版本上的兼容性,建议添加以下版本适配代码:
private void startPrintProcess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 4.4及以上版本使用PrintManager
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
// ...
} else {
// 低于4.4版本,使用第三方打印库或提示用户升级系统
Toast.makeText(this, "打印功能需要Android 4.4或更高版本", Toast.LENGTH_SHORT).show();
}
}
希望本文能够帮助你顺利实现AndroidPdfViewer的打印功能。如果有任何问题或建议,欢迎在评论区留言讨论!
点赞+收藏+关注,获取更多Android PDF相关开发技巧!下期预告:AndroidPdfViewer高级功能定制 - 批注与签名功能实现。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



