Android监听用户的截屏、投屏、录屏行为
Android监听用户的截屏、投屏、录屏行为
一.截屏
方案一:使用系统广播监听截屏操作
从Android Q(10.0)开始,Intent.ACTION_SCREEN_CAPTURED_CHANGED字段不再被支持。这是因为Google在安卓10 中引入了一个新的隐私限制,即限制应用在用户开启了屏幕录制功能或截屏功能时获取相应的广播。
- 创建一个BroadcastReceiver类来接收截屏广播:
public class ScreenCaptureReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (Intent.ACTION_SCREEN_CAPTURES_CHANGED.equals(action)) {// 截屏操作// 在这里执行你的逻辑操作}}
}
- 在AndroidManifest.xml文件中声明截屏广播接收器:
<receiverandroid:name=".ScreenCaptureReceiver"android:enabled="true"><intent-filter><action android:name="android.intent.action.SCREEN_CAPTURE_CHANGED" /></intent-filter>
</receiver>
- 注册截屏广播接收器,开始监听截屏操作:
ScreenCaptureReceiver receiver = new ScreenCaptureReceiver();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_CAPTURE_CHANGED);
registerReceiver(receiver, filter);
- 在不需要监听时,记得取消注册截屏广播接收器:
unregisterReceiver(receiver);
- 在BroadcastReceiver中的onReceive方法中可以执行一些逻辑操作,例如显示一个提示消息:
public class ScreenCaptureReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (Intent.ACTION_SCREEN_CAPTURE_CHANGED.equals(action)) {// 截屏操作Toast.makeText(context, "用户进行了截屏操作", Toast.LENGTH_SHORT).show();}}
}
方案二:使用ContentObserver监听截屏操作
另一种监听截屏操作的方法是使用ContentObserver来监听系统截屏文件的变化。以下是一个示例代码:
public class ScreenCaptureObserver extends ContentObserver {private static final String TAG = "ScreenCaptureObserver";private static final String SCREENSHOTS_DIR = Environment.getExternalStorageDirectory().toString() + "/Pictures/Screenshots";private Context mContext;public ScreenCaptureObserver(Context context) {super(null);mContext = context;}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);Log.d(TAG, "Screen capture detected");if (uri.toString().matches(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/[0-9]+")) {Cursor cursor = null;try {cursor = mContext.getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DATA}, null, null, null);if (cursor != null && cursor.moveToFirst()) {String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));if (path != null && path.startsWith(SCREENSHOTS_PATH)) {// 此处为用户截屏行为的响应逻辑}}} finally {if (cursor != null) {cursor.close();}}}}public void start() {ContentResolver contentResolver = mContext.getContentResolver();contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, this);}public void stop() {ContentResolver contentResolver = mContext.getContentResolver();contentResolver.unregisterContentObserver(this);}
}
使用示例:
ScreenCaptureObserver observer = new ScreenCaptureObserver(this);
observer.start();
对比与选型建议
使用 BroadcastReceiver
方案一的优点:
- 直接监听 Intent.ACTION_SCREENSHOT,无需处理 Uri 的变化。
- 适用于监听应用内的截屏操作。
方案一的缺点:
- 只能监听到整个屏幕的截屏操作,无法获取到具体的截屏内容。
- 有版本限制。从Android Q(10.0)开始,Intent.ACTION_SCREEN_CAPTURED_CHANGED字段不再被支持。
使用 ContentObserver
方案二的优点:
- 可以监听到截屏操作发生的具体 Uri,可以进一步获取截屏的文件路径和信息。
- 适用于监听系统级别的截屏操作。
方案二的缺点:
- 需要指定监听的 Uri,可能需要考虑兼容性问题。
- 需要在代码中处理 Uri 的变化,并解析截屏的文件路径。
选型建议
根据需求,如果只关心截屏的发生与否,并不需要获取截屏的具体内容,方案一可以考虑。如果需要获取截屏内容的具体信息,方案二比较适合。如果只需要监听应用内的截屏操作,方案一比较方便。如果需要监听系统级别的截屏操作,需要使用方案二。
二.录屏
在 Android 开发中,要监听用户的录屏操作,可以使用以下两种方案:
方案一:使用 MediaProjection API
Android 录屏 - 简书 (jianshu.com)
Android Q之后又前台服务限制
- 首先,在你的应用中创建一个
MediaProjectionManager对象来获取用户录屏的权限。
private MediaProjectionManager mediaProjectionManager;
private MediaProjection mediaProjection;
private MediaProjection.Callback projectionCallback;mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
- 请求用户授权录屏权限,在回调中获取
MediaProjection对象。
private static final int REQUEST_CODE_SCREEN_CAPTURE = 1;Intent intent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE);@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == RESULT_OK) {mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);startScreenCapture();}
}
- 开始屏幕录制,并通过
MediaProjection.Callback监听录屏状态。
private VirtualDisplay virtualDisplay;
private MediaRecorder mediaRecorder;private void startScreenCapture() {DisplayMetrics metrics = getResources().getDisplayMetrics();int screenWidth = metrics.widthPixels;int screenHeight = metrics.heightPixels;int screenDensity = metrics.densityDpi;mediaRecorder = new MediaRecorder();mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);mediaRecorder.setVideoSize(screenWidth, screenHeight);mediaRecorder.setVideoFrameRate(30);mediaRecorder.setOutputFile(getOutputFilePath());Surface surface = mediaRecorder.getSurface();virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture",screenWidth,screenHeight,screenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,surface,null,null);mediaRecorder.prepare();mediaRecorder.start();projectionCallback = new MediaProjection.Callback() {@Overridepublic void onStop() {stopScreenCapture();}};mediaProjection.registerCallback(projectionCallback, null);
}private void stopScreenCapture() {if (virtualDisplay != null) {virtualDisplay.release();virtualDisplay = null;}if (mediaRecorder != null) {mediaRecorder.stop();mediaRecorder.reset();mediaRecorder.release();mediaRecorder = null;}if (mediaProjection != null) {mediaProjection.unregisterCallback(projectionCallback);mediaProjection.stop();mediaProjection = null;}
}
方案二:使用 ContentObserver 监听屏幕录制状态变化
- 创建一个继承自
ContentObserver的观察者类,并重写onChange()方法。
public class ScreenRecordObserver extends ContentObserver {private static final String SCREEN_RECORD_STATE_PATH = "/sys/class/graphics/fb0/screen_state";private OnScreenRecordStateChangedListener listener;public ScreenRecordObserver(Handler handler) {super(handler);}public void setOnScreenRecordStateChangedListener(OnScreenRecordStateChangedListener listener) {this.listener = listener;}@Overridepublic void onChange(boolean selfChange, Uri uri) {if (listener != null) {boolean isRecording = isScreenRecording();listener.onScreenRecordStateChanged(isRecording);}}private boolean isScreenRecording() {try {FileInputStream fis = new FileInputStream(SCREEN_RECORD_STATE_PATH);BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fis));String state = bufferedReader.readLine();bufferedReader.close();return !"0".equals(state);} catch (Exception e) {e.printStackTrace();}return false;}
}public interface OnScreenRecordStateChangedListener {void onScreenRecordStateChanged(boolean isRecording);
}
- 注册观察者,监听屏幕录制状态变化。
private ScreenRecordObserver screenRecordObserver;screenRecordObserver = new ScreenRecordObserver(new Handler());
screenRecordObserver.setOnScreenRecordStateChangedListener(new OnScreenRecordStateChangedListener() {@Overridepublic void onScreenRecordStateChanged(boolean isRecording) {if (isRecording) {// 屏幕开始录制} else {// 屏幕停止录制}}
});getContentResolver().registerContentObserver(Uri.parse("content://" + SCREEN_RECORD_STATE_PATH), true, screenRecordObserver);
对比与选型建议
使用 MediaProjection API
方案一的优点:
- 监听到的录屏操作更加准确可靠,可以监听到具体的录屏开始和结束时间,同时可以获取到录屏内容。
- 可以对录屏过程进行更加灵活的控制,如录制分辨率、帧率等。
方案一的缺点:
- 需要申请录屏权限,用户需要给予应用访问屏幕内容的权限。
- 需要编写更多的代码进行屏幕录制的控制。
使用 ContentObserver 监听屏幕录制状态变化
方案二的优点:
- 不需要申请额外的权限。
- 监听录屏状态变化的实现相对简单。
方案二的缺点:
-
不能精确确定录屏的开始和结束时间。
-
无法获取到录屏的具体内容。
选型建议
根据需求,如果需要准确监听录屏的开始和结束时间,并且需要获取到录屏的具体内容,推荐使用方案一。
但如果只是需要知道录屏状态变化,而不关心具体的时间和内容,方案二比较简单方便。
三.投屏
在 Android 开发中,要监听用户的投屏操作,可以使用以下两种方案:
方案一:使用 MediaRouter API
首先,在你的 AndroidManifest.xml 文件中添加以下权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
- 创建一个
MediaRouter.Callback对象,并重写onRouteSelected()和onRouteUnselected()方法。
private MediaRouter mediaRouter;
private MediaRouter.Callback mediaRouterCallback;mediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);mediaRouterCallback = new MediaRouter.Callback() {@Overridepublic void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {// 投屏开始}@Overridepublic void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {// 投屏结束}
};
- 注册监听器,并指定监听的路由类型。
mediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mediaRouterCallback);
方案二:使用 DisplayManager API
首先,在 AndroidManifest.xml 文件中添加以下权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
- 创建一个
DisplayManager.DisplayListener对象,并重写onDisplayAdded()、onDisplayRemoved()和onDisplayChanged()方法。
private DisplayManager displayManager;
private DisplayManager.DisplayListener displayListener;displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);displayListener = new DisplayManager.DisplayListener() {@Overridepublic void onDisplayAdded(int displayId) {// 投屏开始}@Overridepublic void onDisplayRemoved(int displayId) {// 投屏结束}@Overridepublic void onDisplayChanged(int displayId) {// 投屏状态变化}
};
- 注册监听器。
displayManager.registerDisplayListener(displayListener, null);
对比与选型建议
使用 MediaRouter API
方案一的优点:
- 使用方便,只需要注册一个回调即可监听到投屏操作。
- 可以监听到投屏的开始和结束时间。
方案一的缺点:
- 只能监听到路由类型为
ROUTE_TYPE_LIVE_VIDEO的投屏操作,其他类型的投屏操作无法监听到。
使用 DisplayManager API
方案二的优点:
- 可以监听到所有的投屏操作,不限制特定的路由类型。
- 可以监听到投屏的开始和结束时间。
方案二的缺点:
- 需要注册更多的监听器和处理投屏状态变化的逻辑。
- 无法获取到具体的投屏内容。
选型建议
根据需求,如果只需要监听特定类型的投屏操作,并且需要获取投屏的具体内容,推荐使用方案一。
如果需要监听所有类型的投屏操作,方案二比较适合。
同时,如果只关心投屏的开始和结束时间,并不需要具体的投屏内容,两种方案都可以考虑。
相关文章:
Android监听用户的截屏、投屏、录屏行为
Android监听用户的截屏、投屏、录屏行为 一.截屏 方案一:使用系统广播监听截屏操作 从Android Q(10.0)开始,Intent.ACTION_SCREEN_CAPTURED_CHANGED字段不再被支持。这是因为Google在安卓10 中引入了一个新的隐私限制&#…...
MATLAB算法实战应用案例精讲-【路径规划】 图搜索算法
目录 前言 几个高频面试题目 运动规划、路径规划、轨迹规划对比 1. 运动规划 2. 路径规划VS轨迹规划...
Elasticsearch-Kibana使用教程
1.索引操作 1.1创建索引 PUT /employee {"settings": {"index": {"refresh_interval": "1s","number_of_shards": 1,"max_result_window": "10000","number_of_replicas": 0}},"mappi…...
mysql(八)docker版Mysql8.x设置大小写忽略
Mysql 5.7设置大小写忽略可以登录到Docker内部,修改/etc/my.cnf添加lower_case_table_names1,并重启docker使之忽略大小写。但MySQL8.0后不允许这样,官方文档记录: lower_case_table_names can only be configured when initializ…...
KALI LINUX攻击与渗透测试
预计更新 第一章 入门 1.1 什么是Kali Linux? 1.2 安装Kali Linux 1.3 Kali Linux桌面环境介绍 1.4 基本命令和工具 第二章 信息收集 1.1 网络扫描 1.2 端口扫描 1.3 漏洞扫描 1.4 社交工程学 第三章 攻击和渗透测试 1.1 密码破解 1.2 暴力破解 1.3 漏洞利用 1.4 …...
vue之mixin混入
vue之mixin混入 mixin是什么? 官方的解释: 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的…...
[ffmpeg] find 编码器
背景 整理 ffmpeg 中,如何通过名字或者 id 找到对应编码器的。 具体流程 搜索函数 avcodec_find_encoder // 通过 ID 搜索编码器 avcodec_find_encoder_by_name // 通过名字搜索编码器源码分析 ffmpeg 中所有支持的编码器都会注册到 codec_list.c 文件中&…...
Android CardView基础使用
目录 一、CardView 1.1 导入material库 1.2 属性 二、使用(效果) 2.1 圆角卡片效果 2.2 阴影卡片效果 2.3 背景 2.3.1 设置卡片背景(app:cardBackgroundColor) 2.3.2 内嵌布局,给布局设置背景色 2.4 进阶版 2.4.1 带透明度 2.4.2 无透明度 一、CardView 顾名…...
云原生Kubernetes系列 | init container初始化容器的作用
云原生Kubernetes系列 | init container初始化容器的作用 kubernetes 1.3版本引入了init container初始化容器特性。主要用于在启动应用容器(app container)前来启动一个或多个初始化容器,作为应用容器的一个基础。只有init container运行正常后,app container才会正常运行…...
汽车电子芯片介绍之Aurix TC系列
Infineon的AURIX TC系列芯片是专为汽车电子系统设计的,采用了32位TriCore处理器架构。该系列芯片具有高性能、低功耗和丰富的外设接口,适用于广泛的汽车电子应用。以下是AURIX TC系列芯片的主要特性: 1. 高性能处理器 AURIX TC芯片采用了高…...
Linux 设置程序开机自启动的方法
目录 前言开机自启动参考 前言 CentOS Linux release 7.9.2009 (Core) 开机自启动 shell> vim /etc/rc.d/rc.local添加开机后执行的命令 sh /xxx/xxx.sh参考 https://www.cnblogs.com/xlmeng1988/archive/2013/05/22/3092447.html...
java企业财务管理系统springboot+jsp
1、基本内容 (1)搭建基础环境,下载JDK、开发工具eclipse/idea。 (2)通过HTML/CSS/JS搭建前端框架。 (3)下载MySql数据库,设计数据库表,用于存储系统数据。 (4…...
【Windows】如何实现 Windows 上面的C盘默认文件夹的完美迁移
如何实现 Windows 上面的C盘默认文件夹的完美迁移 1. 遇到的问题 在我想迁移C盘的 下载 和 视频 文件夹的时候,遇到了这样的问题,在迁移之后,我显卡录像的视频还是保存到了C盘默认位置里,以及我迁移了 下载 之后下载的盘依然是在…...
kubernetes七层负载Ingress搭建(K8S1.23.5)
首先附上K8S版本及Ingress版本对照 Ingress介绍 NotePort:该方式的缺点是会占用很多集群机器的端口,当集群服务变多时,这个缺点就愈发的明显(srevice变多,需要的端口就需要多) LoadBalancer:该方式的缺点是每个servi…...
二维粒子群算法航线规划
GitHub - gabrielegilardi/PathPlanning: Implementation of particle swarm optimization (PSO) for path planning when the environment is known....
uniapp长按图片识别二维码
引用:https://blog.csdn.net/weixin_48596030/article/details/125405779 <image :src"url" mode"widthFix" click.self"previewImage" show-menu-by-longpress"true" style"width: 350rpx;"></image…...
智能优化算法应用:基于和声算法无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于和声算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于和声算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.和声算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…...
Gitee拉取代码报错You hasn‘t joined this enterprise! fatal unable to access
文章目录 一、问题二、解决2.1、进入**控制面板**2.2、进入**用户账户**2.3、进入**管理Windows凭据**2.4、**普通凭据**2.4.1、添加2.4.2、编辑 2.5、重新拉取|推送代码 三、最后 一、问题 Gitee拉取仓库代码的时候报错You hasnt joined this enterprise! fatal unable to ac…...
算法通关村第十六关-白银挑战滑动窗口经典题目
大家好我是苏麟 , 今天带来滑动窗口经典的一些题目 . 我们继续来研究一些热门的、高频的滑动窗口问题 大纲 最长子串专题无重复字符的最长子串 长度最小的子数组盛最多水的容器 最长子串专题 无重复字符的最长子串 描述 : 给定一个字符串 s ,请你找出其中不含有重…...
springBoot整合task
springBoot整合task 文章目录 springBoot整合task开开关设置任务,并设置执行周期定时任务的相关配置 开开关 设置任务,并设置执行周期 Component public class MyBean {Scheduled(cron "0/1 * * * * ?")public void print(){System.out.prin…...
shein armortoken/smdeviceid/anti/x-gw-auth算法分析
声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包 内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!侵权通过头像私信或名字简介叫我删除博…...
河南AI设计课程指南:机构推荐与避坑秘籍
一、AI 设计热潮下的河南求学路在数字化时代的浪潮中,AI 设计正以前所未有的速度席卷各个行业,成为了创新与效率的新代名词。从广告设计、影视制作到电商运营、交互设计,AI 设计不仅能够大幅提升设计效率,还能通过数据分析和算法模…...
AI智能体开发新范式:用TDD工程化方法构建可靠LLM应用
1. 项目概述:当AI智能体遇上测试驱动开发最近在GitHub上看到一个挺有意思的项目,叫agent-skill-tdd。光看名字,就能嗅到一股“新老结合”的味道——一边是当下火热的AI智能体(Agent),另一边是软件工程领域经…...
昇腾平台上的异构编程
昇腾平台作为国产化 AI 算力核心底座,采用CPUNPU 异构计算架构,异构编程是充分释放昇腾硬件算力、实现高性能计算与 AI 推理融合加速的关键技术。异构编程指在同一计算任务中,协同调度通用处理器(CPU)与神经网络处理器…...
Wwise音频处理完整指南:从游戏音效解包到个性化替换的终极方案
Wwise音频处理完整指南:从游戏音效解包到个性化替换的终极方案 【免费下载链接】wwiseutil Tools for unpacking and modifying Wwise SoundBank and File Package files. 项目地址: https://gitcode.com/gh_mirrors/ww/wwiseutil 还在为游戏音频文件无法编辑…...
知识竞赛的“复活”机制:给落后者第二次机会
🔄 知识竞赛的“复活”机制:给落后者第二次机会包容偶然 挖掘潜力 见证逆袭🎯 引言在知识竞赛中,胜负往往取决于临场发挥、题型适应甚至运气。一次抢答失误、一道冷门题目,都可能让准备充分的选手遗憾离场。…...
OpenMC多群截面计算的3个颠覆性优化策略:从理论到工程实践
OpenMC多群截面计算的3个颠覆性优化策略:从理论到工程实践 【免费下载链接】openmc OpenMC Monte Carlo Code 项目地址: https://gitcode.com/gh_mirrors/op/openmc 核反应堆物理计算中,多群截面精度直接决定了整个模拟系统的可靠性。传统方法在处…...
使用taotaokencli工具一键配置多开发环境下的ai代理
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用 TaoToken CLI 工具一键配置多开发环境下的 AI 代理 基础教程类,介绍如何通过 npx 或全局安装 TaoToken 提供的命令…...
如何用Draw.io ECE插件绘制教科书级别的电路图?
如何用Draw.io ECE插件绘制教科书级别的电路图? 【免费下载链接】Draw-io-ECE Custom-made draw.io-shapes - in the form of an importable library - for drawing circuits and conceptual drawings in draw.io. 项目地址: https://gitcode.com/gh_mirrors/dr/D…...
深入AD9361:除了QPSK和FM,这颗射频芯片在Zynq平台上还能玩出什么花样?
深入AD9361:解锁Zynq平台上的射频创新潜能 当工程师们首次接触AD9361这颗射频芯片时,往往会被其标准应用场景如QPSK调制或FM收音所吸引。然而,这颗高度集成的RF收发器IC的真正价值,在于它为Zynq PSPL架构带来的无限可能性。本文将…...
