Android 基于 ContentObserver 监听用户截屏

前言

很多应用在当你截屏的时候能够感知到,并提示你是否要发送截屏等等。

Andorid 官方 API 没有检测截屏的能力,但有一些变通的办法能够检测用户在使用应用时是否进行了截屏操作。本文将探讨如何实现这一功能。

思路非常简单:用户使用 App 期间,监听用户设备中的图片,查看 “截屏” 文件夹中是否新增了图片

实现方式

我们定义一个 ContentObserver 观察用户设备中图片变化。它以一个 Handler 作为参数,每当我们指定的 URI(统一资源标识符)发生变化时,onChange 方法就会被触发。

   contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
                override fun onChange(selfChange: Boolean, uri: Uri?) {
                    super.onChange(selfChange, uri)               
                }
            }

接下来需要注册 ContentObserver

registerContentObserver 方法需要三个参数。

  • 第一个是 ContentObserver 要观察的 URI。
  • 第二个参数 notifyForDescendants,如果我们希望观察以指定 URI 开头的所有 URI 的变化,该参数应设为 true;如果我们只希望在指定的 URI 本身发生变化时收到通知,该参数可以设为 false。
  • 最后一个参数是我们要注册的 ContentObserver。
context.contentResolver.registerContentObserver(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                true,
                contentObserver 
            )

注册后,来看看如何检测截屏。思路很简单,当用户使用应用时,只要媒体文件发生变化,我们就获取 URI,然后尝试从该 URI 中提取文件路径。我们可以使用 MediaStore.Images API 的 DATA 属性来获取文件路径,但该属性在 API 29 中已被弃用。尽管在 API 30 中该属性仍然存在,但文档中提到开发者不应假定文件始终可用。在 API 29 中,引入了一个名为 RELATIVE_PATH 的新属性。因此,对于 API 29 及更高版本的设备,我们可以使用 RELATIVE_PATHDISPLAY_NAME 来获取截屏文件的路径;对于其他设备,我们可以继续使用 DATA 属性。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            queryRelativeDataColumn(uri)
        } else {
            queryDataColumn(uri)
        }

在下面的代码片段中,我们使用 ContentObserver 返回给我们的 URI 来查询 DATA 属性。如果该路径中包含 “screenshot” 字符串,我们就可以判定用户进行了截屏操作。

  private fun queryDataColumn(uri: Uri) {
        val projection = arrayOf(
            MediaStore.Images.Media.DATA
        )
        context.contentResolver.query(
            uri,
            projection,
            null,
            null,
            null
        )?.use { cursor ->
            val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
            while (cursor.moveToNext()) {
                val path = cursor.getString(dataColumn)
                if (path.contains("screenshot", true)) {
                    // do something
                }
            }
        }
    }

同样,也可以像下面的代码片段那样,使用 DISPLAY_NAMERELATIVE_PATH 来实现相同的功能。

    private fun queryDataColumn(uri: Uri) {
        val projection = arrayOf(
            MediaStore.Images.Media.DATA
        )
        context.contentResolver.query(
            uri,
            projection,
            null,
            null,
            null
        )?.use { cursor ->
            val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
            while (cursor.moveToNext()) {
                val path = cursor.getString(dataColumn)
                if (path.contains("screenshot", true)) {
                    // do something
                }
            }
        }
    }

当用户不再使用你的应用时,别忘了注销 ContentObserver。可以在 onStop 方法中进行注销。

context.contentResolver.unregisterContentObserver(contentObserver)

由于要访问用户设备的媒体文件,所以需要在 AndroidManifest.xml 中添加相应权限。

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

大部分应用的截屏监听都是这么做的,如果知道某个应用在会跟踪你的截屏操作,可以撤销该权限,它就无法正常工作了,例如 Instagram 和 Snapchat 都是是这么做的。只不过如果关闭此权限 Snapchat 会直接不让你启动应用。

这个解决方案仅适用于将截屏存储在 “screenshot” 文件夹中,或者文件名包含 “screenshot” 字符串的设备。在原生安卓设备上,截屏会保存在 “screeshot” 文件夹中,但如果设备厂商更改了默认存储位置,就无法检测到这些截屏了。不过,如果你遇到这种情况,可以针对新目录添加另一个检查条件, ContentObserver 会返回截屏的文件路径。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值