AndroidPdfViewer的打印功能集成:通过PrintManager实现PDF打印

AndroidPdfViewer的打印功能集成:通过PrintManager实现PDF打印

【免费下载链接】AndroidPdfViewer Android view for displaying PDFs rendered with PdfiumAndroid 【免费下载链接】AndroidPdfViewer 项目地址: https://gitcode.com/gh_mirrors/an/AndroidPdfViewer

前言: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)引入的系统级服务,主要由以下核心组件构成:

mermaid

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 打印功能实现完整流程图

mermaid

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_STORAGEREAD_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 打印预览加载缓慢优化

优化打印预览加载速度的关键技术:

  1. 异步加载:在后台线程加载PDF内容,避免阻塞UI线程
  2. 缓存机制:缓存已渲染的页面,避免重复渲染
  3. 渐进式加载:先加载低分辨率预览,再逐步提升清晰度
// 异步加载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 集成步骤总结

  1. 添加打印菜单按钮和权限
  2. 创建自定义PrintDocumentAdapter
  3. 实现PrintManager调用逻辑
  4. 添加打印属性配置功能
  5. 实现进度监听和取消功能
  6. 优化大文件处理和内存使用

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 功能扩展建议

  1. 打印预览功能:实现自定义打印预览界面,支持缩放、旋转等操作
  2. 批量打印:支持多PDF文件批量打印功能
  3. 云打印集成:集成第三方云打印服务(如Google Cloud Print)
  4. 打印样式设置:允许用户设置页眉页脚、水印等打印样式

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高级功能定制 - 批注与签名功能实现。

【免费下载链接】AndroidPdfViewer Android view for displaying PDFs rendered with PdfiumAndroid 【免费下载链接】AndroidPdfViewer 项目地址: https://gitcode.com/gh_mirrors/an/AndroidPdfViewer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值