《HarmonyOS技术精讲-Basic Services Kit》打印服务文档与图片打印开篇打印功能在HarmonyOS上怎么用HarmonyOS NEXT 开发里ohos.print这个 API 经常被误用。很多人第一次接触打印能力时会发现官方示例能运行但换成自己的图片或 HTML 文档要么打印出来空白要么预览界面根本弹不出来。打印这个功能本身不复杂——无非是把内容传给系统打印框架然后用户选打印机、调参数、点确认。但真正麻烦的是数据格式转换和生命周期管理。比如用PrintDocumentAdapter自定义打印内容时回调时机和页面销毁之间的冲突就很容易丢数据。这篇文章把打印图片和打印 HTML 文档的完整实现走一遍同时会重点说明那些官方文档里一笔带过、但实际项目里天天踩的坑。打印服务解决什么问题ohos.print模块提供了将文本、图片、PDF 等内容发送到物理打印机或虚拟打印机的统一接口。它解决的是“应用内内容如何变成纸面输出”的问题。适用场景场景说明图片打印相册、截图、证件照等文档打印收据、订单、报告PDF 打印合同、说明书自定义内容打印用 HTML 模板生成的定制内容不适用的场景需要直接操作打印机硬件参数如进纸器、切纸刀的场景Print Kit 不提供这种底层能力。大量批量打印几十上百页建议走后台任务而不是直接在 UI 线程触发。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机 / 平板核心实现1. 打印一张图片思路是把图片文件转为PixelMap然后通过systemPrint.PrintDocumentAdapter的onStartLayoutWritePage回调写入打印页面。代码实现importimagefromohos.multimedia.image;import{systemPrint}fromohos.print;import{BusinessError}fromohos.base;// 打印单张图片asyncfunctionprintImage(context:Context,imagePath:string):Promisevoid{// step1: 从文件读取图片constfileUriimagePath.startsWith(file://)?imagePath:file://imagePath;constimageSourceimage.createImageSource(fileUri);constpixelMapawaitimageSource.createPixelMap();// step2: 构造打印任务constprintJobsystemPrint.createPrintJob({taskName:PrintImage,adapter:newPrintImageAdapter(pixelMap)});// step3: 启动打印预览printJob.start((err:BusinessError){if(err){console.error(print failed,JSON.stringify(err));}});}// 自定义 PrintDocumentAdapterclassPrintImageAdapterextendssystemPrint.PrintDocumentAdapter{privatepixelMap:image.PixelMap;constructor(pixelMap:image.PixelMap){super();this.pixelMappixelMap;}onStartLayoutWritePage(params:any,callback:(result:any)void):void{// 写入一页图片constpage{index:0,width:params.pageSize.width,height:params.pageSize.height,content:this.pixelMap};callback({result:0,// 0 表示成功pages:[page]});}onFinish():void{// 清理资源this.pixelMapnull;}}关键点说明image.createImageSource只接收文件路径file://或沙箱路径不能直接传网络地址。网络图片需要先下载到本地。onStartLayoutWritePage回调里的params.pageSize是系统根据用户选择的纸张类型自动计算的不要硬编码。每页只能写一个content如果图片很大建议提前缩放否则内存容易涨上去。2. 打印一个 HTML 文档HTML 打印更直接系统内部会帮我们解析样式和布局不需要自己处理PixelMap。代码实现import{systemPrint}fromohos.print;import{BusinessError}fromohos.base;// 打印 HTML 字符串asyncfunctionprintHtmlDocument(context:Context):Promisevoid{consthtmlContenthtml body h1Hello HarmonyOS/h1 p这是一段测试打印的 HTML 内容。/p table border1 trtd项目/tdtd数量/td/tr trtd商品A/tdtd2/td/tr /table /body /html;constprintJobsystemPrint.createPrintJob({taskName:PrintHTML,adapter:newHtmlPrintAdapter(htmlContent)});printJob.start((err:BusinessError){if(err){console.error(print failed,JSON.stringify(err));}});}classHtmlPrintAdapterextendssystemPrint.PrintDocumentAdapter{privatehtml:string;constructor(html:string){super();this.htmlhtml;}onStartLayoutWritePage(params:any,callback:(result:any)void):void{// HTML 内容直接作为字符串传给 contentcallback({result:0,pages:[{index:0,width:params.pageSize.width,height:params.pageSize.height,content:this.html}]});}}注意事项HTML 中引用的 CSS 和图片需要是内联的不支持外部资源加载。也就是说link和img srchttp://...不会生效。表格布局、字体、颜色等 CSS 属性基本都支持但浮动和定位效果可能会有偏差建议打印前在真机上预览一下效果。如果 HTML 内容超过一页系统会自动分页不需要手动处理分页逻辑。3. 页面调用在页面上加一个按钮点击后触发打印EntryComponentstruct PrintDemo{privatecontext:ContextgetContext(this);build(){Column(){Button(打印图片).onClick((){// 沙箱路径示例实际使用时替换为真实图片路径constpaththis.context.filesDir/test.png;printImage(this.context,path);}).margin(20)Button(打印 HTML).onClick((){printHtmlDocument(this.context);})}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}踩坑记录坑1打印预览界面一闪而过现象调用printJob.start后打印预览界面闪一下就消失了没有进入正式打印流程。原因PrintDocumentAdapter的onStartLayoutWritePage回调中如果pages数组为空或content为 null系统会认为打印任务无效直接关闭预览。解决方案确保回调里pages数组至少有一个元素而且content不能为 null。如果content是PixelMap先检查pixelMap是否已释放。onStartLayoutWritePage(params:any,callback:(result:any)void):void{if(!this.pixelMap){console.error(pixelMap is null, print aborted);callback({result:-1,pages:[]});return;}// 正常处理}坑2打印 PDF 时页面方向不生效现象设置了orientation: landscape但 PDF 打印出来还是纵向。原因PrintDocumentAdapter的onStartLayoutWritePage参数里pageSize的宽高是由系统根据纸张类型和用户设置决定的在回调里手动改width和height会被忽略。解决方案在创建打印任务时使用PrintAttributesBuilder设置方向。constattributesnewsystemPrint.PrintAttributesBuilder().setOrientation(systemPrint.PrintOrientation.LANDSCAPE).build();constprintJobsystemPrint.createPrintJob({taskName:PrintPDF,adapter:adapter,attributes:attributes});最佳实践图片打印前先预读取宽高如果图片尺寸很大比如 4000x3000建议在onStartLayoutWritePage中等比缩放否则打印出的图像可能被截断。缩放方式用pixelMap.scale不要重新createPixelMap后者会额外占用内存。非必要不自定义 PrintDocumentAdapter如果只是打印本地图片或 PDF直接用systemPrint.print方法更简单它会自动处理适配器逻辑。只有需要动态生成打印内容如拼接多张图、叠加水印时才需要自定义。打印任务结束后及时清理资源onFinish回调里要释放PixelMap、关闭文件流。如果页面销毁时打印任务还没结束要把PrintJob的引用置为 null避免内存泄漏。常见问题FAQQ为什么真机可以正常打印模拟器点预览没反应A模拟器没有真实的打印服务预览界面可能无法正确弹出。建议在 API 23 以上版本的真机上测试。Q打印任务已经开始用户突然退出页面会怎样A页面销毁后PrintJob的引用会变为空打印预览界面会自动关闭但已提交的打印任务会继续由系统处理。如果需要取消可以在页面的aboutToDisappear中调用printJob.cancel()。Q打印 HTML 时中文显示为乱码AHTML 文档需要指定charset在head中加入meta charsetUTF-8。如果还有乱码检查 HTML 字符串的编码格式确保 UTF-8 编码。如果你也遇到类似问题可以重点检查回调中的content是否有效以及打印任务的生命周期管理。官方文档对PrintDocumentAdapter的行为描述得比较简略建议结合实际运行效果一起验证。不同设备上的打印行为可能存在差异建议真机测试。