You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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)

    1. 接收打印属性(纸张尺寸、方向、分辨率等),初始化PrintedPdfDocument作为打印内容容器
    2. 根据打印属性计算文档总页数,生成PrintDocumentInfo告知打印框架文档的类型、页数等基础信息
    3. 通知打印框架布局完成或失败,确认打印任务的可行性
  • 内容写入阶段(OnWrite)

    1. 遍历打印框架指定的页码范围,筛选需要打印的页码
    2. 对每个目标页码,调用pdfDocument.StartPage(i)创建空白页,在该页上绘制自定义内容(文本、图像、PDF内容等)
    3. 完成单页渲染后调用pdfDocument.FinishPage(page)锁定该页内容
    4. 将完整的PrintedPdfDocument写入到指定输出流
    5. 通知打印框架内容写入完成或失败

开发者可在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 06:54:57