Android监听截屏

Posted bug樱樱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android监听截屏相关的知识,希望对你有一定的参考价值。

android系统没有提供默认的截屏事件监听方式,需要开发者自己想办法实现。查看了网上推荐的实现方式,主要是通过内容观察者(ContentObserver)监听媒体数据库的变化,根据内容名称(路径)中是否包含关键字,判断是否为截屏事件。 关键字:

 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", "snap", "截屏"
    ; 

第一步:对ContentResolver添加内、外存储变化监听;

mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, MainHandler.get());
mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MainHandler.get());
mResolver = AppContext.get().getContentResolver();
// 添加监听
mResolver.registerContentObserver(
        MediaStore.Images.Media.INTERNAL_CONTENT_URI,
        false,
        mInternalObserver
);
mResolver.registerContentObserver(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        false,
        mExternalObserver
); 

第二步:当内容观察者(ContentObserver)监听到变化时,会调用onChange方法,此时,我们使用ContentResolver去查询最新的一条数据; 需要注意的是,查询外部存储一定要有读取存储权限(Manifest.permission.READ_EXTERNAL_STORAGE),否则会在查询的时候报错; 第三步:判断查到到数据是否为截图文件;在这里有一个很难处理到问题,在华为荣耀手机上,截图预览图生成的同时就会通知存储内容变化,而小米则是在截图预览图消失后通知变化; 解决方案:

  1. 判断当前文件路径是否与上次有效路径相同,相同执行步骤2,不相同则执行步骤3;
  2. 当前路径与上次路径相同,取消回调请求,重新延迟发送回调请求;
  3. 当前路径与上次路径不同,判断内容的生成时间(MediaStore.Images.ImageColumns.DATE_TAKEN)和添加时间(MediaStore.Images.ImageColumns.DATE_ADDED)是否相同,相同执行步骤4,不相同则执行步骤5;
  4. 内容的生成时间和添加时间相同,认为此时为生成长截图,立刻取消回调请求,执行空回调(用于取消弹窗等操作);
  5. 内容的生成时间和添加时间不同,检查是否含有关键字,若判定为截图,更新上次有效路径,取消回调请求,重新延迟发送回调请求;
 // 获取各列的索引
            int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
            int dateAddIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_ADDED);
            // 获取行数据
            final String data = cursor.getString(dataIndex);
            long dateTaken = cursor.getLong(dateTakenIndex);
            long dateAdded = cursor.getLong(dateAddIndex);
            if (data.length() > 0) 
                if (TextUtils.equals(lastData, data)) 
                    MainHandler.get().removeCallbacks(shotCallBack);
                    MainHandler.get().postDelayed(shotCallBack, 500);
                 else if (dateTaken == 0 || dateTaken == dateAdded * 1000) 
                    MainHandler.get().removeCallbacks(shotCallBack);
                    if (listener != null) 
                        listener.onShot(null);
                    
                 else if (checkScreenShot(data)) 
                    MainHandler.get().removeCallbacks(shotCallBack);
                    lastData = data;
                    MainHandler.get().postDelayed(shotCallBack, 500);
                
             

完整代码:(其中AppContext为全局Application单例,MainHandler为全局主线程Handler单例)

public class ScreenShotHelper 
    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", "snap", "截屏"
    ;

    /**
     * 读取媒体数据库时需要读取的列
     */
    private static final String[] MEDIA_PROJECTIONS = 
            MediaStore.Images.ImageColumns.DATA,
            MediaStore.Images.ImageColumns.DATE_TAKEN,
            MediaStore.Images.ImageColumns.DATE_ADDED,
    ;
    /**
     * 内部存储器内容观察者
     */
    private ContentObserver mInternalObserver;
    /**
     * 外部存储器内容观察者
     */
    private ContentObserver mExternalObserver;
    private ContentResolver mResolver;
    private OnScreenShotListener listener;
    private String lastData;
    private Runnable shotCallBack = new Runnable() 
        @Override
        public void run() 
            if (listener != null) 
                final String path = lastData;
                if (path != null && path.length() > 0) 
                    listener.onShot(path);
                
            
        
    ;

    private ScreenShotHelper() 
        // 初始化
        mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, null);
        mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null);

        mResolver = AppContext.get().getContentResolver();
        // 添加监听
        mResolver.registerContentObserver(
                MediaStore.Images.Media.INTERNAL_CONTENT_URI,
                false,
                mInternalObserver
        );
        mResolver.registerContentObserver(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                false,
                mExternalObserver
        );
    

    private static class Instance 
        static ScreenShotHelper mInstance = new ScreenShotHelper();
    

    public static ScreenShotHelper get() 
        return Instance.mInstance;
    

    public void setScreenShotListener(OnScreenShotListener listener) 
        this.listener = listener;
    

    public void removeScreenShotListener(OnScreenShotListener listener) 
        if (this.listener == listener) 
            synchronized (ScreenShotHelper.class) 
                if (this.listener == listener) 
                    this.listener = null;
                
            
        
    

    public void stopListener() 
        mResolver.unregisterContentObserver(mInternalObserver);
        mResolver.unregisterContentObserver(mExternalObserver);
    

    private void handleMediaContentChange(Uri contentUri) 
        Cursor cursor = null;
        try 
            // 数据改变时查询数据库中最后加入的一条数据
            cursor = mResolver.query(
                    contentUri,
                    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 dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
            int dateAddIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_ADDED);
            // 获取行数据
            final String data = cursor.getString(dataIndex);
            long dateTaken = cursor.getLong(dateTakenIndex);
            long dateAdded = cursor.getLong(dateAddIndex);
            if (data.length() > 0) 
                if (TextUtils.equals(lastData, data)) 
                    //更改资源文件名也会触发,并且传递过来的是之前的截屏文件,所以只对分钟以内的有效
                    if (System.currentTimeMillis() - dateTaken < 3 * 3600) 
                        MainHandler.get().removeCallbacks(shotCallBack);
                        MainHandler.get().postDelayed(shotCallBack, 500);
                    
                 else if (dateTaken == 0 || dateTaken == dateAdded * 1000) 
                    MainHandler.get().removeCallbacks(shotCallBack);
                    if (listener != null) 
                        listener.onShot(null);
                    
                 else if (checkScreenShot(data)) 
                    MainHandler.get().removeCallbacks(shotCallBack);
                    lastData = data;
                    MainHandler.get().postDelayed(shotCallBack, 500);
                
            
         catch (Exception e) 
            //
         finally 
            if (cursor != null && !cursor.isClosed()) 
                cursor.close();
            
        
    

    /**
     * 根据包含关键字判断是否是截屏
     */
    private boolean checkScreenShot(String data) 
        if (data == null || data.length() < 2) 
            return false;
        
        data = data.toLowerCase();
        for (String keyWork : KEYWORDS) 
            if (data.contains(keyWork)) 
                return true;
            
        
        return false;
    

    private class MediaContentObserver extends ContentObserver 
        private Uri mContentUri;

        MediaContentObserver(Uri contentUri, Handler handler) 
            super(handler);
            mContentUri = contentUri;
        

        @Override
        public void onChange(boolean selfChange) 
            super.onChange(selfChange);
            if (listener != null) 
                handleMediaContentChange(mContentUri);
            
        
    

    public interface OnScreenShotListener 
        void onShot(@Nullable String data);
    

 

总结: 1.必须要有读取内存的权限; 2.内容生成时间为毫秒,内容添加时间为秒,比较时需要注意换算; 3.当内容生成时间等于内容添加时间时,应当取消之前的截屏监听操作(尤其是会遮挡页面视图的部分);

文末

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

以上是关于Android监听截屏的主要内容,如果未能解决你的问题,请参考以下文章

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

截屏状态监听 - iOS

监听手机截屏事件

android源码解析(二十七)-->HOME事件流程

如何在android上通过降低音量和电源键来截取屏幕截图?

android中怎么用代码实现截屏