PDF 预览看似是“把文件交给组件”,真正上线后却会遇到大文件首开慢、加密文档、沙箱路径、分页布局、搜索高亮、批注保存、外链跳转、内存释放和编辑冲突。PDF Kit 提供 pdfServicepdfViewManager.PdfControllerPdfView 等能力;API 26 适配的重点,是把控制器生命周期和业务文档状态设计完整。

本文以合同审阅场景为例,搭建一条“安全导入—加载—预览—搜索—批注—原子保存—释放”的工程链路,并明确哪些接口需要按设备与 SDK 再验证。

HarmonyOS 7 新特性(四十八)封面

一、先区分文档与视图状态

文件本身的版本、校验值和权限,不应混在页面缩放、当前页与布局状态中。

interface PdfDocumentState {
  documentId: string
  localPath: string
  sha256: string
  version: number
  readOnly: boolean
  encrypted: boolean
}

interface PdfViewState {
  pageIndex: number
  zoom: number
  continuous: boolean
  layout: 'SINGLE' | 'DOUBLE'
  searchQuery?: string
}

视图可随时重建,文档状态则需要持久化和并发控制。

二、导入文件先进入应用沙箱

无论文件来自资源、选择器、分享还是下载,都先复制到受控目录,限制大小、扩展名和来源。

interface PdfImportPolicy {
  maxBytes: number
  allowedMime: string[]
  requireHash: boolean
}

function validateImport(meta: { size: number; mime: string }, p: PdfImportPolicy) {
  if (meta.size > p.maxBytes) throw new Error('PDF_TOO_LARGE')
  if (!p.allowedMime.includes(meta.mime)) throw new Error('UNSUPPORTED_TYPE')
}

不要直接长期依赖临时 URI。复制时使用随机文件名,原始文件名只作为显示信息并做字符清洗。

三、控制器与页面生命周期成对

官方预览示例使用 new pdfViewManager.PdfController()loadDocument()PdfView。页面销毁或切换文档时必须释放。

import { pdfService, pdfViewManager, PdfView } from '@kit.PDFKit'

@Entry
@Component
struct PdfReaderPage {
  private controller: pdfViewManager.PdfController =
    new pdfViewManager.PdfController()

  async aboutToAppear() {
    const result = await this.controller.loadDocument(this.localPath)
    if (result !== pdfService.ParseResult.PARSE_SUCCESS) {
      this.showLoadError(result)
    }
  }

  aboutToDisappear() {
    this.controller.releaseDocument()
  }
}

生命周期方法和 releaseDocument 的异常处理以目标 SDK 为准;核心原则是一个已加载文档只由明确所有者释放一次。

四、加载过程做状态化错误处理

type PdfLoadState =
  | { kind: 'COPYING'; progress: number }
  | { kind: 'PARSING' }
  | { kind: 'READY'; pageCount: number }
  | { kind: 'PASSWORD_REQUIRED' }
  | { kind: 'FAILED'; code: string; retryable: boolean }

损坏、加密、权限不足、空间不足和内存不足的恢复方式不同。不要统一显示“打开失败”。加密文档密码不得写入日志或持久化明文。

HarmonyOS 7 新特性(四十八)核心链路

五、预览参数按设备形态自适应

手机默认单页连续滚动,平板横屏和 PC 可使用双页;缩放与页面适配需要保留用户上下文。

interface PdfLayoutPolicy {
  widthVp: number
  orientation: 'PORTRAIT' | 'LANDSCAPE'
  input: 'TOUCH' | 'MOUSE'
}

function chooseLayout(p: PdfLayoutPolicy): 'SINGLE' | 'DOUBLE' {
  return p.widthVp >= 840 && p.orientation === 'LANDSCAPE'
    ? 'DOUBLE' : 'SINGLE'
}

通过控制器提供的页面布局、连续滚动、适配、间距和缩放接口设置视图,切换布局后尽量保持当前页和阅读锚点。

六、搜索采用可取消会话

PdfController 提供关键字搜索、清除搜索和索引控制能力。用户连续输入时取消或覆盖旧会话。

interface SearchSession {
  id: number
  query: string
  startedAt: number
}

class PdfSearchCoordinator {
  private current = 0
  next(query: string): SearchSession {
    return { id: ++this.current, query: query.trim(), startedAt: Date.now() }
  }
  isCurrent(id: number) { return id === this.current }
}

空查询立即清除高亮。对超大文档显示搜索进度,结果跳转后保留“第 N/总数”反馈。

七、高亮与文本选择遵守坐标语义

PDF 坐标、屏幕坐标、缩放和旋转可能不同。业务侧保存页码与文档坐标,不保存屏幕像素。

interface DocumentRect {
  pageIndex: number
  left: number
  top: number
  right: number
  bottom: number
  rotation: number
}

旋转或单双页切换后重新投影显示。选择回调、矩形变化和文本选择监听的注册时机按 API 文档处理。

八、批注用命令日志保证可撤销

高亮、下划线、文本注释和形状等编辑不要直接散落调用。统一转成命令,记录操作者、文档版本和撤销信息。

interface AnnotationCommand {
  commandId: string
  documentVersion: number
  pageIndex: number
  type: 'ADD' | 'UPDATE' | 'DELETE'
  annotationId?: string
  payload: Record<string, unknown>
  createdAt: number
}

控制器的 enableAnnotation、添加/更新/删除批注等接口只由命令执行器调用,便于审计和失败重试。

九、保存使用临时文件加原子替换

直接覆盖原文件,崩溃或磁盘不足会损坏唯一副本。

async function atomicSave(target: string) {
  const temp = `${target}.saving`
  await pdfGateway.saveDocument(temp)
  await fsyncFile(temp)
  await verifyPdf(temp)
  await replaceFile(temp, target)
}

保存前比较文档版本,发现服务端或其他端已更新时进入冲突流程,不静默覆盖。

十、外链点击必须验证

PDF 内可包含 URI 跳转。registerActionClickListener 收到目标后,先校验协议、域名和用户意图。

function allowedPdfLink(raw: string): boolean {
  const url = new URL(raw)
  if (url.protocol !== 'https:') return false
  return ['docs.example.com', 'help.example.com'].includes(url.hostname)
}

未知域名用系统确认页或阻止,不允许 javascript:file: 和任意自定义 scheme 静默执行。

十一、大文件的内存与缩略图治理

不要一次将每页转换成全分辨率 PixelMap。缩略图按可见窗口生成,设 LRU 上限,页面离开后释放。

interface ThumbnailBudget {
  maxEntries: number
  maxBytes: number
  prefetchBefore: number
  prefetchAfter: number
}

const budget: ThumbnailBudget = {
  maxEntries: 24,
  maxBytes: 48 * 1024 * 1024,
  prefetchBefore: 2,
  prefetchAfter: 4
}

滚动速度快时减少后台生成,避免与正文渲染争抢资源。

十二、隐私与生命周期清理

合同、病历和账单类 PDF 属于敏感内容。进入后台时按业务要求遮罩最近任务缩略图;登出清除临时文件、搜索词和批注草稿。

interface PdfRetentionPolicy {
  cacheTtlMs: number
  removeOnLogout: boolean
  redactRecentTask: boolean
  allowExport: boolean
}

导出、分享和保存副本必须检查权限与水印策略。

十三、测试矩阵

const pdfCases = [
  '1-page-small', '1000-page-large', 'encrypted', 'corrupted',
  'mixed-page-size', 'rotated-page', 'annotation-save',
  'disk-full', 'external-link', 'background-restore'
]

手机、平板和 PC/2in1 分别验证触摸、鼠标、键盘、窗口缩放与单双页。性能报告记录首屏、翻页、搜索、保存和峰值内存。

HarmonyOS 7 新特性(四十八)检查清单

十四、上线检查清单

  • 外部 PDF 复制到受控沙箱并校验大小、类型和哈希;
  • 控制器加载与释放成对;
  • 加密、损坏、空间不足等错误可区分;
  • 布局按设备形态切换并保留阅读位置;
  • 搜索会话可取消,空查询清理高亮;
  • 批注操作可撤销、可审计;
  • 保存采用临时文件与原子替换;
  • PDF 外链经过协议和域名校验;
  • 缩略图和 PixelMap 有内存预算;
  • 登出、后台和导出遵守隐私策略。

结语

PDF Kit 提供了丰富的预览与编辑原子能力,但产品质量取决于控制器生命周期、文件一致性、安全跳转和资源预算。把文档状态与视图状态分离,用可取消搜索、命令化批注和原子保存构建闭环,再覆盖大文件、加密与多设备输入,PDF 功能才能从“能打开”走向可用于真实业务。

官方参考

  • PDF Kit 产品介绍:https://developer.huawei.com/consumer/cn/sdk/pdf-kit/
  • 预览 PDF 文档:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/pdf-pdfview-component
  • PDF Kit API 总览:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/pdf-api
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐