尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
OpenHarmony 五种截屏方式与避坑指南
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 正常无触屏设备、定制 ROMhdc 命令行截屏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 去取原始数据。我个人的习惯是日常快速记录用主机侧工具一旦涉及这个图是不是设备真实输出这种疑问立刻切到设备侧命令行两条路交叉验证基本就不会被表象骗到。
RELATED

相关推荐

OpenHarmony截屏五种方式:三种粒度选型与权限避坑

OpenHarmony截屏五种方式:三种粒度选型与权限避坑

上周在群里被问到一个挺典型的问题:OpenHarmony 设备上想弄一张屏幕截图,除了老老实实按电源键加音量减,还有没有别的路子?问的人不是普通用户,是个正在做行业定制的开发,他的真实诉求是"我要在自动化…

📅 2026/10/1 6:17:44
Android网络视频播放器源码拆解:ExoPlayer缓存与缓冲策略实战

Android网络视频播放器源码拆解:ExoPlayer缓存与缓冲策略实战

简介:这是一份基于安卓的网络视频播放器完整源码项目,定位为毕业设计参考与安卓进阶练习,覆盖网络视频解析、播放控制、进度同步、全屏切换等典型功能。项目源码以Java实现为主,涉及网络通信、异步任务和界面布局,适合…

📅 2026/10/1 6:17:44
从零搭建AI工程体系:数据、训练、部署、监控全链路实战

从零搭建AI工程体系:数据、训练、部署、监控全链路实战

我带了几年做AI应用落地的团队,面试过不少算法背景的候选人,发现一个特别普遍的现象:让他在notebook里跑一个模型,准确率能聊得头头是道,损失曲线怎么分析也门儿清,但只要一问"这个模型上线后用户请求…

📅 2026/10/1 6:17:44
MORE NEWS

更多资讯

📰

从零实现自定义视频播放控件:属性、事件与实战踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

C++实现不围棋游戏:MCTS状态管理与OpenGL界面整合实战

简介:基于C实现的不围棋游戏完整源码,系计算概论期末大作业,适合学习游戏编程、蒙特卡洛树搜索与OpenGL交互的学生,也可作为课程设计或AI博弈入门范例。不围棋规则中,落子若吃掉对方棋子或自杀均判负,禁止空…

📰

凸包问题算法详解:Graham扫描与Andrew单调链实战

凸包(Convex Hull)问题算法详解如果你跟计算几何打过交道,肯定绕不开凸包这个东西。简单说,给一堆散落的点,凸包就是能把所有点都包进去的最小凸多边形,就像拿一根橡皮筋把钉子板上的钉子全部箍起来。听起来…

📰

Chrome 6并发限制下的大屏首屏加载优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Switch玩暗黑2离线方案:模拟器+网络劫持实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Cursor+GitOps:自动化运维新姿势,TaoToken 统一 Key 接入实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬