鸿蒙 HarmonyOS 6 | PDFKit预览能力升级实战
PDF 预览在办公、教育、阅读类应用里一直是高频需求。鸿蒙这条能力线本身分成两层,一层是 `pdfService`,负责文档加载、编辑、保存这类底层操作;另一层是 `PdfView`,负责预览、搜索、批注这类交互。到了 API 20,这条链路里更值得关注的点,集中在 `PdfController` 的加载回调、页数监听,以及 ArkWeb 里针对 PDF 预览补进来的两个事件上。 `PdfView
前言
PDF 预览在办公、教育、阅读类应用里一直是高频需求。鸿蒙这条能力线本身分成两层,一层是 pdfService,负责文档加载、编辑、保存这类底层操作;另一层是 PdfView,负责预览、搜索、批注这类交互。到了 API 20,这条链路里更值得关注的点,集中在 PdfController 的加载回调、页数监听,以及 ArkWeb 里针对 PDF 预览补进来的两个事件上。 PdfView 从 5.0.0(12) 开始支持,registerPageCountChangedListener 是 6.0.0(20) Beta3 新增能力,ArkWeb 的 onPdfLoadEvent 和 onPdfScrollAtBottom 也在 6.0.0(20) Beta3 进入支持范围。
这次升级最实用的地方,不在功能堆得更多,在于预览链路终于有了更清晰的状态信号。文档什么时候开始进入可用状态,总页数什么时候拿到,Web 预览里的 PDF 有没有加载成功,用户是不是已经滚到最底部,这些都可以挂到明确回调上去做。

一、梳理 PdfView
PdfView 适合承接本地 PDF 预览、页码跳转、页面适配、滚动预览、关键字搜索和批注这类能力。项目里如果主要目标是做应用内 PDF 阅读器,优先走 PdfView 会更顺。页面这边通常先准备一个 PdfController,再把它交给 PdfView。文档预览和搜索、批注这些能力,也都是围绕 PdfController 展开的。
更好的接法,是在页面显示前先把文件准备好,再开始加载。loadDocument 的签名支持 path、可选密码、初始页码和进度回调,返回值是 Promise<ParseResult>。也就是说,文档能不能打开、是不是密码错误、是不是解析失败,最终都能通过 ParseResult 收敛。
下面这段代码适合作为本地 PDF 预览的最小可用骨架:
import { pdfService, pdfViewManager } from '@kit.PDFKit'
import { fileIo } from '@kit.CoreFileKit'
import type common from '@ohos.app.ability.common'
import hilog from '@ohos.hilog'
@Entry
@Component
struct PdfPreviewPage {
private controller: pdfViewManager.PdfController = new pdfViewManager.PdfController()
@State pageCount: number = 0
@State isLoading: boolean = true
async aboutToAppear(): Promise<void> {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext
const filePath = `${context.filesDir}/input.pdf`
// 首次进入时,把 rawfile 里的 PDF 拷到应用沙箱
if (!fileIo.accessSync(filePath)) {
const content: Uint8Array = context.resourceManager.getRawFileContentSync('rawfile/input.pdf')
const file = fileIo.openSync(filePath, fileIo.OpenMode.WRITE_ONLY |
fileIo.OpenMode.CREATE | fileIo.OpenMode.TRUNC)
fileIo.writeSync(file.fd, content.buffer)
fileIo.closeSync(file.fd)
}
// API 20 新增,文档加载前就可以注册页数变化监听
this.controller.registerPageCountChangedListener((count: number) => {
this.pageCount = count
hilog.info(0x0000, 'PdfPreview', `PDF总页数: ${count}`)
})
const result = await this.controller.loadDocument(filePath, '', 0, (progress: number) => {
hilog.info(0x0000, 'PdfPreview', `加载进度: ${progress}`)
})
this.isLoading = false
if (result !== pdfService.ParseResult.PARSE_SUCCESS) {
hilog.error(0x0000, 'PdfPreview', `文档加载失败: ${result}`)
}
}
aboutToDisappear(): void {
this.controller.releaseDocument()
}
build() {
Stack() {
if (!this.isLoading) {
PdfView({
controller: this.controller,
pageFit: pdfService.PageFit.FIT_WIDTH,
showScroll: true
})
.width('100%')
.height('100%')
} else {
LoadingProgress()
.width(48)
.height(48)
}
}
.width('100%')
.height('100%')
}
}
这段代码里最重要的点有三个。第一,PDF 文件最好先落到应用沙箱里,再交给控制器加载。第二,页数监听放在 loadDocument 之前注册,这样总页数一出来就能接住。第三,页面退出时记得 releaseDocument(),文档实例不要一直挂在内存里。loadDocument 的参数形态和页数监听接口,已经都在 PDFKit 的控制器接口里补齐了。
二、PdfView 适合做阅读器,ArkWeb 更适合做在线预览
API 20 这一轮容易被混淆的一点,是有些回调属于 PdfView,有些回调属于 ArkWeb。registerPageCountChangedListener 是 PdfController 的能力,适合放在应用内阅读器。onPdfLoadEvent 和 onPdfScrollAtBottom 则是 ArkWeb 这条线补进来的 PDF 事件,适合放在 Web 组件加载 PDF 的场景里。两条链路都能做 PDF 预览,但适用场景不一样。
ArkWeb 这边的好处很直接。你如果本来就是基于 Web 组件去展示在线文档,或者 PDF 本身就来自远端地址,这两个事件会很好用。onPdfLoadEvent 可以直接拿到加载结果,onPdfScrollAtBottom 则特别适合做阅读完成、已读确认、加载更多推荐内容这类逻辑。ArkWeb 的事件定义页里已经把这两个事件列出来了,API 变更清单里也明确标出了它们是 6.0.0(20) Beta3 新增能力。
下面这段代码适合直接做 Web 侧 PDF 预览的起步版本:
import { webview } from '@kit.ArkWeb'
import hilog from '@ohos.hilog'
@Entry
@Component
struct WebPdfPage {
controller: webview.WebviewController = new webview.WebviewController()
build() {
Web({
src: 'https://example.com/document.pdf#page=3&zoom=200&toolbar=0&pdfbackgroundcolor=ffffff',
controller: this.controller
})
.domStorageAccess(true)
.onPdfLoadEvent((eventInfo) => {
hilog.info(
0x0000,
'WebPdfPage',
`PDF加载事件, url=${eventInfo.url}, result=${eventInfo.result}`
)
})
.onPdfScrollAtBottom((eventInfo) => {
hilog.info(
0x0000,
'WebPdfPage',
`PDF已滚动到底部, url=${eventInfo.url}`
)
// 这里可以做“已读完成”或推荐内容加载
})
}
}
这里有两个小点比较实用。第一,Web 组件支持通过 URL 参数直接控制 PDF 的初始状态,比如页码、缩放比例、工具栏显隐和背景色。第二,这条链路更适合在线预览、站内阅读页和混合内容页面,尤其是你本来已经在用 Web 承接内容展示时,这套回调会省很多事。
三、文档切换和加载反馈实践
PDF 预览这类页面有一个很常见的问题,文档一切换,界面就会闪一下。原因通常不复杂,旧文档刚释放,新文档还没真正加载出来,中间那段时间如果页面没有任何反馈,用户就会明显感知到断层。更稳的处理方式,是把加载态单独收出来,用状态变量控制 PdfView 的挂载时机。这样切文档时,页面至少会是一个稳定的 loading 过程。
这一类写法可以直接落成下面这样:
@State isLoading: boolean = false
@State currentFilePath: string = ''
async switchDocument(filePath: string): Promise<void> {
this.controller.releaseDocument()
this.isLoading = true
const result = await this.controller.loadDocument(filePath)
this.isLoading = false
if (result === pdfService.ParseResult.PARSE_SUCCESS) {
this.currentFilePath = filePath
} else {
hilog.error(0x0000, 'PdfPreview', `切换文档失败: ${result}`)
}
}
build() {
Stack() {
if (!this.isLoading) {
PdfView({
controller: this.controller,
pageFit: pdfService.PageFit.FIT_WIDTH,
showScroll: true
})
.width('100%')
.height('100%')
} else {
LoadingProgress()
.width(52)
.height(52)
}
}
.width('100%')
.height('100%')
}
这类写法解决的是一个很实际的问题,用户不需要知道底层是不是在重新解析 PDF,只要看到页面始终是稳定反馈,体验就会顺很多。加载成功和失败也都有明确状态点,后面接 Toast、错误页或者重试逻辑都会方便。loadDocument 返回 ParseResult 这一点,本身就适合拿来做这层状态管理。
四、几个容易踩的坑,提前避开
第一个坑,是把 PdfView 和 ArkWeb 的 PDF 事件混成一套。registerPageCountChangedListener 属于 PdfController,onPdfLoadEvent 和 onPdfScrollAtBottom 属于 Web 组件。两者可以都做 PDF 预览,但不该混着写。阅读器场景优先走 PdfView,在线文档或网页承载场景更适合走 Web。
第二个坑,是忽略加密文档。loadDocument 支持密码参数,密码错误和解析错误不属于一类结果。业务层最好把这两种情况分开处理,密码错了可以继续提示用户输入,解析失败就该走错误提示或回退逻辑。loadDocument(path, password?, initPageIndex?, onProgress?) 这套签名,已经把密码和进度回调都放进来了。
第三个坑,是页面退出时忘记释放文档。PDF 文档本身可能比较大,长时间挂着控制器实例不释放,内存占用会越来越难看。文档切换、页面关闭、阅读器退出这些时机,最好都把 releaseDocument() 放进去。这个问题看起来像细节,项目一大就很容易出事。
总结
鸿蒙 6 API 20 这一轮在 PDF 预览链路上做的升级,最有价值的地方就是把状态信号补全了。PdfController 侧有 loadDocument 的加载结果和页数监听,ArkWeb 侧补进了 onPdfLoadEvent 和 onPdfScrollAtBottom。只要把这几处回调接顺,文档加载反馈、总页数展示、在线预览、已读判断这些能力就都能挂到明确的节点上去做。
阅读器优先走 PdfView,在线 PDF 优先走 Web,文档切换时单独做 loading 状态,退出时及时释放资源。
更多推荐




所有评论(0)