本文正在参与「金石方案」

截屏能够说是手机设备最常用的功用了,Android 体系非常重视截屏方面的体会,近几年的更新都不忘去优化这方面的体会。

从一开端仅在告诉栏提示已截屏,到 Android 11 支撑在左下角生成截屏缩略图供编辑或分享,再到 Android 12 支撑滚动截屏,以及这次的 Android 14 答应 App 直接监听用户的截屏操作。

为打造愈加标准的监听截屏体会,Android 14 正式推出了受隐私保护的相关 API。简略来说,该 API 答应 App 以 Activity 为单位注册监听 Callback,当这些 Activity 可见并且被用户截屏的话,体系将回调这些 Callback 并告知用户当时的 App 监听到了截屏操作。

如下是官方供给的作用示例:

Android 14 新 API:直接监听截屏操作,不用再观察媒体文件了~

需求留心的是:监听截屏 API 并不供给图画数据,意在告诉 App 截屏的机遇,你能够挑选在这个机遇做相应的操作。比方:客服类 App 能够问询用户是否要上传截屏以反应问题抑或引导用户从相册挑选截屏;隐私程度高的 App 则能够提示用户不要走漏、做好隐私保护等等。

实战

首先要给 App 声明监听截屏的权限: DETECT_SCREEN_CAPTURE

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

这个权限是 normal 等级的,无需动态请求,APK 被装置的时分将主动授予。

Allows an application to get notified when a screen capture of its windows is attempted.

Protection level: normal

之后的写法比较简略,即在需求监听截屏的 Activity 里注册回调:

  1. 创建 ScreenCaptureCallback 的实例,并在 onScreenCapture() 完成里增加所需逻辑,比方此处为弹出警告 Dialog:
  class ScreenShotActivity : AppCompatActivity() {
    private val screenCaptureCallback = ScreenCaptureCallback {
      Log.d("ScreenShot", "onScreenCaptured()", Throwable())
  
      AlertDialog.Builder(this)
         .setMessage("You have taken a screenshot...")
         .setTitle("Warning")
         .show()
     }
     ...
   }
  1. 接着,在该 Activity 的 onStart() 方法里注册上述的 ScreenCaptureCallback 实例,需求指定该回调的履行线程 Executor
class ScreenShotActivity : AppCompatActivity() {
   ...
  override fun onStart() {
    super.onStart()
​
    Log.d("ScreenShot", "onStart() registerScreenCaptureCallback")
    registerScreenCaptureCallback(mainExecutor, screenCaptureCallback)
   }
   ...
}
  1. 记住在 Activity 的 onStop() 里注销该 ScreenCaptureCallback 实例,防止内存 leak:
class ScreenShotActivity : AppCompatActivity() {
   ...
  override fun onStop() {
    super.onStop()
​
    Log.d("ScreenShot", "onStop() unregisterScreenCaptureCallback")
    unregisterScreenCaptureCallback(screenCaptureCallback)
   }
   ...
}

然后将方针 Activity 启动,经过 log 能够看到已经注册了监听回调,后面便是操作截屏进行验证。

04-05 21:57:53.904 5230 5230 D ScreenShot: onStart() registerScreenCaptureCallback

可是问题来了:笔者手动没有能够运转 Android 14 的真机,运转 14 的是 Pixel 6 模仿器。

要知道模仿器上你是无法直接一起按下 POWER 键+ VOLUME DOWN 按键来完结截屏的,一起 Pixel ROM 的告诉面板也没有供给截屏的 UI 进口。

笔者忽然想到了用 adb 模仿按键事件,可苦苦查找之后发现 input keyevent 仅支撑单个 keyevent 的模仿。就在要放弃的时分,我忽然想到另一种 event 模仿方法,便是 sendevent

首先经过查找和测验得悉 POWER 键的长按输入方法:

adb shell sendevent /dev/input/event0 1 116 1 && adb shell sendevent /dev/input/event0 0 0 0 && sleep 3 && adb shell sendevent /dev/input/event0 1 116 0 && adb shell sendevent /dev/input/event0 0 0 0

再找到 VOLUME DOWN 键的长按输入方法:

adb shell sendevent /dev/input/event12 1 114 1 && adb shell sendevent /dev/input/event12 0 0 0 && sleep 3 && adb shell sendevent /dev/input/event12 1 114 0 && adb shell sendevent /dev/input/event12 0 0 0

两者一结合即可模仿一起长按 POWER 键+ VOLUME DOWN 按键的操作:

adb shell sendevent /dev/input/event0 1 116 1 && adb shell sendevent /dev/input/event0 0 0 0 && adb shell sendevent /dev/input/event12 1 114 1 && adb shell sendevent /dev/input/event12 0 0 0 && sleep 3 && adb shell sendevent /dev/input/event0 1 116 0 && adb shell sendevent /dev/input/event0 0 0 0 && adb shell sendevent /dev/input/event12 1 114 0 && adb shell sendevent /dev/input/event12 0 0 0

需求留心的是上述指令需求在 root 环境下履行,即 adb root 之后再履行 sendevent 指令,当然如果你有真机就无需这么迂回啦。

成功模仿用户截屏操作之后,如愿看到了截屏的缩略图和体系在屏幕下方给用户 App 监听了当时截屏的 Toast 提示

Android 14 新 API:直接监听截屏操作,不用再观察媒体文件了~

以及体系给予当时 App 的截屏操作的回调:

04-05 21:58:04.459 5230 5230 D ScreenShot: onScreenCaptured()

App 也依据回调弹出了代码里拟定的截屏警告 Dialog。

能监听到 adb 等方法建议的截屏吗?

了解 Android 上截屏的朋友会知道,截屏的建议方法有很多种,除了最常用的按键组合外还能够:

  • 代码中运用 DeviceCapture test 框架、MediaProjectionManager、SurfaceControl 建议
  • 调试中运用 adb 指令建议
  • AS 的 logcat 窗口里的 “Screen Capture” 图标、Emulator 窗口的 “Take Screenshot” 菜单

等等。

那这些场景下的截屏操作,14 的方法是否能监听到呢?

答案是否定的,官方予以了明确说明:

In Android 14, the system API only detects a screenshot if the user performs a specific combination of hardware button presses. The API doesn’t detect screenshots that are taken when running test commands related to screenshots, including ADB, or within instrumentation tests that capture the device’s current screen contents.

笔者也实践测验了下,无论是 ADB 指令仍是 AS 的 UI 进口触发的截屏,都没有回调上述的 ScreenCaptureCallback,体系亦没有截屏的提示。

理由也可想而知,一般用户不会用这样的方法截屏,这种调试场景下、体系场景下的截屏需求乃至不会在屏幕下方展现缩略图,自然没有必要回调监听的 API 了。

原理

Android 14 的源码尚未揭露,但能够打印 ScreenCaptureCallback仓库瞥到相关细节:

04-06 22:35:23.593 4627 4627 D ScreenShot: onScreenCaptured()
04-06 22:35:23.593 4627 4627 D ScreenShot: java.lang.Throwable
04-06 22:35:23.593 4627 4627 D ScreenShot:   at eu.thomaskuenneth.textviewhighlightsdemo.ScreenShotActivity.screenCaptureCallback$lambda-0(ScreenShotActivity.kt:13)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at eu.thomaskuenneth.textviewhighlightsdemo.ScreenShotActivity.$r8$lambda$5lHWf6dEQmh2JMn9HXzbeN068DI(Unknown Source:0)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at eu.thomaskuenneth.textviewhighlightsdemo.ScreenShotActivity$$ExternalSyntheticLambda0.onScreenCaptured(Unknown Source:2)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.app.ScreenCaptureCallbackHandler$ScreenCaptureObserver.lambda$onScreenCaptured$0(ScreenCaptureCallbackHandler.java:66)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.app.ScreenCaptureCallbackHandler$ScreenCaptureObserver$$ExternalSyntheticLambda0.run(Unknown Source:2)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.os.Handler.handleCallback(Handler.java:958)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.os.Handler.dispatchMessage(Handler.java:99)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.os.Looper.loopOnce(Looper.java:205)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.os.Looper.loop(Looper.java:294)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at android.app.ActivityThread.main(ActivityThread.java:8128)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at java.lang.reflect.Method.invoke(Native Method)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
04-06 22:35:23.593 4627 4627 D ScreenShot:   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:946)

经过上述仓库 ,再结合 13 的现有截屏逻辑,能够估测下该 Callback 的完成:

  1. SystemUI 的 TakeScreenshotService 在截屏完结之后告诉 AMS,AMS 判断当时 Activity 的 ScreenCaptureCallbackHandler 中是否存在 ScreenCaptureObserver
  2. YES 则意味着注册了 ScreenCaptureCallback,便经过 AIDL 告知 App 进程,App 进程内部经过 Handler 告知 ScreenCaptureCallbackHandler 在预设的 Executor 履行 onScreenCaptured() 回调

总结

ScreenCaptureCallback 监听截屏 API 的运用简略、清晰,以后不需求再经过监听媒体文件的改变等逻辑来迂回完成,选用官方的方法去勘探用户的截屏操作并依照文件读写的标准去提取文件。

但需求留心 adb、代码、AS 等方法建议的截屏无法经过该 API 监听得到,笔者认为这些不属于用户操作,没有必要归入到监听规模中。别的,这个监听 API 并不是 Application 等级的,每个方针 Activity 都得注册,所以能够考虑在 BaseActivity 中完结 API 的注册和注销。

相信这个 API 后续亦会扩展到 Jetpack 当中,到时无论是否晋级到了 Android 14 都能够测验切换到官方的监听截屏方法中来。

参考

  • Detect when users take device screenshots
  • DeviceCapture
  • ADB 模仿输入事件总结
  • Android体系截屏的完成分析