☰
OpenHarmony 五种截屏方式与避坑指南
2026/10/1 6:17:44 网站建设 项目流程

hdc shell snapshot_display这个命令,我第一次在 OpenHarmony 开发板上敲的时候,返回了一句 usage,当时还以为工具没编进去。后来翻了图形子系统的源码才发现,参数写法和我想的不一样。这件事让我意识到,OpenHarmony 的截屏能力其实分散在好几层:内核按键事件、SystemUI 的按键代理、图形子系统的合成器、应用框架的 API,每一层都能截,但适用的人和场景完全不同。这篇就把我实际用过的五种截屏方式摊开讲,从端侧用户按组合键,到开发者用 hdc 命令行,再到应用内调 screenshot 模块和 AVScreenCapture。如果你手上是开发板、x86 模拟器,或者正在写一个需要截屏的系统应用,下面这些内容可以直接抄。

1. 先搞清楚需求:OpenHarmony 截屏到底分几种场景

很多人一上来就问"OpenHarmony 怎么截屏",这个问题本身就不成立,因为问的人身份不一样,答案就完全不一样。普通用户要的是按一下键就有图;驱动和应用开发者要的是拿到一个能存盘的 PixelMap 或者 jpeg 文件;做投屏、录屏、远程协助的人要的是持续的帧数据流。需求不同,能走的路也完全不同。

1.1 用户态、开发者态、应用内三条主线

我习惯把 OpenHarmony 的截屏拆成三条主线来理解。

第一条是用户态截屏,指的是设备已经正常开机、跑起了桌面和 SystemUI,用户通过物理按键或者控制中心的下拉快捷开关触发。这条链路对使用者最友好,但对开发者来说可控性最差,因为中间隔了 SystemUI 和按键服务两层。

第二条是开发者态截屏,指的是通过 hdc 连上设备,在命令行或者 IDE 侧发起截屏。这条路的优势是不依赖设备有没有屏幕、有没有按键,x86 模拟器和无头设备都能用,缺点是需要设备开启调试授权,量产机器上基本走不通。

第三条是应用内截屏,指的是应用自己调用 OpenHarmony 提供的截屏接口,把当前屏幕或者某个窗口的内容抓成图像对象,再自己决定怎么处理。这条路能力最强,可以做区域截取、可以做实时帧流,但权限门槛也最高,绝大多数截屏接口都要求系统应用级别。

提示:选哪条路之前,先明确你的身份是"用设备的人""调设备的人"还是"在设备上写代码的人",这个定位错了,后面全是白费功夫。

1.2 五种方式的选型对照表

我把下面要详细讲的五种方式先做个横向对比,方便你直接对号入座。

方式触发入口依赖条件权限门槛典型使用场景
物理组合键电源键+音量下键设备有实体按键、SystemUI 正常无手机、平板日常使用
控制中心快捷开关下拉控制中心点击SystemUI 正常无触屏设备、定制 ROM
hdc 命令行截屏snapshot_displayhdc 已连接、调试授权shell 权限开发调试、x86 模拟器
screenshot 模块 API应用代码调用系统应用、签名CAPTURE_SCREEN系统工具类应用
窗口快照/屏幕捕获window.snapshot、AVScreenCapture应用框架、媒体框架视接口而定投屏、录屏、截帧

这张表里最容易被忽略的是权限门槛那一列。很多人在 OpenHarmony 应用里调 screenshot 报 201 错误,第一反应是代码写错了,其实十有八九是权限没申请下来——ohos.permission.CAPTURE_SCREEN是系统核心级别权限,普通三方应用根本拿不到,这一点必须先认清。

1.3 动手前必须确认的三件事

正式操作之前,有三件事我会先确认一遍,能省掉后面大量返工。

第一,设备版本和 API 版本。AVScreenCapture 这类接口在不同 API 版本上包名和参数都变过,早期挂在 media 下,后来独立成 avScreenCapture 模块。你手上如果是老版本 SDK,照抄新文档一定编不过。

第二,hdc 是否真的连上了。hdc list targets输出为空的话,后面所有命令行截屏都是空谈,先把设备授权弹窗点掉再说。

第三,你要的是全屏还是指定区域。这个决定你走 API 还是走命令行。命令行工具基本只能全屏,想要区域截取就得靠 API 里的 screenRect 参数自己算。

2. 方式一与方式二:端侧用户按键与快捷开关截屏

先聊最贴近用户的两种,因为这俩是绝大多数人第一次接触 OpenHarmony 设备时会用的方式,也是出问题最多、最难排查的方式,因为链路太长了。

2.1 物理组合键:电源键加音量下键的完整链路

OpenHarmony 默认的截屏组合键是电源键 + 音量下键同时短按。这个组合不是写在某个配置文件里就完事的,它背后是一条相当长的调用链,理解这条链对你排查"按键没反应"很有帮助。

按键按下去之后,事件从内核的输入子系统冒上来,被 OpenHarmony 的输入框架multimodalinput接住。输入框架里有一个按键事件的分发逻辑,它会判断这是不是系统级组合键。判断通过后,事件会被交给电源管理服务power_manager和 SystemUI 侧的按键代理模块。真正执行截屏动作的一般在 SystemUI 里,它调用图形子系统提供的截屏能力,拿到图像后写到相册目录,最后弹一个缩略图动画提示用户。

这条链上任何一环出问题,表现都是"按了没反应"。我遇到过的情况包括:输入事件被某个前台应用拦截了、SystemUI 进程异常重启了、以及最离谱的一次是设备根本没编进截屏服务的动态库。

截屏文件默认落在图库的 Screenshots 相册里,物理路径形如/storage/media/100/local/files/Pictures/Screenshots/,文件名一般是时间戳格式。如果你在调试设备上看不到图,先去这个目录ls一下,比盯着屏幕猜有用得多。

2.2 控制中心快捷开关:触屏设备的主流路径

平板和触屏设备上更常用的是下拉控制中心,点"截屏"开关。这条路径和组合键最终走的是同一个执行入口,区别只在于触发源从按键事件变成了 SystemUI 的一个按钮回调。所以有个很实用的结论:快捷开关能截、组合键截不了,说明问题出在按键链路;反过来则是 SystemUI 的截屏执行本身有问题。这个对照法能帮你快速把故障范围砍一半。

在开发板上做产品定制的时候,控制中心的快捷开关布局通常是可以改的,各厂商在 SystemUI 的配置文件里调整开关列表。有的厂商还会加"长截屏""区域截屏"这类扩展,这些就不是 OpenHarmony 原生能力了,属于厂商自己在 SystemUI 和图形接口上做的二次封装。

2.3 按键截屏的实操心得与踩坑记录

讲几个我自己踩过的坑。

第一个是时序问题。电源键和音量下键必须"同时"按,但是人手动按很难真的同时。实测下来,先按住电源键再快速补音量下键的成功率,比反过来要高一点。这不是玄学,因为电源键的按下事件通常优先级更高,先建立上下文再补组合键判定更容易命中。

第二个是长按和短按的边界。按住超过一秒往往会被识别成关机菜单而不是截屏,这个阈值在各厂商实现里不一样,有的在电源服务里配,有的在 SystemUI 里写死。做定制的时候如果发现误触发关机,就去查这个判定阈值。

第三个是锁屏状态下截屏。锁屏界面截屏涉及隐私,部分实现会直接屏蔽,或者在图库里打码。这个行为不是 bug,是策略,别浪费时间去"修"。

注意:在 x86 或者 PC 形态的 OpenHarmony 设备上,物理按键这条路基本走不通,因为压根没有对应的按键硬件。这类设备请直接跳到第 3 节的命令行方案。

3. 方式三:hdc 命令行截屏,开发者最顺手的一条路

如果你是在开发板上调试、在 x86 模拟器里跑应用,或者单纯想快速拿一张设备当前画面,命令行截屏是性价比最高的一条路。它不依赖按键、不依赖屏幕朝向、不依赖 SystemUI 状态,只要能连上 hdc 就能用。

3.1 snapshot_display 工具的参数拆解

OpenHarmony 图形子系统里带了一个叫snapshot_display的命令行工具,位置一般在/system/bin/snapshot_display。用法很朴素:

# 先看看工具支持哪些参数,不同版本参数集不完全一致 hdc shell snapshot_display -h # 最简用法:截一张全屏图,存到设备临时目录 hdc shell snapshot_display -f /data/local/tmp/shot.jpeg

参数里最常用的是-f,指定输出文件路径。这里有个必须注意的点:输出格式由文件扩展名决定,写.jpeg它才给你 jpeg,写成.png大概率报错或者存不出来。我见过不少人卡在这里,以为工具坏了。

比较复杂的一点是分辨率和延迟相关的参数。部分版本的snapshot_display支持指定宽高和延迟时间,用来抓某些动画中间态的帧。这些参数各版本差异比较大,我一般不建议盲写,先-h看一眼当前设备的实际支持情况,再决定用哪些。这样做比照抄网上的命令靠谱。

3.2 从设备拉图到主机的完整流程

命令行截屏的完整流程是"设备侧生成、主机侧取回"两步,很多人只做了第一步,然后发现主机目录下什么都没有。

# 第一步:设备侧生成截图,放到 /data/local/tmp 这种 shell 可写目录 hdc shell snapshot_display -f /data/local/tmp/shot.jpeg # 第二步:把文件从设备拉回主机当前目录 hdc file recv /data/local/tmp/shot.jpeg ./shot.jpeg # 顺手确认一下文件是真的有内容,不是 0 字节 ls -lh ./shot.jpeg

选/data/local/tmp/作为落盘目录是有讲究的。这个目录 shell 用户可写可读,权限宽松,不用担心 SELinux 拦截。我曾经图省事直接往/storage/下面写,结果因为访问策略被拦,命令返回成功但文件根本没落地,排查了半天。

拿到主机之后,验证文件的有效性也有个技巧:jpeg 文件头是固定的 FF D8 开头,用xxd或者十六进制工具看一眼前两个字节,就能快速判断这是真图还是一个空壳文件。这个习惯在处理"截出来全黑"的问题时特别有用,因为全黑的图也是合法 jpeg,文件头一样,但至少能排除"根本没截到"这种情况。

3.3 x86 模拟器与开发板上的差异

x86 形态的 OpenHarmony 设备有个绕不开的问题:没有物理按键。所以第 2 节讲的两条路在这种环境下是废的。剩下能用的就是命令行,以及用uinput工具模拟按键事件。

# 模拟按一下电源键,键码 116 对应 KEY_POWER hdc shell uinput -K -d 116 -u 116 # 音量下键的键码是 114 hdc shell uinput -K -d 114 -u 114

但这里有个很现实的坑:用 uinput 模拟组合键几乎不可能成功。因为上面两条命令是串行执行的,中间隔着一次 shell 往返,时间差通常几十毫秒,远超"同时按下"的窗口。你得到的结果是设备先响应了一次电源键短按,再响应了一次音量键,组合判定根本不会触发。所以我从来不推荐用 uinput 去凑组合键,老老实实用snapshot_display更省事。

心得:uinput 模拟单键是有用的,比如你想自动点亮屏幕再截屏,可以先发一次电源键唤醒,等一秒再调 snapshot_display,这个组合在自动化脚本里很实用。

4. 方式四:应用内 screenshot 模块 API 截屏

前面三种都是从外部操作设备,这一节开始进到应用代码里。如果目标是在一个系统级应用里实现"点按钮截图并保存",那@ohos.screenshot模块就是最直接的答案,但它有两个硬门槛:权限和签名。

4.1 接口原型与调用流程

screenshot模块的核心接口是save,传入一个描述截取范围的对象,返回一个 PixelMap 图像对象。整体流程是"组装参数 → 调 save → 拿到 PixelMap → 自己编码存盘"。

import screenshot from '@ohos.screenshot'; import image from '@ohos.multimedia.image'; import fs from '@ohos.file.fs'; async function captureFullScreen(displayId: number) { // screenRect 描述要截的屏幕区域,这里取全屏 720x1280 const options: screenshot.ScreenshotOptions = { screenRect: { left: 0, top: 0, width: 720, height: 1280 }, imageSize: { width: 720, height: 1280 }, rotation: 0, displayId: displayId, }; const pixelMap: image.PixelMap = await screenshot.save(options); return pixelMap; }

这里几个参数值得单独说说。screenRect是你在屏幕坐标系里圈出来的矩形,imageSize是你希望输出图像的像素尺寸。这两个可以不一样,前者决定"截哪块",后者决定"输出多大",OpenHarmony 会帮你做缩放。理解这一点很关键,因为做区域截屏就是靠调整 screenRect 实现的,而不是靠截全屏再裁剪,后者浪费内存还慢。

displayId在单屏设备上一般是 0。多屏场景下得靠@ohos.display模块遍历拿到每个屏幕的 id,这个在车机类的多屏设备上会用到。

4.2 权限申请与签名配置

权限是这条路最大的拦路虎。screenshot.save需要ohos.permission.CAPTURE_SCREEN,这个权限的可用级别是系统核心(system_core),意味着它只对系统应用开放,普通三方应用在权限声明阶段就会被卡住。

申请方式是在模块的配置文件里静态声明:

{ "requestPermissions": [ { "name": "ohos.permission.CAPTURE_SCREEN", "reason": "$string:reason_capture_screen", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ] }

光声明还不够,应用必须用系统级证书签名,并且安装到系统应用目录下,才能真的拿到这个权限。如果你是在做产品定制,这一步通常由系统集成方在编译镜像时完成,把应用预置进去。如果你的应用是后面单独侧载安装的,哪怕声明了权限也会被拒绝。

调试阶段最常见的报错是 201(权限校验不通过)。我的排查顺序是:先看权限有没有声明,再看签名证书是不是系统级,最后看应用是不是被预置到了系统分区。绝大多数情况下问题出在第二步。

4.3 完整落盘示例与内存释放

拿到 PixelMap 之后,还得把它编码成文件格式再存盘,这一步很多人会漏掉,结果对象在内存里飘着,既看不到文件,也占着显存。

async function savePixelMapToFile(pixelMap: image.PixelMap, filePath: string) { const imagePacker = image.createImagePacker(); const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 98, }; const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); try { await imagePacker.packing(pixelMap, packOpts, file.fd); } finally { fs.closeSync(file); imagePacker.release(); } }

quality参数我一般给到 98,因为截屏不是照片分享,没必要为了省体积牺牲清晰度。如果你要做大批量连续截屏,比如每隔 500 毫秒截一张,那就要特别注意release()的调用时机,PixelMap 不释放会迅速吃满图形内存,最后表现为应用被杀或者设备整体卡顿,而且这个现象很有迷惑性,你会以为是渲染出了问题。

5. 方式五:窗口快照与 AVScreenCapture 的取帧方案

第五种方式其实包含了两个不同的接口,适用场景差别挺大,但都属于"应用框架内取图像"这个大类,我把它们放在一起讲,方便你对比选择。

5.1 window.snapshot 窗口级截图的边界

如果只想截自己应用的窗口内容,而不是整个屏幕,window.snapshot是最轻的选择。

import window from '@ohos.window'; async function snapshotOwnWindow(context: Context) { const win = await window.getLastWindow(context); const pixelMap: image.PixelMap = await win.snapshot(); return pixelMap; }

它有几个鲜明的特点。第一,只能截到本应用自己的窗口,截不到别的应用,也截不到系统桌面,这是安全设计不是缺陷。第二,它截的是窗口内容,不含窗口外的系统状态栏,所以如果你要的是"完整的一张屏幕图",用这个会缺边。第三,它对权限的要求比 screenshot 宽松得多,普通应用就能用,这也是它最实用的地方。

我实际用它做过一个应用内"分享当前页面"的功能,把主页截图后生成分享卡片,全程不需要任何系统权限,侧载安装照样跑得通。这个方案值得更多开发者知道。

5.2 AVScreenCapture 屏幕捕获的工作方式

要做录屏、投屏、远程协助这类需要连续帧的场景,就得请出AVScreenCapture。它本质上是屏幕捕获,但同时给你音频和视频两条流,你可以只取视频流做录屏,也可以按帧取图做实时截图。

大致流程是创建捕获实例、配置音视频源、启动捕获、在回调里处理视频帧。

import avScreenCapture from '@ohos.multimedia.avscreenCapture'; const capture = avScreenCapture.createAVScreenCaptureRecorder(); await capture.init({ // 具体字段随 API 版本变化,以手上 SDK 的 d.ts 为准 videoCapSource: avScreenCapture.VideoCaptureSource.SCREEN, });

这里我必须强调一个现实问题:AVScreenCapture 的接口在 API 版本演进中改过包名和配置结构,早期挂在 media 模块下,后来独立出来。所以你在网上找到的示例代码很可能编不过,最靠谱的做法是打开本地 SDK 目录下的.d.ts声明文件,以那份为准。这个习惯我在 OpenHarmony 开发上养成了很久,能避开大量版本坑。

它同样需要系统级权限,并且因为涉及音视频采集,隐私相关的策略会更严格,部分设备上还需要用户显式授权。

5.3 帧率、分辨率与性能的取舍

用 AVScreenCapture 做实时截图时,性能是绕不开的。

分辨率越高,单帧的 PixelMap 内存占用越大。一张 1080x2340 的 ARGB 图,算下来单帧就是 10MB 左右,你如果按每秒 30 帧去取,光图像内存就是 300MB 级别的压力,普通设备根本扛不住。我在做远程协助 demo 的时候就是这么翻车的,最后把采集分辨率降到 720p 以下,帧率压到 5 帧,才稳定下来。

帧率的选择也有讲究。做"定时截屏上传"这类业务,其实完全不需要高帧率,1 到 2 帧足够了,剩下的靠业务层去重。追求高帧率只在做流畅投屏时才必要,而那种场景下你更应该考虑硬编码和传输优化,而不是死磕采集帧率。

6. 踩坑现场:黑屏、隐私模式与渲染异常的排查

这一节是我觉得最有价值的部分,因为上面五种方式你都会用了之后,真正折磨人的是"为什么截出来不对"。

6.1 截图全黑、全白、花屏的原因定位

截出来全黑是最常见的抱怨。可能的原因有好几类,我按概率从高到低排:

第一,采集时机太早。界面还没完成渲染就去截,拿到的是一张还没画内容的缓冲区。解决方法是加延迟,命令行工具用延迟参数,API 方式就在调用前等一帧或者监听窗口的绘制完成事件。

第二,受保护图层。系统对某些内容做了保护,捕获到的这块区域会被填充成黑色。这种情况你换什么方式截都是黑的,不是工具问题。

第三,隐私模式生效。下一小节专门讲。

截出来花屏则更多和渲染管线有关。我在热词里看到"openharmony 画面渲染异常"这个说法,实际遇到的情况包括:合成器输出的缓冲区格式和你期望的不一致、宽高对齐没处理好导致的错行、以及某些 GPU 路径下的纹理同步问题。花屏的一个快速判断方法是换个分辨率再截一次,如果换分辨率后正常了,那基本可以锁定是缓冲区对齐或者尺寸计算的问题。

6.2 禁止截屏:隐私模式的开启与影响

activity 禁止截屏这个需求在实际产品里非常常见,比如密码输入页、支付页。OpenHarmony 提供的做法是设置窗口隐私模式:

const win = await window.getLastWindow(context); await win.setWindowPrivacyMode(true);

开启之后,这个窗口的内容在任何截屏、录屏、投屏路径里都会被隐藏,通常表现为一块纯色区域。这个设置是窗口级的,只影响被设置的那个窗口,切换页面记得关掉。

这里有个很容易踩的坑:它同时也会影响你自己的截图逻辑。如果你的应用开了隐私模式,然后用window.snapshot去截自己,拿到的也会是被遮挡后的内容。我遇到过开发同学在支付页做"截图反馈问题"的功能,结果反馈上来的图全是黑块,排查了半天才发现是自己开的隐私模式在生效,属于自家人打自家人。

6.3 常见问题速查表与主机侧工具的补充

把上面这些整理成一张速查表,出问题的时候直接对号入座。

现象最可能原因快速验证方法处理方向
组合键无反应按键链路被拦或 SystemUI 异常试控制中心快捷开关对比两种触发源缩小范围
hdc 命令返回成功但无文件落盘目录权限被拦换/data/local/tmp/重试避开受限目录
文件 0 字节图形服务未就绪检查文件大小加延迟或等待渲染完成
截图全黑采集过早或受保护图层延迟后再截调整时机,确认无隐私模式
花屏错行缓冲区宽高对齐问题换分辨率重截检查尺寸计算
API 报 201权限或签名不达标查权限声明与证书系统签名并预置应用
截图带黑块隐私模式被开启检查 setWindowPrivacyMode关闭或调整作用窗口

最后补充一个工具层面的经验。snipaste这类主机侧的截图工具在开发过程中确实好用,但一定要分清楚:它截的是你主机屏幕上显示的画面,不是设备侧的真实输出。我在调试 x86 模拟器的时候,经常用这类工具快速抓一张模拟器窗口的图发群里,方便快捷。但如果要验证设备真实的图像输出、排查渲染或隐私相关的问题,主机侧截图工具是帮不上忙的,因为它截的是"显示器的显示结果",中间隔了一层投屏或者渲染窗口,任何设备侧的异常在这一步都可能被抹平。真正要定位问题,还是得回到设备侧,用snapshot_display或者应用内的 API 去取原始数据。

我个人的习惯是:日常快速记录用主机侧工具,一旦涉及"这个图是不是设备真实输出"这种疑问,立刻切到设备侧命令行,两条路交叉验证,基本就不会被表象骗到。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询