HarmonyOS 文件预览服务避坑指南:从入门到真香
一、背景引入:这玩意儿是干啥的?
咱今天聊的这个 Preview Kit,中文名儿叫"文件预览服务"。听名字就知道,这玩意儿就是帮你预览文件的。
你可能会问:“预览文件?我自己写个组件不就完了吗?”
嘿,您要真这么想,那我得给您点个赞——有这股劲儿,当年我写代码也是这么想的。但踩了几个坑之后,我就服了。
为啥要用 Preview Kit?
咱说个实际场景:
你在应用里做了个文件管理器,用户点了个 Word 文档,你得让人家看到内容吧?这时候你有几个选择:
- 自己写解析器:docx、xlsx、pptx、pdf… 您慢慢写,写完了叫我一声
- 接第三方 SDK:WPS、OnlyOffice,选一个,然后掏钱
- 用 Preview Kit:系统自带的,香
我选 3,为啥?
- 不用自己解析:Office 文档、PDF、图片、视频、音频,系统都给你整明白了
- 不用额外花钱:WPS 那 SDK 可不便宜
- 用户体验好:右上角还能"用其他应用打开",跳转原生应用,多顺滑
说白了,Preview Kit 就是个"神级 API",你给它一个文件 URI,它给你弹个预览窗口,完事儿。
这玩意儿能预览啥?
咱列个表,您心里有数:
| 类型 | 支持的后缀 | 备注 |
|---|---|---|
| 图片 | jpg、png、gif、webp、bmp、svg | 这个最基础,肯定支持 |
| 视频 | mp4、mkv、ts | 常见格式都 OK |
| 音频 | m4a、aac、mp3、ogg、wav | 听个响没问题 |
| 文本 | txt、cpp、c、h、java、xml | 代码文件也能看 |
| 网页 | html、htm | 内嵌网页预览 |
| 这个用得最多 | ||
| Office | doc、docx、xls、xlsx、ppt、pptx、csv、ofd | 借助 WPS 能力 |
注意啊:Office 文档预览是借助 WPS 的能力,所以预览界面会有 WPS 的技术支持标识,这个你别想着去掉,人家出的力。
二、整体架构:大概长啥样?
Preview Kit 的架构,说白了就三层:
┌─────────────────────────────────────────┐ │ 你的应用 │ (调用 openPreview 接口) └─────────────────┬───────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ Preview Kit 服务 │ (解析文件 + 渲染预览界面) └─────────────────┬───────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ 系统能力/WPS │ (真正的解析和渲染) └─────────────────────────────────────────┘ 工作流程:
- 你调用
openPreview,传入文件 URI - Preview Kit 检查文件类型,决定咋预览
- 图片视频音频:系统直接渲染
- Office/PDF:调用 WPS 能力
- 弹出一个模态窗,显示预览内容
- 用户右上角点"用其他应用打开",跳转原生应用
几个概念先整明白:
- 模态窗:就是那个预览窗口,它出来之后,你爹窗口就动不了了,只能操作它
- 应用窗:你自己的应用窗口
- AMS:AbilityManagerService,系统服务,管窗口调度的
三、核心功能:咋整?
3.1 最基础的预览
来,上代码,最简单的预览:
import{ preview }from'@kit.PreviewKit';// 假设你有个文件 URIlet fileUri ="file://com.example.app/files/test.docx";// 直接预览 preview.openPreview({ uri: fileUri }).then(()=>{console.info("预览打开成功");}).catch((err)=>{console.error("预览打开失败", err);});就这?就这!
对,就这么简单。你给它 URI,它给你预览,完事儿。
3.2 判断文件能不能预览
不是所有文件都能预览的,你得先判断一下:
import{ preview }from'@kit.PreviewKit';let fileUri ="file://com.example.app/files/test.xxx";// 先判断能不能预览 preview.canPreview({ uri: fileUri }).then((result)=>{if(result){// 能预览,打开 preview.openPreview({ uri: fileUri });}else{// 不能预览,提示用户 promptAction.showToast({ message:"这文件预览不了,您换个应用打开吧"});}});建议:这个 canPreview 最好加上,别到时候用户点了个不支持的文件,直接报错,体验不好。
3.3 文件打开加速:这玩意儿有点东西
从 HarmonyOS 5.0.3(15) 开始,多了个"文件打开加速"功能。
啥意思呢?
你有个 10MB 的 Word 文档,用户点开后,通常得等个几秒甚至十几秒才能打开。文件打开加速就是提前预测用户可能要打开哪个文件,提前在后台预加载,等用户真点的时候,"唰"一下就开了。
爽感体验,懂吧?
咋接入?
这玩意儿是 C/C++ 的 API,ArkTS 暂时还没有。你得这么整:
步骤 1:申请权限
在 module.json5 里加:
"requestPermissions": [ { "name": "ohos.permission.PRELOAD_FILE" } ] 注意:ohos.permission.PRELOAD_FILE 是受限权限,你得去 AGC 申请,不是想用就能用的。
步骤 2:CMakeLists.txt 加依赖
target_link_libraries(entry PUBLIC libopen_file_boost.so) 步骤 3:注册回调
#include"PreviewKit/open_file_boost.h"// 查询应用状态的回调 OpenFileBoost_AppState AppQueryAppStateCb(void){// 如果当前允许预加载,返回 ALLOWif(CanPreload()){return OPEN_FILE_BOOST_APP_STATE_ALLOW_PRELOAD;}else{return OPEN_FILE_BOOST_APP_STATE_REJECT_PRELOAD;}}// 预加载回调 OpenFileBoost_CbErrCode AppOnFilePreload(void* fileInfo){int32_t fileDescriptor =0;// 获取文件描述符HMS_OpenFileBoost_GetFdFromPreloadFileInfo(fileInfo,&fileDescriptor);// 获取文件路径char sandboxPath[MAX_BUFFER_LENGTH]={0};HMS_OpenFileBoost_GetSandboxPathFromPreloadFileInfo( fileInfo, sandboxPath, MAX_BUFFER_LENGTH );// 这里你自己整预加载逻辑// 比如把文件内容先读到内存里return OPEN_FILE_BOOST_CALLBACK_SUCCESS;}// 取消预加载回调 OpenFileBoost_CbErrCode AppCancelFilePreload(void* fileInfo){// 系统说"哎刚才那个文件不用预加载了"// 你在这儿清理资源return OPEN_FILE_BOOST_CALLBACK_SUCCESS;}// 注册 OpenFileBoost_ErrCode ret =HMS_OpenFileBoost_RegisterFilePreload( AppQueryAppStateCb, AppOnFilePreload, AppCancelFilePreload );if(ret != OPEN_FILE_BOOST_SUCCESS){// 注册失败,处理一下}步骤 4:用户打开文件时通知系统
// 用户真打开了预加载的文件HMS_OpenFileBoost_NotifyPreloadHit( fileDescriptor, sandboxPath, pathLen );这步很重要,你通知了系统,系统才知道"哦这文件预加载命中了",下次预测更准。
3.4 预加载状态感知:UI 提示
从 5.0.5(17) 开始,还能感知预加载状态,在 UI 上给用户提示。
场景:你做个下载管理器,文件下载完了,系统说"这文件我正在预加载",你可以在 UI 上显示个小动画,用户就知道"哦系统在加速"。
import{ openFileBoost }from'@kit.PreviewKit';// 1. 注册状态回调 openFileBoost.on('filePreloadStateChanged',(info)=>{if(info.state === openFileBoost.FilePreloadState.PRELOADING){// 预加载中,显示 loading 动画showLoadingAnimation();}if(info.state === openFileBoost.FilePreloadState.PRELOADED){// 预加载完成,显示完成动画showLoadedAnimation();}if(info.state === openFileBoost.FilePreloadState.NOT_PRELOADED){// 没预加载,啥也别显示}});// 2. 添加要监听的文件 openFileBoost.addFile("/storage/Users/currentUser/Desktop/10MB_file.docx");// 3. 查询当前状态let status = openFileBoost.queryFilePreloadStatusInfo("/storage/Users/currentUser/Desktop/10MB_file.docx");console.info("预加载状态:", status.state);注意:
- 最多监听 50 个文件
- 不用的时候
removeFile取消监听 - 退出的时候
off注销回调
四、避坑指南
好了,重头戏来了。下面是我踩过的坑,你们别踩。
坑 1:传入路径而不是 URI
翻车现场:
// 错误示范 preview.openPreview({ uri:"/storage/Users/currentUser/Desktop/test.docx"// 这是路径!});// 结果:预览失败,没报错,就是打不开正确姿势:
// 正确示范 preview.openPreview({ uri:"file://com.example.app/files/test.docx"// 这是 URI});记住:openPreview 只认 URI,不认路径。URI 长这样:file:// 开头。
坑 2:DocumentViewPicker 的 URI 没持久化权限
翻车现场:
// 用户通过 DocumentViewPicker 选了个文件let filePicker =newdocumentViewPicker.DocumentViewPicker();let uris =await filePicker.open();// 直接预览 preview.openPreview({ uri: uris[0]});// 结果:预览失败,提示"无权限"为啥?
DocumentViewPicker 拿到的 URI,你只有临时权限,这权限不能分享给预览服务。
正确姿势:
// 方式 1:持久化权限import{ fileUri }from'@kit.CoreFileKit';let uris =await filePicker.open();// 先持久化权限 fileUri.acquirePersistedUriPermission(uris[0]);// 再预览 preview.openPreview({ uri: uris[0]});// 方式 2:拷贝到沙箱import{ fileIo }from'@kit.CoreFileKit';let uris =await filePicker.open();// 拷贝到沙箱let sandboxUri ="file://com.example.app/files/copied.docx";await fileIo.copy(uris[0], sandboxUri);// 预览沙箱文件 preview.openPreview({ uri: sandboxUri });建议:用方式 2,简单,不用操心权限问题。
坑 3:模拟器上测 Office 预览
翻车现场:
// 模拟器上 preview.openPreview({ uri:"file://.../test.docx"});// 结果:打不开,以为是代码问题真相:
模拟器不支持 .pdf、.pptx、.xlsx、.docx 等文档类文件的预览。真机可以,模拟器不行。
正确姿势:
别在模拟器上测文档预览,直接上真机。模拟器只测测图片视频得了。
坑 4:文件打开加速只在 2in1 设备上支持
翻车现场:
// 在手机上测试文件打开加速// 结果:注册回调没反应,以为代码写错了真相:
文件打开加速功能只在 2in1 设备(就是那种能当平板又能当电脑的设备)上支持。手机、平板都不支持。
正确姿势:
// 先检查系统能力import{ systemCapability }from'@kit.SystemCapabilityKit';let supported = systemCapability.isSystemCapabilityAvailable('SystemCapability.PCService.OpenFileBoost');if(supported){// 支持,注册加速registerFilePreload();}else{// 不支持,别费劲了console.info("这设备不支持文件打开加速");}坑 5:受限权限没申请
翻车现场:
// module.json5 "requestPermissions": [ { "name": "ohos.permission.PRELOAD_FILE" } ] 代码写好了,一运行,报错"权限不足"。
真相:
ohos.permission.PRELOAD_FILE 是受限权限,不是你在 module.json5 里写了就行的。你得去 AGC(AppGallery Connect)申请,提交材料,等审核。
正确姿势:
- 先去 AGC 申请受限权限
- 等审核通过
- 下载权限证书
- 配置到项目里
- 然后再用
建议:如果只是为了预览,别整文件打开加速,那玩意儿门槛高。基础的 openPreview 就够了。
坑 6:预览完不关窗口
翻车现场:
用户点预览,预览窗口出来了,看完想关掉,找不到关闭按钮。
真相:
Preview Kit 的预览窗口是模态窗,右上角有个"×",但有些用户找不到。
正确姿势:
这个真没法改,系统设计的。但你可以在预览之前提示用户:
promptAction.showDialog({ title:"提示", message:"预览窗口右上角可以关闭", buttons:[{ text:"知道了"}]});坑 7:URI 不存在
翻车现场:
// 文件已经被删了,但 URI 还在 preview.openPreview({ uri:"file://.../deleted.docx"});// 结果:没反应,也不报错正确姿势:
import{ fileIo }from'@kit.CoreFileKit';asyncfunctionsafePreview(uri:string){// 先检查文件存在不try{await fileIo.access(uri);// 存在,预览 preview.openPreview({ uri });}catch{// 不存在,提示 promptAction.showToast({ message:"文件不存在,可能已经被删除了"});}}五、完整实战案例
来,整个完整的例子,你直接抄作业。
场景:文件管理器预览功能
// FilePreviewManager.tsimport{ preview }from'@kit.PreviewKit';import{ fileIo }from'@kit.CoreFileKit';import{ promptAction }from'@kit.ArkUI';exportclassFilePreviewManager{// 预览文件asyncpreviewFile(uri:string):Promise<void>{// 1. 检查文件存在try{await fileIo.access(uri);}catch{ promptAction.showToast({ message:"文件不存在"});return;}// 2. 检查能不能预览try{let canPreview =await preview.canPreview({ uri });if(!canPreview){ promptAction.showToast({ message:"这文件预览不了,用其他应用打开吧"});return;}}catch{ promptAction.showToast({ message:"预览服务不可用"});return;}// 3. 打开预览try{await preview.openPreview({ uri });console.info("预览打开成功");}catch(err){console.error("预览打开失败", err); promptAction.showToast({ message:"预览打开失败,请稍后重试"});}}// 批量预览(比如用户选了多个图片)asyncpreviewFiles(uris:string[]):Promise<void>{for(let uri of uris){awaitthis.previewFile(uri);// 稍微等会儿,别一下全弹出来awaitnewPromise(resolve =>setTimeout(resolve,300));}}}// 使用let previewManager =newFilePreviewManager();// 用户点了个文件 previewManager.previewFile("file://com.example.app/files/test.docx");六、总结
好了,唠了这么多,总结一下:
Preview Kit 能干啥?
- 预览图片、视频、音频、文本、HTML、PDF、Office 文档
- 右上角能"用其他应用打开",跳转原生应用
- 可选的文件打开加速功能(2in1 设备)
咋用?
import{ preview }from'@kit.PreviewKit'; preview.openPreview({ uri:"file://..."});就这,简单。
有啥坑?
- 只认 URI,不认路径
- DocumentViewPicker 的 URI 要持久化权限或拷贝到沙箱
- 模拟器不支持文档预览
- 文件打开加速只在 2in1 设备上支持
- 受限权限要去 AGC 申请
- 先检查文件存在不
我的建议
如果你就是做个简单的预览功能:
- 用
openPreview就够了 - 别整文件打开加速,那玩意儿门槛高
- 记得加
canPreview判断 - URI 一定要检查存在不
如果你要做深度优化:
- 文件打开加速可以考虑,但只针对 2in1 设备
- 预加载状态感知可以加,UI 提示更友好
- 受限权限提前申请,别等上线了才发现用不了