.NET MAUI与Android互操作:PDF打印问题及代码修复求助
.NET MAUI Blazor Hybrid Android PDF打印问题修复与解答
问题背景
在.NET MAUI Blazor Hybrid项目中,已通过Android Print Manager成功实现图片打印,但PDF打印功能存在异常,需解决代码转换错误、理解自定义打印适配器逻辑及官方示例代码疑问。
参考文档
- Android Print Manager打印图片官方文档
- Android自定义文档打印官方文档
实现代码
.NET接口定义
namespace PrintMAUI.Services { public interface IPrintCalller { Task<bool> printImage(byte[] bitmapBytes); Task<bool> printPDF(byte[] pdfBytes); } }
Android平台实现类
namespace PrintMAUI.Platforms { public class PrintCaller : MauiAppCompatActivity, IPrintCalller { public async Task<bool> printImage(byte[] bitmapBytes) { try { PrintHelper photoPrinter = new PrintHelper(Platform.CurrentActivity); photoPrinter.ScaleMode = PrintHelper.ScaleModeFill; Bitmap bitmap = await BitmapFactory.DecodeByteArrayAsync(bitmapBytes, 0, bitmapBytes.Length); photoPrinter.PrintBitmap("Testing Print for BL", bitmap); return true; }catch (Exception ex) { return false; } } public async Task<bool> printPDF(byte[] pdfBytes) { try { PrintManager printManager = Platform.CurrentActivity.GetSystemService(Android.Content.Context.PrintService).JavaCast<PrintManager>(); string jobName = "simple Print Job"; printManager.Print(jobName, new CustomPrintDocumentAdapter(pdfBytes), null); return true; }catch(Exception ex) { return false; } } } }
自定义打印适配器
using Android.Graphics.Pdf; using Android.OS; using Android.Print; using Android.Print.Pdf; using Android.Runtime; using Java.IO; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static Android.Print.PrintAttributes; namespace PrintMAUI.Platforms { public class CustomPrintDocumentAdapter : PrintDocumentAdapter { //private byte[] data; private int totalPages = 0; PrintedPdfDocument pdfDocument; public CustomPrintDocumentAdapter(byte[] data) { //this.data = data; } public override void OnLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { pdfDocument = new PrintedPdfDocument(Platform.CurrentActivity,newAttributes); if (cancellationSignal.IsCanceled) { callback.OnLayoutCancelled(); return; } int pages = computePageCount(newAttributes); if(pages > 0) { PrintDocumentInfo pdi = new PrintDocumentInfo.Builder("PDF.pdf") .SetContentType(Android.Print.PrintContentType.Document) .SetPageCount(pages) .Build(); callback.OnLayoutFinished(pdi, true); } else { callback.OnLayoutFailed("Calculations Failed!"); } } private bool containsPage(PageRange[] pages,int num) { //TODO: you have to do it return true; } public override void OnWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { for (int i = 0; i < totalPages; i++) { // Check to see if this page is in the output range. if (containsPage(pages, i)) { // If so, add it to writtenPagesArray. writtenPagesArray.size() // is used to compute the next output page index. //writtenPagesArray.append(writtenPagesArray.size(), i); PdfDocument.Page page = pdfDocument.StartPage(i); // check for cancellation if (cancellationSignal.IsCanceled) { callback.OnWriteCancelled(); pdfDocument.Close(); pdfDocument = null; return; } // Rendering is complete, so page can be finalized. pdfDocument.FinishPage(page); } } // Write PDF document to file try { //FileOutputStream stream = new FileOutputStream(destination.FileDescriptor); FileStream stream = new FileStream((string)destination.FileDescriptor, FileMode.Create, FileAccess.Write); pdfDocument.WriteTo(stream); } catch (Exception e) { callback.OnWriteFailed(e.ToString()); return; } finally { pdfDocument.Close(); pdfDocument = null; } //PageRange[] writtenPages = computeWrittenPages(); /// Signal the print framework the document is complete callback.OnWriteFinished(pages); } private int computePageCount(PrintAttributes printAttributes) { int itemsPerPage = 4; MediaSize pageSize = printAttributes.GetMediaSize(); if (!pageSize.IsPortrait) { itemsPerPage = 6; } int printItemCount = 1; return (int)Math.Ceiling((double)printItemCount/itemsPerPage); } } }
已尝试方案
- 单独创建含打印SDK的项目,因政策限制无法从MAUI调用
- 将打印SDK集成到MAUI项目,未成功
- 使用Android Print Manager实现打印,图片打印成功,但PDF打印存在问题
问题解答
1. 修复FileStream转换错误
原代码错误在于将Java的FileDescriptor强制转换为C#字符串,这是无效的类型转换。正确写法是通过Android提供的IO流API桥接:
try { // 使用ParcelFileDescriptor直接创建输出流 using (var stream = destination.CreateOutputStream()) { pdfDocument.WriteTo(stream); } } catch (Exception e) { callback.OnWriteFailed(e.ToString()); return; }
也可以通过Java的FileOutputStream间接处理:
try { using (var javaOutputStream = new Java.IO.FileOutputStream(destination.FileDescriptor)) using (var stream = javaOutputStream.BaseStream) { pdfDocument.WriteTo(stream); } } catch (Exception e) { callback.OnWriteFailed(e.ToString()); return; }
核心是避免跨平台类型的强制转换,利用Android原生IO流处理输出。
2. CustomPrintDocumentAdapter如何支持自定义文档打印
CustomPrintDocumentAdapter是Android打印框架的核心组件,通过两个核心阶段实现自定义打印:
布局阶段(OnLayout)
- 接收打印属性(纸张尺寸、方向、分辨率等),初始化
PrintedPdfDocument作为打印内容容器 - 根据打印属性计算文档总页数,生成
PrintDocumentInfo告知打印框架文档的类型、页数等基础信息 - 通知打印框架布局完成或失败,确认打印任务的可行性
- 接收打印属性(纸张尺寸、方向、分辨率等),初始化
内容写入阶段(OnWrite)
- 遍历打印框架指定的页码范围,筛选需要打印的页码
- 对每个目标页码,调用
pdfDocument.StartPage(i)创建空白页,在该页上绘制自定义内容(文本、图像、PDF内容等) - 完成单页渲染后调用
pdfDocument.FinishPage(page)锁定该页内容 - 将完整的
PrintedPdfDocument写入到指定输出流 - 通知打印框架内容写入完成或失败
开发者可在StartPage与FinishPage之间插入任意渲染逻辑,实现完全自定义的文档格式打印。
3. 官方示例中writtenPagesArray的来源
writtenPagesArray是官方示例中自行声明的IntArray对象,用于跟踪实际已处理的页码。完整逻辑示例如下:
public override void OnWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { // 声明IntArray存储已处理的页码 IntArray writtenPagesArray = new IntArray(); for (int i = 0; i < totalPages; i++) { if (containsPage(pages, i)) { // 将当前页码加入数组 writtenPagesArray.Append(i); // ...页面渲染逻辑 } } // 将IntArray转换为PageRange数组,通知打印框架实际打印范围 PageRange[] writtenPages = PageRange.CreateArray(writtenPagesArray.ToArray()); callback.OnWriteFinished(writtenPages); }
它的作用是记录哪些页码被实际渲染,最终传递给打印框架,确保框架准确知晓打印任务的完成范围。
内容的提问来源于stack exchange,提问作者geontricks developer
相关产品推荐
相关产品推荐

