Android 14 的截屏检测 API,能做什么不能做什么

Android 14 的截屏检测 API,能做什么不能做什么

Android 14 的截屏检测 API,能做什么不能做什么


Android 14 的截屏检测 API,能做什么不能做什么


Android 14 正式引入 DETECT_SCREEN_CAPTURE 权限和 ScreenCaptureCallback 的时候,我第一反应是:Google 终于把这个坑填了。之前做金融类 App,检测截屏全靠各种 hack,读 MediaStore 轮询、监听文件系统变化、甚至用 AccessibilityService 去猜,没有一个靠谱的。所以 UDC 2023 上看到这个 API 的预告,我立刻找了个 Pixel 7 刷上 Beta 测试。


实际用下来,这个 API 的设计比我想象的要克制,限制也比我预期的多。有些场景它能完美覆盖,有些场景它根本碰不到,还有些行为在不同厂商 ROM 上的表现让人很头疼。这篇文章把我这大半年的测试和踩坑记录整理一下,主要基于 Android 14 (API 34) 和 Android 15 (API 35) 的正式版本,测试设备包括 Pixel 7/8、三星 Galaxy S24 Ultra、小米 14 Pro 和 OPPO Find X7。


注册回调的 API 设计


核心接口很简单,在 ActivityFragment 上调用:


val callback = object : ScreenCaptureCallback {
    override fun onScreenCaptured() {
        // 用户刚刚截了图
    }
}

activity.registerScreenCaptureCallback(mainExecutor, callback)

注销也对应:


activity.unregisterScreenCaptureCallback(callback)

几个关键点从文档里容易忽略,但实际测试很重要:


第一,这个回调必须在 ActivityonStart() 之后、onStop() 之前注册。如果你在 onCreate() 里提前注册,直接抛 IllegalStateException。这个设计意味着它绑定的是可见生命周期,而不是整个 Activity 存在周期。我最初在 BaseActivity 的 onCreate 里统一注册,结果崩溃了一堆,日志很清晰:


java.lang.IllegalStateException: Must call registerScreenCaptureCallback() after onStart()

第二,executor 参数必须是 mainExecutor 吗?文档说 "typically the main executor",实际测试发现后台 executor 也能工作,回调会post到那个线程执行。但这里有个坑:如果你在子线程 executor 里做 UI 操作,需要自己切回来。更隐蔽的问题是,某些厂商(三星 One UI 6.1)似乎对非主线程的回调有延迟,我测到过 200-500ms 的滞后,而 Pixel 上基本是即时触发。这个差异在做实时遮罩覆盖时会很明显。


第三,回调是 Activity 级别的,不是应用级别的。这意味着如果你在一个多 Activity 的 App 里,每个 Activity 都要单独注册。我尝试在 Application.ActivityLifecycleCallbacks 里统一代理,技术上可行,但要注意 Activity 之间的过渡动画期间,旧 Activity 的 onStop 和新 Activity 的 onStart 有重叠,快速切换时可能出现漏注册或重复注册。


什么情况下会触发


这是最需要实测的部分,因为文档只写了 "when the user performs a screen capture",但 "screen capture" 的边界很模糊。


确定会触发的情况:


  • 物理按键截屏(电源+音量下)。所有测试设备都触发,包括三星、小米、OPPO。
  • 三指下滑截屏(小米、OPPO 的默认手势)。
  • 通知栏快捷开关的截屏按钮。
  • 辅助功能里的 "执行手势" 模拟按键截屏(我用来写自动化测试)。

  • 不会触发的情况:


  • 录屏。对,录屏不会触发 ScreenCaptureCallback。Android 的屏幕录制走的是 MediaProjection API,系统不把它视为 "capture"。这个区分在文档里没明说,但实测如此。对于金融 App 来说,这其实是个大问题——用户录屏泄露敏感信息的风险,不比截屏低。
  • 其他应用通过 MediaProjection 请求投屏/录屏。比如用 scrcpy 有线投屏,整个过程中不会触发任何回调。
  • 智能助理的长截图(三星的 Scroll Capture、小米的带壳截图等扩展功能)。这里要分开说:三星的 Scroll Capture 在保存最终长图时会触发一次回调,但滚动拼接的过程不触发;小米的带壳截图在保存时触发,但"分享"快捷按钮弹出的瞬间不触发。
  • 开发者选项里的 "Bug 报告" 或 "Take bug report",这个不走常规截屏通道。

  • 模糊地带:


  • 智能选择(Pixel 的 App Actions)里的 "截图" 操作。Pixel 8 上测试,从最近任务长按进入智能选择,选 "Screenshot" 会触发回调,但延迟明显,大约 1-2 秒。
  • 折叠屏的分屏/拖拽截图。Galaxy Z Fold 5 上,把应用拖到边缘触发截图分享,这个会触发回调,但回调到达时截图文件已经生成完毕,留给应用做反应的时间窗口很小。

  • 权限模型和系统行为


    Android 14 新增权限 `android.permission.DETECT_SCREEN_CAPTURE`, protectionLevel 是 `normal instant`。这意味着不需要运行时申请,manifest 里声明即可,对 instant app 也开放。

    <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />

    但这里有个版本兼容的坑。这个权限在 Android 14 以下不存在,如果你用 targetSdkVersion 34 编译,minSdk 设到 28,直接编译会报错:


    Error: Permission DETECT_SCREEN_CAPTURE is only available on API 34 and above.

    解决方式是用 uses-permission-sdk-23 标签,或者更常见的做法是在运行时判断:


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
        activity.registerScreenCaptureCallback(...)
    }

    我倾向于后者,因为 registerScreenCaptureCallback 方法本身就是 API 34 才加入的,权限声明和代码调用要同步保护。


    系统对回调的响应有明确限制。Google 在 UDC 的 session 里提到,这个 API 的设计意图是 "give apps a chance to react",而不是 "prevent capture"。所以:


  • 你不能在回调里阻止截图文件生成,文件已经写在磁盘上了。
  • 你不能知道截图保存的具体路径(不像以前读 MediaStore 可以拿到)。
  • 回调没有返回值,系统不等待你的处理。

  • 这个设计哲学和 iOS 的 UIApplication.userDidTakeScreenshotNotification 类似,都是通知性质,不是拦截性质。但 iOS 的通知里至少带了截图的 UIImage 对象,Android 这边完全是空回调,信息量少得多。


    实际能做什么:响应策略


    既然阻止不了,那能做什么?我整理了几种实际可行的策略,以及它们的局限。


    即时遮罩覆盖


    最常见的需求,检测到截屏后立刻在屏幕上覆盖一层水印或黑屏。实现上要注意时序:


    override fun onScreenCaptured() {
        val overlay = View(this).apply {
            setBackgroundColor(Color.BLACK)
        }
        window.addContentView(overlay, 
            ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
        
        // 3秒后移除,或者等用户交互
        overlay.postDelayed({ (overlay.parent as ViewGroup).removeView(overlay) }, 3000)
    }

    Pixel 上从按键到回调大约 50-100ms,覆盖基本能赶上截图内容显示。但三星 S24 Ultra 上我测到过 300ms+ 的延迟,截图里已经能看到部分敏感内容,遮罩覆盖上去时截图早就生成了。这个延迟差异没有文档说明,可能和三星的截屏处理流程(保存、压缩、生成缩略图)有关。


    更麻烦的是,用户可能连续快速截屏。每次截屏都会触发回调,如果你每次都加遮罩,界面会闪烁得很厉害。我最后加了 500ms 的 debounce,但这也导致连续截图的第二次可能漏掉。


    本地日志记录


    对于企业合规场景,记录截屏行为本身就有价值。但要注意隐私合规,这个日志不能上传用户截图内容,只能记时间戳和上下文。


    override fun onScreenCaptured() {
        SecurityAudit.log(ScreenCaptureEvent(
            timestamp = System.currentTimeMillis(),
            activityClass = this::class.java.name,
            // 不要记屏幕内容!
        ))
    }

    这里有个坑:如果用户在设置里开启了 "使用 App 打开截图编辑"(比如三星的 Smart Capture 会立即打开编辑界面),你的 Activity 可能已经进入 onPause,但回调还在触发。我遇到过 onScreenCaptured 执行时 isFinishing 为 true 的情况,这时候写日志或弹窗都要检查生命周期状态。


    配合 FLAG_SECURE 使用


    WindowManager.LayoutParams.FLAG_SECURE 是老牌方案,能阻止系统截屏和录屏,但副作用很大:用户正常截屏会提示 "无法截取屏幕截图",体验很差。而且 FLAG_SECURE 不能动态开关,一旦设置就全程生效。


    我的实际做法是把两者结合:默认不设 FLAG_SECURE,检测到截屏敏感页面(比如银行卡号展示)时动态添加,同时注册截屏回调。但 FLAG_SECURE 的动态添加有个问题:已经存在的 Surface 需要重新创建,某些自定义 View(SurfaceView、TextureView)会闪一下。


    // 进入敏感页面
    window.addFlags(LayoutParams.FLAG_SECURE)
    
    // 同时注册截屏检测,用于记录"用户尝试截屏"的行为
    registerScreenCaptureCallback(...)

    这个组合在 Pixel 上工作正常,但小米 14 Pro 上遇到过一个很诡异的 bug:同时存在 FLAG_SECURE 和截屏回调时,三指下滑截屏会触发回调,但系统提示 "无法截屏"(因为 FLAG_SECURE),然后通知栏里却出现了截图缩略图,点进去是黑屏。这个状态不一致的问题我提了 issue 到小米社区,没有回应。


    不能做什么:明确的能力边界


    阻止特定截屏


    做不到。回调发生时截图文件已经生成,路径在 /Pictures/Screenshots/ 下(各厂商路径略有不同)。没有任何 API 能删除这个文件,除非你有 MANAGE_EXTERNAL_STORAGE 这种特殊权限,但 Google Play 对这种权限的审核很严格,金融 App 也很难拿到。


    我测试过在回调里用 MediaStore 查询最近文件然后删除,技术上可行,但:


    1. 需要 WRITE_EXTERNAL_STORAGEMANAGE_EXTERNAL_STORAGE

    2. 删除时机和系统生成缩略图的时机竞争,经常删了原图但缩略图还在

    3. 这种行为在 Google Play 政策上属于 "干扰用户设备正常功能",有下架风险


    检测录屏


    前面说过,录屏不走截屏通道。但有一个间接检测方式:监听 MediaProjection 的启动。Android 10 开始,MediaProjection 需要前台服务,系统会显示一个持续通知。你可以用 NotificationListenerService 检测这个通知,但:


  • 需要用户开启通知读取权限
  • 不同厂商的录屏通知标题不一样(三星是 "Screen recorder",小米是 "屏幕录制")
  • 用户可能关闭录屏通知
  • 这个检测有延迟,录屏已经开始几秒后才捕获到

  • 我见过的最激进的方案是轮询检查 /proc/[pid]/fd/ 里的 socket 连接,试图发现 MediaProjection 的 Surface 连接,但这需要 root,且版本兼容性极差。


    检测特定应用的截屏


    做不到。系统不告诉你截图是用什么工具截的,是系统自带、第三方 App、还是 adb shell。以前通过文件路径或 MediaStore 的 DATA 列能推测一点,但 Android 10 的 Scoped Storage 之后这条路也堵死了。


    跨进程/跨应用的检测


    ScreenCaptureCallback 是进程内的,绑定到 Activity。如果你的应用有多个进程(比如主进程 + 推送进程),或者用了 android:process 分离的组件,每个进程的 Activity 都要单独注册。更关键的是,如果你的应用被系统杀死,然后用户从最近任务截图,这时候你的进程可能还没启动,根本收不到回调。


    我测试过一个场景:应用处于停止状态(force stop 后),用户从最近任务预览图长按进入截图(Pixel 的功能),这时候系统会触发截图,但应用进程不会启动,回调自然不存在。这个场景对于"后台保活检测截屏"的需求来说是死路。


    厂商差异和实际坑点


    这是最让人头疼的部分。AOSP 的 API 定义很清晰,但各厂商的实现差异很大。


    三星 One UI 6.1


  • Scroll Capture(长截图)的触发时机:在最终保存时触发一次,不是滚动过程中每次拼接都触发
  • 截屏后弹出的 Smart Capture 工具栏(带编辑、分享按钮)出现时,你的 Activity 会收到 onPause,但回调可能在这之后到达
  • 折叠屏的 Flex Mode 下,如果应用处于多窗口状态,截屏回调有时不触发,疑似 bug

  • 小米 HyperOS 1.0


  • 三指下滑截屏的触发非常灵敏,但回调延迟比 Pixel 高 100-200ms
  • "带壳截图"功能在生成带壳图片时会触发两次回调:一次是原始截图,一次是带壳后的最终图
  • 游戏工具的 "快捷截屏"(Game Turbo 里的悬浮球)不触发回调,这个走的是小米自己的截屏通道,绕过了系统 API

  • OPPO ColorOS 14


  • 侧边栏的 "截屏" 功能触发回调
  • 三指长按区域截屏触发回调,但回调到达时区域选择已经完成,遮罩覆盖意义不大
  • 隐私替身(Privacy替身)功能开启时,截屏检测行为不稳定,我遇到过回调丢失

  • Pixel 的原生行为


    最符合文档描述,但也有一些细节:

  • 截图后通知栏的预览图弹出时,回调已经执行完毕
  • 如果截图后立即删除(从通知栏左滑删除),没有额外的回调
  • 截图编辑界面(Markup)打开时,原 Activity 的回调不会再次触发

  • 和旧方案的对比与迁移


    在 Android 14 之前,检测截屏主要有几种方案:


    MediaStore 监听


    注册 ContentObserver 监听 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,过滤 DISPLAY_NAME 包含 "Screenshot" 的文件。这是过去最主流的方案,但问题很多:


  • 需要 READ_EXTERNAL_STORAGE 权限
  • Scoped Storage 之后,只能读到自己创建的文件,除非用 MediaStore 查询但拿不到路径
  • 延迟高,文件写入完成到 MediaStore 索引更新有 gap
  • 各厂商截图文件名格式不同("Screenshot_20240101_120000" vs "截屏_2024-01-01-12-00-00")

  • 我维护的代码里,MediaStore 方案要维护一个巨大的文件名正则表,还要处理时区、语言、厂商定制。


    FileObserver/ inotify


    监听 /sdcard/Pictures/Screenshots/ 目录变化。需要存储权限,且 Android 10 的 Scoped Storage 之后对外部目录的访问受限。更致命的是,很多厂商把截图目录改到了私有路径,比如三星的 /DCIM/Screenshots/、小米的 /MIUI/Gallery/cloud/.cache/


    AccessibilityService


    监听窗口变化,猜测截屏行为。这个方案权限过重,Google Play 对 AccessibilityService 的使用审核越来越严,且容易被用户拒绝。


    迁移到 ScreenCaptureCallback 之后,我的建议是保留旧方案作为 fallback,但用新 API 做主要检测:


    fun Activity.registerScreenCaptureDetection(callback: () -> Unit) {
        if (Build.VERSION.SDK_INT >= 34) {
            val screenCaptureCallback = object : ScreenCaptureCallback {
                override fun onScreenCaptured() = callback()
            }
            registerScreenCaptureCallback(mainExecutor, screenCaptureCallback)
            // 需要保存引用以便 unregister
        } else {
            // fallback 到 MediaStore 监听
            legacyScreenshotDetector.register(this, callback)
        }
    }

    但这里有个产品决策:fallback 方案在 Android 14+ 还要不要保留?我的实际做法是保留,因为新 API 有覆盖不到的场景(比如前面说的进程未启动时),但降低优先级,避免重复触发。


    性能影响和稳定性


    注册 ScreenCaptureCallback 本身几乎没有性能开销。我在 Pixel 8 上做了简单测试,注册 1000 次再注销,总耗时 < 10ms,内存占用增加可以忽略。系统服务端的实现是 WindowManagerService 里维护一个 RemoteCallbackList,截屏事件发生时遍历通知,这个架构本身很轻量。


    但回调触发时的处理要小心。如果你在 onScreenCaptured 里做耗时操作(比如网络请求、大量日志写入),会阻塞后续截屏检测。系统对回调执行时间没有明确限制,但 ANR 规则仍然适用——如果你在主线程 executor 里卡太久,应用会 ANR。


    更隐蔽的问题是,连续快速截屏时的行为。我测试过以 200ms 间隔连续按截屏键,Pixel 上每次都能触发回调,但回调的执行顺序和截屏顺序一致,没有丢失。三星 S24 Ultra 上,快速截屏时出现过回调合并,两次截屏只触发一次回调,这个可能是厂商优化导致的,没有文档说明。


    内存泄漏方面,ScreenCaptureCallback 是接口,不是 Activity 的内部类的话不会隐式持有外部引用。但如果你用匿名内部类写法,或者把 callback 存成了 Activity 的字段,要注意在 onStop 时注销。我遇到过的一个 leak 场景:用 ViewModel 持有 callback 引用,但 ViewModel 生命周期比 Activity 长,导致 Activity 泄漏。


    一个完整的集成示例


    把我实际项目里的核心逻辑抽出来,去掉业务细节:


    class SecureActivity : AppCompatActivity() {
        
        private var screenCaptureCallback: ScreenCaptureCallback? = null
        private var lastCaptureTime = 0L
        
        override fun onStart() {
            super.onStart()
            if (Build.VERSION.SDK_INT >= 34) {
                screenCaptureCallback = object : ScreenCaptureCallback {
                    override fun onScreenCaptured() {
                        val now = SystemClock.elapsedRealtime()
                        if (now - lastCaptureTime < 500) return // debounce
                        lastCaptureTime = now
                        
                        onScreenshotDetected()
                    }
                }
                registerScreenCaptureCallback(mainExecutor, screenCaptureCallback!!)
            }
        }
        
        override fun onStop() {
            if (Build.VERSION.SDK_INT >= 34) {
                screenCaptureCallback?.let { unregisterScreenCaptureCallback(it) }
                screenCaptureCallback = null
            }
            super.onStop()
        }
        
        private fun onScreenshotDetected() {
            // 业务处理:遮罩、日志、提示等
            if (isFinishing         
            // 示例:短暂覆盖黑屏
            val overlay = FrameLayout(this)
            window.addContentView(overlay, 
                ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
            overlay.setBackgroundColor(Color.BLACK)
            
            overlay.postDelayed({
                if (!isFinishing && !isDestroyed) {
                    (overlay.parent as? ViewGroup)?.removeView(overlay)
                }
            }, 2000)
        }
    }

    几个防御性编程的点:


  • isFinishing / isDestroyed 检查,因为回调可能在生命周期边缘触发
  • debounce 避免连续截屏的闪烁
  • overlay.parent 的可空检查,因为延迟任务执行时 Activity 可能已销毁
  • 注销时先设 screenCaptureCallback = null,再调 super.onStop()

  • 对 Android 15 的观察


    Android 15 (API 35) 目前正式版已发布,截屏检测 API 没有大变化,但有几个值得注意的行为调整:


  • DETECT_SCREEN_CAPTURE 权限的 protectionLevel 没变,但文档增加了说明:如果应用同时声明了 RECORD_AUDIOCAPTURE_VIDEO_OUTPUT,系统可能会限制截屏检测的精度。这个关联很隐晦,我猜测是为了防止恶意应用用截屏检测来反制录屏取证。
  • 折叠屏设备上的多窗口场景,Android 15 增加了 Activity#isInMultiWindowMode 和截屏回调的联合判断建议,但 API 本身没新增。
  • 隐私仪表盘(Privacy Dashboard)里,用户可以看到哪些应用注册了截屏检测,这个在 Android 14 上就有,但 Android 15 里入口更明显。

  • 我试过一个边缘场景:在 Android 15 的预测性返回手势(Predictive Back)过程中截屏。预测性返回会显示上一个 Activity 的预览,这时候截屏,回调注册在当前 Activity 还是上一个?实测是两者都触发,因为系统把预测性返回的预览视为独立的 Surface,截屏操作同时捕获了多个 Surface。这个行为可能导致重复检测,需要应用层去重。


    最后说几句


    ScreenCaptureCallback 是个"终于来了"的 API,但它不是银弹。Google 的设计意图很明确:让应用知道截屏发生了,给应用一个反应机会,但不给阻止能力。这个边界在隐私保护和应用需求之间做了权衡,但坦白说,对金融、医疗等强合规场景来说,这个 API 单独使用是不够的。


    我的实际项目里,最终的方案是组合拳:Android 14+ 用 ScreenCaptureCallback 做即时响应,配合 FLAG_SECURE 做关键页面保护,同时保留轻量级的 MediaStore 监听做兜底和跨进程检测。录屏检测仍然是个难题,目前主要靠 MediaProjection 的前台服务通知间接判断,或者干脆在服务端做行为分析(比如短时间内大量图片上传)。


    这个 API 最大的价值,是把以前各种 hack 方案统一到了一个标准接口上,可靠性比 MediaStore 监听高很多,也不需要额外权限。但要用好它,必须清楚它的能力边界,不要期待它能解决所有屏幕内容保护的问题。Android 的屏幕内容安全是个系统工程,从 SurfaceFlinger 的 layer 隔离、到 FLAG_SECURE 的硬件合成保护、再到这个应用层的检测回调,每一层都有它的位置和局限。

    Android 的增量编译,到底省了多少时间 2026-07-17
    Charles 和 Proxyman 的 HTTPS 解密配置教程 2026-07-17

    评论区