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…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...
Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...
中科院1区顶刊|IF14+:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点
中科院1区顶刊|IF14:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点 当下,免疫与代谢性疾病的关联研究已成为生命科学领域的前沿热点。随着研究的深入,我们愈发清晰地认识到免疫系统与代谢系统之间存在着极为复…...
