我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

Android FLAG_SECURE:阻止截屏与录屏

介绍 Android 的 FLAG_SECURE 如何阻止截屏、录屏和最近应用预览,并提供代码示例、使用场景、局限性以及投屏时的行为。

时不时地,您会碰到一个让人感觉有点像魔法的 Android 标志。FLAG_SECURE 就是其中之一。一旦开启,您的应用突然就变得“无法截屏”:常用的组合键不再起作用,录屏莫名其妙地变成一片黑,您的 UI 也从最近应用预览中消失了。它看起来像是某种硬核的 DRM 开关。

其实并不是。从底层来看,FLAG_SECURE 实际上非常简单:它是告诉 Android“这个特定的窗口是敏感的,不要让它的像素被捕获”的一种方式。仅此而已。没有加密,没有秘密握手,只是向系统发出一个强烈的提示,告诉它当有人试图抓取屏幕内容时应如何对待这个窗口。

在本文中,我们将逐一介绍 FLAG_SECURE 在实践中的作用、它在概念上如何工作、如何在代码中使用它、在哪些地方开启它是合理的、一些不会惹恼用户的使用模式,以及您绝对不应忽视的局限性。

1. FLAG_SECURE 实际上做了什么

我们先从启用这个标志后人们实际看到的现象说起,因为问题通常就是从这里开始的。

当您在某个窗口上(例如在一个 Activity 中)设置 FLAG_SECURE 时,Android 就不再把该窗口当作可以正常捕获的普通 UI,而是将其标记为对任何想要获取像素的东西都“禁止触碰”。

您会注意到的第一个效果是截屏被阻止。当您的安全窗口位于前台时,常用的系统快捷键(例如电源键 + 音量减键)要么拒绝截屏,要么生成一张全黑或空白的图片。从用户的角度看,就像是设备无法对该屏幕截屏。而在底层,Android 只是在忠实地遵守规则:该窗口被标记为安全窗口,因此像素不会外流。

被关闭的不只是系统自带的截屏功能。大多数第三方截屏应用也面临同样的处境。它们同样依赖系统级机制来捕获屏幕内容,而当它们请求属于安全窗口的像素时,平台交给它们的是一片空无。结果通常也是一样的:一张黑色或空白的图片,或者干脆失败。但请记住,用另一台相机对着屏幕拍照仍然是可行的。

再来看录屏,情况也类似。录屏工具通常会在显示安全窗口的位置显示一块黑色区域,而不是您应用的内容。您仍然会得到一段视频,但凡是安全窗口出现在画面中的地方,都只是一个黑色矩形。有些录屏工具可能仍会在边缘显示系统 UI 或其他非安全窗口,但您的窗口会保持隐藏。这就像是在录像中恰好在敏感内容所在的位置挖了一个洞。

然后是最近应用 / 概览屏幕,也就是您上滑或点击“最近”按钮时看到的界面。通常,Android 会对您应用的 UI 拍一张快照,用作小卡片预览。当 FLAG_SECURE 生效时,这张快照会被刻意设置为无用。在系统的概览 / 最近应用屏幕中,Android 不会显示您安全窗口的图像。它通常显示为一个纯色或黑色矩形,从而防止敏感信息在应用处于后台时,或在有人从您身后偷看您翻阅已打开的应用时被看到。

而这一切的作用范围都是经过精心限定的。FLAG_SECURE 不会锁定整台设备,也不会影响所有应用。它只作用于设置了该标志的特定窗口(Activity、Dialog 等)。如果您没有在另一个 Activity 上设置该标志,那么那个屏幕的行为就和平常一样:截屏正常,录屏正常,最近应用预览也会显示 UI 的样子。

简而言之,FLAG_SECURE 保护的是用户(或其他应用)能够通过 Android 自身的显示管线从您的应用中以视觉方式捕获的内容,但它是按窗口逐一生效的,而不是一个全局的“隐私模式”。

智能手机屏幕特写,显示因安全策略而弹出的“Screenshot Blocked”提示,说明在 Android 开发中启用 FLAG_SECURE 窗口标志以阻止屏幕捕获的效果
Android FLAG_SECURE 示例:截屏被阻止的提示

2. FLAG_SECURE 的底层工作原理

既然我们已经介绍了可见的行为,下面来谈谈在概念上实际发生了什么。

理解这个标志最简单的方式,是把它看作窗口上的一个隐私开关:

  • 开关打开 → Android 被告知:“绝不要让这个窗口的像素以可被捕获的形式离开安全显示管线。”
  • 开关关闭 → 该窗口的行为与普通窗口相同;允许截屏和录屏。

在底层,当某个窗口设置了 FLAG_SECURE 时,Android 的合成器会记录该窗口是“安全的”这一事实。每当系统被要求捕获屏幕时——无论是通过截屏 API、录屏、生成最近任务缩略图,还是在某些投屏/镜像场景中——它都必须用所有可见窗口来构建一张图像。对于非安全窗口,它会顺利地把它们的像素复制到捕获结果中。对于确实安全的窗口,任何捕获屏幕的尝试都会要么将安全窗口排除在外,要么用一个黑色/空白的占位区域替换它。

关键在于,这是由系统强制执行的,而不是由您的应用逻辑来执行。您不必拦截截屏事件,也不必去猜测录屏工具何时在运行。一旦设置了该标志,Android 本身就负责确保该窗口的像素不会泄露到截屏、录屏或缩略图中。其他试图耍小聪明、使用官方 API 捕获显示内容的应用同样受这些规则约束:它们请求像素,合成器应用安全策略,您的内容就不会出现。

这也是为什么 FLAG_SECURE 对于通过软件捕获您 UI 的随意尝试、甚至是有一定决心的尝试,都具有相当的鲁棒性。但务必牢记它不覆盖哪些内容:它只控制通过 Android 渲染管线进行的视觉捕获。它不会神奇地保护您存储中的数据、网络上的数据或任何其他地方的数据。

3. 如何在代码中使用 FLAG_SECURE

实现部分很简单。没有什么秘密 API,也不需要特殊权限——只是窗口上的一个标志。

您通常会在 Activity 的 onCreate 中启用它,时间点在调用 setContentView 之前或前后。在 Java 中,写法如下:

在 Java 中使用 FLAG_SECURE:

import android.os.Bundle;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;

public class SecureActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_SECURE,
            WindowManager.LayoutParams.FLAG_SECURE
        );

        setContentView(R.layout.activity_secure);
    }
}

在 Kotlin 中使用 FLAG_SECURE:

import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity

class SecureActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        window.setFlags(
            WindowManager.LayoutParams.FLAG_SECURE,
            WindowManager.LayoutParams.FLAG_SECURE
        )

        setContentView(R.layout.activity_secure)
    }
}

只需这些,就能让该 Activity 的窗口在其存续期间一直保持安全。

不过,有时您并不希望窗口始终处于安全状态。也许屏幕的一部分是敏感的、另一部分不是,又或者存在某种您希望允许截屏的特定模式。在这些情况下,您可以在不再需要时于运行时移除 FLAG_SECURE:

在 Kotlin 中于运行时在不再需要时移除 FLAG_SECURE:

window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)

在 Java 中于运行时在不再需要时移除 FLAG_SECURE:

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);

这里的一个关键细节是,FLAG_SECURE 是按窗口生效的,而不是按应用生效的。该常量是:

WindowManager.LayoutParams.FLAG_SECURE

没有任何清单(manifest)设置可以将其自动应用到整个应用。您必须逐个窗口设置(或清除)它:

  • 每个您想要保护的 Activity。
  • 每个应当处于安全状态的 Dialog 或自定义窗口。

这既是一项特性,也是一个容易让人自食其果的陷阱:您获得了细粒度的控制,但也必须确保每一个敏感界面都被覆盖到。

4. FLAG_SECURE 的常见使用场景

那么,在现实世界中,哪些地方适合使用它?简短的回答是:任何显示您确实不希望随随便便出现在某人的截图存档、云备份或聊天记录中的信息的地方。

我们来逐一看看一些常见类别。

4.1 银行和金融应用

银行和金融应用是这方面最典型的代表。显示账户余额、银行对账单、交易历史、卡号或一次性密码(OTP)的屏幕,充满了既敏感又一旦落入不法之手就极易被重复利用的数据。如果这些界面允许随意截屏,这些信息就可能出现在用户的相册中、被同步到云存储,或在即时通讯应用中被转发,而且通常没有任何加密或访问控制。

通过在这些金融屏幕上启用 FLAG_SECURE,您就在这条泄露路径前设置了一道基本的减速带。它无法阻止执意用另一台设备拍摄屏幕的人,但确实能防止很多随手的“我就快速截个图吧”的时刻,而这些时刻日后往往会给人们带来麻烦。

一款金融科技应用的示意图,显示高度敏感的个人身份信息(PII)和金融资产,代表一个需要强有力防护以防止数据泄露和未授权访问的关键攻击面。

4.2 身份验证和安全类应用

如果您正在构建任何与身份验证或机密信息相关的东西——密码管理器、2FA / OTP 生成器、身份核验流程——那么 FLAG_SECURE 应当牢牢在您的考虑范围之内。

这些应用经常显示高价值的机密信息:密码、恢复密钥、备用验证码、短时效的登录验证码等等。如果用户可以对这些屏幕截屏,它们往往最终会被存放在您最不希望它们出现的地方:相机胶卷、云相册以及各种笔记应用。从那里开始,它们进一步泄露并不需要费多大力气。

在应用中显示这些机密信息的部分应用 FLAG_SECURE,意味着它们无法通过操作系统被轻易捕获。用户如果想保存它们,就必须采取更有意识的步骤——比如把它们写下来或使用另一台相机。这并不能解决所有问题,但能大幅减少在处处允许截屏时出现的意外暴露。

一款多因素身份验证(MFA)工具生成基于时间的一次性密码(TOTP)和安全令牌的示意图,展示了对必须防范视觉窃取手段的关键机密信息的处理。

4.3 健康和医疗应用

健康和医疗应用属于一个特殊类别:其中的数据极其私密,而且在许多司法管辖区都受到严格监管。

显示个人健康记录、化验结果、诊断、治疗详情或详细问诊记录的屏幕,并不只是“有点隐私”;它们往往受到严格的隐私法律约束。如果这些界面可以被截屏或录制成视频,随后又被自动备份或分享,您就为极其敏感的数据脱离应用的控制打开了一条便捷的通道。

在这些界面上使用 FLAG_SECURE 有助于降低这种风险。它不能取代适当的加密、访问控制和合规工作,但它解决了一个非常具体的问题:人们随手为自己的检查结果截个图,却在不知不觉中将这些图片推送到了从未为医疗隐私而设计的环境中。

一款医疗健康移动应用的示意图,显示电子健康记录(EHR)和受保护的健康信息(PHI),展示了一个受严格监管合规要求(如 HIPAA)约束、需要强有力控制以防止数据暴露的界面。

4.4 企业和内部工具

在组织内部,您经常会看到内部控制台、管理面板和调试工具,它们暴露了各种没人希望出现在 Twitter 上的信息。想想看:内部指标、客户数据、事件时间线、配置界面,或者夹杂着标识符和机密信息的日志视图。

在这些环境中,员工截屏并在内部分享可能完全正常——直到其中某张截图泄露到组织外部,或出现在它绝对不该出现的地方。针对生产系统使用的调试工具尤其危险,因为它们经常显示从未打算面向客户的原始数据。

在特别敏感的管理界面和内部工具上启用 FLAG_SECURE,是限制此类行为影响范围的一种务实方法。人们仍然可以记录自己在做什么,但您不再让他们能够毫不费力地捕获并传播您最敏感的内部 UI 的像素级精确副本。

一个内部管理控制台的示意图,暴露了运营情报、系统告警和用户日志,突显了一个经常被忽视、需要特权访问管理和数据脱敏的攻击向量。

4.5 隐私关键型通信与自助终端场景

还有一整类应用,其敏感性更多在于人们如何使用它们,而不在于具体的领域。安全通讯、阅后即焚聊天和自毁消息就是很好的例子。

如果您宣传“这条消息会消失”,但 UI 却可以被轻易截屏,那么体验与预期就是不一致的。您无法阻止有人拿另一部手机对着屏幕拍照,但至少可以避免为简便的内置捕获途径开绿灯。用 FLAG_SECURE 标记这些特殊的对话,传达了一个相当明确的信号:这些内容并不打算永远留在您的相册里。

自助终端(kiosk)类应用是另一个有趣的例子。想想签到终端、排队系统、自助填表设备,或任何人们在半公开场所走过去就能使用的共享设备。这些屏幕往往会短暂显示姓名、证件号码、预订详情或其他个人数据。在这些流程中启用 FLAG_SECURE,可以让投机取巧的用户——或运行在设备上的恶意软件——更难悄悄窃取屏幕上显示的内容。

一个安全通讯平台的示意图,包含阅后即焚数据(消失的消息)和安全传输指示,背景为淡红色,以强调对截屏防护等反取证安全策略的主动执行。
在所有这些类别中,应当遵循的原则是:在屏幕内容的敏感程度明显超过截屏便利性的地方使用 FLAG_SECURE。在其他地方,开启它之前请三思。

5. 设计模式:在何处、何时启用

在应用里随意撒一些 FLAG_SECURE 并不是一种策略。您需要的是在安全性和可用性之间取得平衡的、经过深思熟虑的模式。

一种简单的模式是“始终安全”的 Activity。对于某些屏幕,它们所显示的一切都是敏感的。典型的例子是类似 OtpVerificationActivity 或 CardDetailsActivity 这样的屏幕。在这些屏幕中,您只需在 onCreate 中设置 FLAG_SECURE,并且永远不清除它。只要用户进入该 Activity,截屏和录屏就完全不可能,没有例外。

一种更细致的模式是基于状态的切换。设想一个单独的 MainActivity,它通常显示无害的内容,但有时会展开一个“敏感详情”面板——例如一个显示完整账号的底部面板。当该面板隐藏时,没有理由阻止截屏。一旦显示它,您就打开该标志;当它被关闭时,您再关掉该标志。在 Kotlin 中,写法可能如下:

fun showSensitiveContent() {
    window.setFlags(
        WindowManager.LayoutParams.FLAG_SECURE,
        WindowManager.LayoutParams.FLAG_SECURE
    )
    // Show a fragment or view containing sensitive data
}

fun hideSensitiveContent() {
    window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
    // Hide that fragment or view
}

这种模式让您两全其美:人们仍然可以对通用 UI 截屏,而真正敏感的时刻则受到保护。

然后是仅在对话框中保护安全内容的方法。有时,整个流程中唯一敏感的部分是一个临时对话框——比如一个显示完整卡号、恢复短语或一次性机密信息的弹窗。在这些情况下,在对话框自己的窗口上设置 FLAG_SECURE,而不是在整个 Activity 上设置,可能是合理的。底层屏幕仍然可以截屏,但对话框的内容永远不会出现在捕获结果中。

无论您选择哪种模式,关键都在于保持一致:定义哪些屏幕或状态被视为敏感,在那里应用 FLAG_SECURE,其他地方则保持不变。这样,您就不会让用户莫名其妙地感到意外,而且能把这个标志精准地用在最能降低风险的地方。

6. 局限性与常见误解

接下来是与功能同样重要的部分:FLAG_SECURE 不做什么。

最重要的一点:它无法阻止相机或外部设备。用户仍然可以拿另一部手机、单反相机、网络摄像头或任何他们喜欢的设备对着屏幕进行拍摄。FLAG_SECURE 只控制 Android 内部基于软件的捕获。如果您的威胁模型包括有人亲自站在那里拍摄设备,那么这个标志帮不了您。

其次,FLAG_SECURE 不会加密或以其他方式保护您的数据。它保护的是视觉显示,而不是:

  • 网络通信
  • 磁盘上的存储
  • 日志、缓存或备份

如果您通过网络发送敏感数据,您仍然需要 HTTPS(并且仍然需要正确校验证书)。如果您在磁盘上存储机密信息,您仍然需要加密数据库或 EncryptedSharedPreferences 等加密机制。如果您在日志中记录敏感值,当这些日志被发送到中央服务器时,FLAG_SECURE 也救不了您。

还有作用范围问题。由于该标志是按窗口而非全局生效的,很容易保护了一条路径却遗漏了另一条。如果您的敏感数据也可以在另一个没有设置 FLAG_SECURE 的 Activity 或视图中访问到,那么那个屏幕仍然可以被捕获。您需要从“这条信息出现的每一个地方”而不是“这一个屏幕”的角度来思考。

另一个微妙之处是:FLAG_SECURE 不会追溯保护旧的捕获内容。在您启用该标志之前拍摄的截图或录屏不受影响。它们会一直留在用户存放的地方——本地相册、云备份、即时通讯应用。该标志只对其生效期间的捕获尝试起作用。

最后,还存在用户体验上的取舍。有些用户非常依赖截屏来报告缺陷、保存操作说明或记录工作流程。如果您在他们并不认为特别敏感的地方阻止截屏,就会让他们感到沮丧。过度使用 FLAG_SECURE 可能会让人们觉得您的应用被不必要地锁死了,尤其是在您没有清楚说明为什么不允许截屏的情况下。

结论是:FLAG_SECURE 是一个专注的工具,能很好地解决一个特定问题。它不是万能的。您仍然需要围绕它进行真正的安全设计。

7. 与投屏和外部显示器的交互

最后一个经常让人意外的细节是:当您投屏或镜像屏幕时,FLAG_SECURE 会如何表现。

根据 Android 版本和设备的不同,标记了 FLAG_SECURE 的内容在某些外部显示器上可能根本不会显示。如果系统认为外部显示器是“非安全的”(例如某些投屏目标或镜像显示器),它会应用与截屏和录屏基本相同的逻辑。

在实践中,这意味着在屏幕镜像或投屏会话期间:

  • 安全窗口在远程显示器上可能显示为空白或黑色区域。
  • 非安全 UI 和系统界面元素可能仍然可见。
  • 每当安全窗口位于前台时,您的应用在投屏输出中可能看起来像是“缺失”了部分 UI。

这种行为与您有时会在文档中看到的措辞一致:“Prevents the content of the window from appearing in screenshots or from being viewed on non‑secure displays.”(防止窗口内容出现在截屏中或在非安全显示器上被查看。)这里的“非安全显示器”是一种简称,指的是 Android 不完全信任其能执行与设备主屏幕相同保障的输出设备。

如果您的应用经常用于演示、产品展示或远程支持场景,那么值得测试一下您的安全屏幕在投屏时的表现,并判断这是否可以接受。在某些情况下,您可能需要为这些场景提供替代流程,或至少显示一条友好的提示,说明为什么无法共享该屏幕。

8. 总结

FLAG_SECURE(WindowManager.LayoutParams.FLAG_SECURE)是一个 Android 窗口标志,它的意思是:“不要让这个窗口的像素被捕获。”一旦在窗口上设置,它会让标准截屏失效,让录屏在该窗口出现的位置显示为黑色,在最近应用视图中隐藏有意义的缩略图,甚至可以阻止内容在某些外部显示器上显示。

使用方法是在关键的窗口上设置该标志——通常是特定的 Activity 或 Dialog——并在必要时,当 UI 不再显示敏感数据时再将其清除:

window.setFlags(
    WindowManager.LayoutParams.FLAG_SECURE,
    WindowManager.LayoutParams.FLAG_SECURE
)

// Later, if needed:
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)

您应当考虑在显示真正敏感或受监管信息的屏幕上使用它:银行详情、身份验证机密、健康数据、内部控制台、隐私关键型聊天、自助终端流程以及类似场景。但您也需要非常清楚它不做什么:它不能阻止相机,不加密任何内容,除非您主动设置否则不会全局生效,也不会清理过去的截图。

经过深思熟虑地使用,FLAG_SECURE 是堵住一种特定且非常常见的泄露途径的便捷方法:像素通过截屏和录屏离开您的应用。它并不是 Android 安全的全部——但对于合适的屏幕而言,它绝对是您需要讲好的那一部分。

标签:

android, security, mobile