☰
Android takeScreenshot API实战:CircleToSearch无权限截屏与HardwareBuffer解析
2026/10/29 10:43:58 网站建设 项目流程

【免费下载链接】CircleToSearch

CircleToSearch with multi search engine support for all android

项目地址:https://gitcode.com/gh_mirrors/ci/CircleToSearch
点击查看免费下载

CircleToSearch是一款面向所有 Android 设备的多引擎识图搜索应用(Google、Bing、Yandex、TinEye)。它的核心技术,就是借助 Android 11+(API 30)为无障碍服务提供的takeScreenshot API,在不申请 MediaProjection、不弹系统授权对话框的情况下截取当前屏幕,再对截屏结果中的 HardwareBuffer 进行包装与解析。本文将结合这个开源项目的源码,完整拆解这条从"无权限截屏"到"软件位图"的链路。

为什么选 takeScreenshot:无权限截屏的关键

Android 应用截屏传统上有两条路,体验都不好:

方案痛点
MediaProjection 虚拟屏每次使用都要弹"是否允许录屏"对话框,且流程繁琐
读系统截屏文件Android 10+ 存储隔离,基本不可行

而 Android 11 引入的AccessibilityService.takeScreenshot()让已开启的无障碍服务可以直接抓取指定 Display 的当前画面:

  • ✅ 无需MediaProjection权限、无系统弹窗,截屏"零打断"
  • ⚠️ 前提是用户手动开启无障碍服务(这本身就是系统级授权),且服务需声明canTakeScreenshot="true"

这也是 CircleToSearch 能"在任意应用之上随手圈选搜索"的底层能力——截屏必须发生在 Overlay 弹出之前,否则截到的就是自己的界面。

前置条件:无障碍服务配置中开启截屏能力

整个能力的第一块拼图在 accessibility_service_config.xml 中:

<accessibility-service android:canRetrieveWindowContent="true" android:canPerformGestures="true" android:canTakeScreenshot="true" ... />

同时 AndroidManifest.xml 中服务需绑定BIND_ACCESSIBILITY_SERVICE权限。另外项目在onServiceConnected中还会叠加FLAG_RETRIEVE_INTERACTIVE_WINDOWS等标志(见 CircleToSearchAccessibilityService.kt),用于读取多窗口内容,保证截屏与文本节点坐标对齐。

takeScreenshot 实战:从调用到回调

核心实现在 performCapture(),骨架如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { takeScreenshot( Display.DEFAULT_DISPLAY, // 目标显示 executor, // 回调执行的线程池 object : TakeScreenshotCallback { override fun onSuccess(screenshot: ScreenshotResult) { /* 处理 HardwareBuffer */ } override fun onFailure(errorCode: Int) { /* 记录错误码 */ } } ) }

三个要点:

  1. 版本门槛是 API 30(R),低版本需走其他触发通道或降级;
  2. 回调在指定 Executor 上执行(项目用单线程 Executor),不是主线程,后续拉起 Activity 前注意线程切换;
  3. 成功回调拿到的是ScreenshotResult,它不直接给 Bitmap,而是携带一块HardwareBuffer+ColorSpace——这正是下一节要解析的部分。

HardwareBuffer 解析:wrapHardwareBuffer 与 ARGB_8888 拷贝

ScreenshotResult.hardwareBuffer是 GPU 侧的显存块,不能直接当普通 Bitmap 裁剪、读取像素。项目采用"包装 → 拷贝 → 释放"三步曲(源码位置):

val hardwareBuffer = screenshot.hardwareBuffer val colorSpace = screenshot.colorSpace // ① 硬件包装:得到一个 GPU 支撑的(不可变)Bitmap val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace) if (bitmap == null) { hardwareBuffer.close() // 包装失败,立即释放缓冲 return } // ② 拷贝为纯软件位图,可裁剪、可逐像素访问 val copy = bitmap.copy(Bitmap.Config.ARGB_8888, false) hardwareBuffer.close() // ③ 拷贝完成,显式关闭 HardwareBuffer BitmapRepository.setScreenshot(copy) launchOverlay(searchModeOverride)

为什么要传 ColorSpace?高分辨率屏幕常使用广色域(如 Display P3),包装时带上原始 ColorSpace 可以避免颜色偏移。

为什么要再 copy 一次?wrapHardwareBuffer返回的 Bitmap 是硬件支撑的(hardware-backed),对它的读写受 GPU 限制且不可修改;copy(ARGB_8888, false)生成纯 CPU 位图后,后续的Bitmap.createBitmap()裁剪、OCR 像素读取才能正常工作。

必须close()吗?必须。HardwareBuffer实现了ExplicitCloseable,忘记关闭会泄漏显存,重复 close 也会抛异常——所以项目里"包装失败"与"拷贝完成"两条路径都显式调用了close()。

内存级接力:BitmapRepository 的无落盘设计

截屏完成后位图并不通过 Intent 传递(Bitmap 太大,parceling 有 1MB 上下限风险),而是存进一个内存单例 BitmapRepository.kt:

object BitmapRepository { private var screenshot: Bitmap? = null fun setScreenshot(bitmap: Bitmap?) { screenshot = bitmap } fun getScreenshot(): Bitmap? = screenshot fun clear() { screenshot = null } }

接力链非常简洁:

  1. 无障碍服务setScreenshot(copy)写入;
  2. OverlayActivity.loadScreenshot() 启动后读取,渲染出圈选界面;
  3. 关闭 Overlay 时BitmapRepository.clear(),onDestroy 再做兜底清理。

这个设计的隐私收益很直观:截屏全程只在内存中流转,从不落盘;即使用户"保存选区",写入相册的也只是用户圈定的裁剪区域(见 ImageUtils.kt 的cropBitmap)。整体架构可参考官方文档 docs/image_search.md。

多条触发路径:一次截屏逻辑,多种入口

同一个performCapture()被多个入口复用,用户按习惯选择即可:

  • ⚡快速设置磁贴:下拉通知栏点击 CTS 磁贴(CircleToSearchTileService.kt)
  • 🗣️设为默认助手:长按 Home 键触发,此时系统直接回调onHandleScreenshot(Bitmap),截屏由系统完成,AssistSessionService 只需存入 Repository 并拉起 Overlay
  • 🔘状态栏双击 / 悬浮气泡:由无障碍服务自行调用takeScreenshot

多条路径最终都收敛到BitmapRepository → OverlayActivity这一条主干,新增触发方式时几乎不用改动图像管线。

踩坑清单:4 个最容易翻车的地方

  1. 忘记hardwareBuffer.close()—— 显存泄漏,建议放在 finally 或两条分支都显式调用;
  2. 直接在硬件 Bitmap 上裁剪——wrapHardwareBuffer结果不可写、不可部分裁剪,务必先copy(ARGB_8888, false);
  3. 线程假设错误——TakeScreenshotCallback在 Executor 上回调,不要默认它在主线程;
  4. 忽略onFailure—— 系统繁忙、多屏等场景可能失败,至少记录errorCode并给用户提示。

总结

CircleToSearch 用不到 50 行代码演示了 takeScreenshot API 的完整范式:声明canTakeScreenshot→ 回调取 HardwareBuffer →wrapHardwareBuffer包装 → 拷贝为 ARGB_8888 → 内存单例接力 → 用完即清。它证明了在 Android 上,"无权限、无打断"的截屏能力并不需要任何灰色手段——只要你的应用场景符合无障碍服务的定位,这是官方给的正经通道。如果你想在自己的无障碍类应用(屏幕翻译、内容提取、辅助搜索等)中实现类似能力,这套链路可以直接照搬。

【免费下载链接】CircleToSearch

CircleToSearch with multi search engine support for all android

项目地址:https://gitcode.com/gh_mirrors/ci/CircleToSearch
点击查看免费下载
上一篇:免费开源的GTA5线上小助手实测:被NPC教做人之后,我用十分钟接管了洛圣都
下一篇:Parler-TTS Mini使用指南:用描述词精准控制语音性别、音高、语速与背景噪声

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询