监听Android系统截屏

Android截屏监控(已适配Android 14) Android使用ContentObserver实现截屏监控,已适配Android 14 阅读详情

1. 原理

因为Android系统没有提供截屏的相关API,所以需要我们自己去实现。国内的Android手机都是使用定制系统的,截图方式五花八门,采用对截图按键的监听的方案并不合适。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为截屏了。

需要ContentObserver监听的资源URI:

  • 内部存储器内容:MediaStore.Images.Media.INTERNAL_CONTENT_URI
  • 外部存储器内容:MediaStore.Images.Media.EXTERNAL_CONTENT_URI

2. 具体实现

根据以上原理,我们可以封装一个工具类来处理系统截屏,具体代码如下:

package com.fantasy.blogdemo.screenshot;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.util.Log;

/**
 * 系统截屏监听工具,监听系统截屏,然后对截图进行处理
 * <pre>
 *     author  : Fantasy
 *     version : 1.0, 2019-06-05
 *     since   : 1.0, 2019-06-05
 * </pre>
 */
public class ScreenShot {
    private static final String TAG = "ScreenShot";
    private static final String[] MEDIA_PROJECTIONS = {
            MediaStore.Images.ImageColumns.DATA,
            MediaStore.Images.ImageColumns.DATE_TAKEN,
            MediaStore.Images.ImageColumns.DATE_ADDED
    };

    /**
     * 截屏依据中的路径判断关键字
     */
    private static final String[] KEYWORDS = {
            "screenshot", "screen_shot", "screen-shot", "screen shot", "screencapture",
            "screen_capture", "screen-capture", "screen capture", "screencap", "screen_cap",
            "screen-cap", "screen cap"
    };

    private ContentResolver mContentResolver;
    private CallbackListener mCallbackListener;
    private MediaContentObserver mInternalObserver;
    private MediaContentObserver mExternalObserver;
    private static ScreenShot mInstance;

    private ScreenShot() {
    }

    /**
     * 获取 ScreenShot 对象
     *
     * @return ScreenShot对象
     */
    public static ScreenShot getInstance() {
        if (mInstance == null) {
            synchronized (ScreenShot.class) {
                mInstance = new ScreenShot();
            }
        }
        return mInstance;
    }

    /**
     * 注册
     *
     * @param context          上下文
     * @param callbackListener 回调监听
     */
    public void register(Context context, CallbackListener callbackListener) {
        mContentResolver = context.getContentResolver();
        mCallbackListener = callbackListener;

        HandlerThread handlerThread = new HandlerThread(TAG);
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, handler);
        mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, handler);

        mContentResolver.registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
                false, mInternalObserver);
        mContentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                false, mExternalObserver);
    }

    /**
     * 注销
     */
    public void unregister() {
        if (mContentResolver != null) {
            mContentResolver.unregisterContentObserver(mInternalObserver);
            mContentResolver.unregisterContentObserver(mExternalObserver);
        }
    }

    private void handleMediaContentChange(Uri uri) {
        Cursor cursor = null;
        try {
            // 数据改变时,查询数据库中最后加入的一条数据
            cursor = mContentResolver.query(uri, MEDIA_PROJECTIONS, null, null,
                    MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1");
            if (cursor == null) {
                return;
            }
            if (!cursor.moveToFirst()) {
                return;
            }
            int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            int dateAddedIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_ADDED);
            // 处理获取到的第一行数据
            handleMediaRowData(cursor.getString(dataIndex), cursor.getLong(dateAddedIndex));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    }

    /**
     * 处理监听到的资源
     */
    private void handleMediaRowData(String path, long dateAdded) {
        long duration = 0;
        long step = 100;

        // 发现个别手机会自己修改截图文件夹的文件,截屏功能会误以为是用户在截屏操作,进行捕获,所以加了一个时间判断
        if (!isTimeValid(dateAdded)) {
            Log.d(TAG, "图片插入时间大于1秒,不是截屏");
            return;
        }

        // 设置最大等待时间为500ms,因为魅族的部分手机保存截图有延迟
        while (!checkScreenShot(path) && duration <= 500) {
            try {
                duration += step;
                Thread.sleep(step);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (checkScreenShot(path)) {
            if (mCallbackListener != null) {
                mCallbackListener.onShot(path);
            }
        }
    }

    /**
     * 判断时间是否合格,图片插入时间小于1秒才有效
     */
    private boolean isTimeValid(long dateAdded) {
        return Math.abs(System.currentTimeMillis() / 1000 - dateAdded) <= 1;
    }

    private boolean checkScreenShot(String path) {
        if (path == null) {
            return false;
        }
        path = path.toLowerCase();
        for (String keyword : KEYWORDS) {
            if (path.contains(keyword)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 媒体内容观察者
     */
    private class MediaContentObserver extends ContentObserver {
        private Uri mUri;

        MediaContentObserver(Uri uri, Handler handler) {
            super(handler);
            mUri = uri;
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            //Log.d("ScreenShot", "图片数据库发生变化:" + selfChange);
            handleMediaContentChange(mUri);
        }
    }

    /**
     * 回调监听器
     */
    public interface CallbackListener {
        /**
         * 截屏
         *
         * @param path 图片路径
         */
        void onShot(String path);
    }

}

3. 使用方式

在 AndroidManifest.xml 中添加权限:

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

在Android 6.0及以上系统需要动态申请权限,这里不提供代码。有兴趣想知道我怎么实现的,可以看看我提供的demo(demo的访问地址在文末提供)。

获取实例:

private ScreenShot mScreenShot = ScreenShot.getInstance();

注册并处理截屏:

mScreenShot.register(mContext, new ScreenShot.CallbackListener() {
    @Override
    public void onShot(String path) {
        // 捕获到系统截屏,path是截屏的绝对路径
    }
});

不需要监听系统截屏的话,需要取消注册:

mScreenShot.unregister();

好了,以上就是 ScreenShot 工具类的使用方式。想看完整代码的同学,可以到我的GitHub上面看看BlogDemo。

 

如果想进一步交流和学习的同学,可以加一下QQ群哦!

Android系统截屏监听工具 Android系统截屏监听工具 做系统截屏通知的时候,找了很多资料,测试后发现要做挺多机型适配的,而且有时候会没有监听到,有时候又监听到多次,不能实现想要的效果。一下是最终的解决方案,已在足够多的手机品牌跟同一品牌不同版本上做过足够多的适配。 1、首先创建一个工具类 /** * 系统截屏监听工具,监听系统截屏,然后对截图进行处理 */ public class ScreenShot { private static final String TAG = "ScreenShot"; pri 阅读详情

相关推荐

Android App中监听系统截屏(截屏监听功能)

功能需求: App内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,进行对图片的二次操作,例如添加二维码,公司logo等一系列操作。 首先来app界面图及截屏监听图添加效果图 主要是利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了,下面看看截屏管理类 ScreenShotListenManager.java package com.test.base; import andro

lplj717的博客 8391

Android监听用户的截屏、投屏、录屏行为

探索Android开发中监听用户的截屏+录屏+投屏行为

进击的小怪兽 1万+

android截图事件监听的原理与实现

Android系统没有对用户截屏行为提供回调的api,所以我们只能走野路子来获取用户是否截屏了。一般大家都会采用如下两种方法 1.监听截屏图片所在目录变化(FileObserver) 2.监听媒体库的变化(ContentObserver)  上面两种方法均不是万能的,需要结合使用才能达到良好的效果,首先看看如何监控目录 在android中,我们可以通过FileObserver来监听目录变化,先来看看如何使用 private static final File DIRECTORY_PICTURES = new File(Environment.getExternalStorageDire

Android监听截屏

Android系统没有提供默认的截屏事件监听方式,需要开发者自己想办法实现。查看了网上推荐的实现方式,主要是通过内容观察者(ContentObserver)监听媒体数据库的变化,根据内容名称(路径)中是否包含关键字,判断是否为截屏事件。 关键字: 第一步:对ContentResolver添加内、外存储变化监听; 第二步:当内容观察者(ContentObserver)监听到变化时,会调用onChange方法,此时,我们使用ContentResolver去查询最新的一条数据; 需要注意的是,查询外部存储一定要有

大模型学习路线 4295

详解Android截屏事件监听

本篇文章主要介绍了Android截屏事件监听,Android系统没有直接对截屏事件监听的接口,本文介绍了2种方法,有兴趣的可以了解一下。

Aurora8b10b光口通信实战:复位时序与IP核配置详解

本文深入解析了Aurora 8B10B协议在FPGA高速光口通信中的核心实践,重点剖析了确保链路稳定启动的关键复位时序与IP核配置细节。通过详解全双工与单工模式下的复位流程、Vivado中IP核的物理层与链路层参数设置,并结合用户逻辑设计实例,为工程师实现可靠的高速数据传输提供了清晰的实战指南。

weixin_29280219的博客 178

安卓截屏监听

安卓系统并没有提供原生的接口,所以就需要自己去实现。各种定制系统截图方式五花八门,对截图按键的监听并不容易。最通用的方式也就是对图库进行监听,如果在特定时间内增加了符合规则的图片,则判定用户进行了截图操作。当有特殊需求时,例如,某个页面禁止截屏,或者当用户截屏时,提示用户需要意见反馈。就需要用到截屏监听。2.、 截图监听器ScreenShotListenManager。在6.0及以上系统需要动态申请权限。

m0_72345017的博客 1381

实测对比:DECIMER、Img2Mol、MolScribe,哪个化学结构识别工具更靠谱?

化学结构识别(CSR)工具的核心任务是将分子结构图像转换为SMILES(Simplified Molecular Input Line Entry System)表示。这一过程需要克服图像质量差异、绘制风格变化以及特殊基团识别等挑战。三款工具采用不同的技术路线:DECIMER:基于EfficientNetV2+Transformer架构,优势在于处理标准期刊文献中的分子图示Img2Mol。

weixin_32576389的博客 346

Android 截屏监听(截图分享功能)

一、需求背景如果检测到用户在 app 中有截图行为,那么弹出一个分享提示框,提示用户去分享这个截图。本文主要从三个方面来讲:1、截图检测的具体实现2、截图检测踩到的坑3、截图合成二、截图分享实现1、截图分享实现分为两个部分,首先是截图事件的监听。安卓系统并没有提供 api 来监听系统截图事件,也没有什么广播可以用来监听这类事件,那要怎么解决呢?我们先来看下截图源码。两个类都在目录framework/base/package/SystemUI/src/com/android/systemui/screensh

大模型学习路线 6629

ChatBI告别“NL2SQL依赖症”!从准确率50%到90%,3大技术路线+4个企业案例拆解核心玩法

摘要: ChatBI通过自然语言交互实现数据民主化,其核心是“语义理解+数据执行+知识沉淀”的全链路能力,而非单一的NL2SQL技术。行业采用三大技术路线提升准确率:专有知识库(提升30%)、指标标签语义层(准确率85%)、JSON中间层(多轮对话提升25%)。数据执行方案包括NL2SQL、NL2API等,需结合场景选择。企业落地需解决四大痛点:跨表查询准确率低(引入拓扑图)、模糊查询(用户干预机制)、响应慢(预计算缓存)、多轮对话(上下文池)。Spring AI Alibaba提供轻量级集成方案,助力企业

weixin_52610848的博客 1656

Unity Sprite Editor切片功能详解:如何快速将一张大图拆分成多个小图(附常见问题解决)

本文详细解析了Unity Sprite Editor的切片功能,指导开发者如何高效地将一张大图拆分为多个独立小图。文章涵盖了自动与手动切片操作步骤、轴心点设置、子精灵命名管理等核心技巧,并针对切片错位、性能优化等常见问题提供了实用解决方案,帮助提升2D游戏资源处理效率。

i0j1k2l3m的博客 647

Android监听系统截屏的坑

最近有做在监听系统截屏操作,然后对截屏的图片获取到,再在其底部加入二维码或者其他信息生成分享海报。这里面最不好做的就是监听系统截屏的操作了,系统没有提供相关api,所以得靠“骚操作”,目前业内主流的操作,像这篇博文写的那样:https://blog.youkuaiyun.com/xietansheng/article/details/52692163 通过 自定义媒体内容观察者内部类,去观察媒体数据库的改变,...

落叶挽歌的博客 3374

ABB RobotWare数据包全版本归档与高效获取指南

记得去年有个汽车产线升级项目,客户现场用的是RobotWare 6.05.0166版本,而我的RobotStudio里只有最新的7.x系列。RobotWare数据包本质上就是ABB机器人的"操作系统镜像",不同版本的机器人控制器需要匹配特定版本的RobotWare。有个容易忽略的细节:偶数版本号(如6.04、6.06)通常比奇数版本更稳定,因为ABB的内部发布策略是偶数版用于正式产线,奇数版多用于测试新功能。最终实现了零停机升级,这个经验告诉我们:版本切换不是简单的覆盖安装,而是需要系统性的验证流程。

weixin_30764883的博客 278

Android 监听系统截屏操作

在Android App中监听系统截屏功能,没有系统标准的监听器或者api可以调用,需要自己实现。针对这个需求,目前大部分实现方案是监听系统的媒体数据库。

qq_40959750的博客 2325

Playwright实战:打造绕过全维度前端反爬的无头浏览器爬虫(2026实战版)

Playwright绕过前端反爬的核心是模拟真实:真实的设备指纹、真实的行为轨迹、真实的网络请求;2025年前端反爬的核心检测维度是设备指纹+行为轨迹+网络请求,三者缺一不可;实战中需重点关注WebGL/Canvas指纹模拟、行为随机性、请求头完整性,这是绕过反爬的关键;性能优化需以“不触发反爬”为前提,优先选择浏览器复用、资源拦截,而非无限制并发。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 2094

Xilinx平台SRIO介绍(五)示例工程Example Design介绍

理解IP的使用,少不了Example Design的学习。

数字IC/FPGA杂货铺,找一找,或许有你想要的~ 1万+

Android 系统 截屏监听 的 原理与实现

Android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了。

谢TS的博客 3万+

解决pd.read_json报错ValueError: Trailing data问题

首先,我们使用open函数打开JSON文件,然后使用json.load函数将JSON文件数据解码为Python对象。在使用Python进行数据处理中,我们经常需要将JSON格式的数据读入到DataFrame中进行处理。而使用pandas库的read_json函数,就可以方便地将JSON数据读入到DataFrame中。但是有时候在读取JSON数据时,会遇到一个非常常见的错误:ValueError: Trailing data。这个错误的解决方案并不难,只需要在read_json中添加一个参数即可。

学习使你进步。 2234

Android系统 截屏监听 的 原理与实现

本文链接: http://blog.youkuaiyun.com/xietansheng/article/details/52692163 1. 原理 Android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以

埋头苦干程序猿 1231

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Android平台监听系统截屏方案预研及相关知识点

最近有个针对系统截屏的需求,所以预研了Android平台上捕获系统截屏的方案。 最直接的方式就是监听手机的系统截屏组合键(电源键+音量下键),但是这种方式实现难度大,且有的机型使用特殊手势进行截屏,兼容性问题难以解决。 所以网上流行的方案是监听系统截屏目录下文件创建事件或者多媒体数据库图片资源变更通知。我对两种方式都做了测试,多多少少都存在一些问题,现整理如下: 通过FileObse

omnispace的博客 2283

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

详细讲解安卓截屏监听操作

项目中有时会遇到监听用户截屏操作的需求,应用场景也比较丰富。 eg. 1: 某个隐私页面禁止截屏,当发现用户截屏后,删除该截屏文件; eg. 2: 当用户截屏时,可能是需要进行分享,意见反馈等操作,此时进行弹窗提示,方便用户操作(支付宝就有类似功能)。

vchao的博客 5209

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

客户端监听用户截屏事件,自动在截屏底部增加品牌水印和官网网址二维码以及截屏时间

Android系统-截屏监听的原理与实现: https://blog.youkuaiyun.com/xietansheng/article/details/52692163?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task iOS截屏分享功能实现: https://blog.youkuaiyun.com/studying_ios/article/details/71092763?de

wannaeatpanda的博客 715

andriod 监听系统截屏操作

最近公司项目需要做一个需求,就是监听app截屏,然后上报埋点信息的需求,但android是没有系统的接口可监听,只能自己实现(不像ios 系统提供了,调用一下几行代码就完事),而自己实现的话就存在各种android机型的适配问题,下面将整个代码贴一下: package com.hundsun.winner.application.base; import android.content.Con...

weixin_42042620的博客 536
上一篇: Android安全:禁止APP录屏和截屏
下一篇: Android图形验证码
Fantasy丶Lin
博客等级 码龄11年 92粉丝 59原创
评论 15
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值