赞
踩
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),否则会在查询的时候报错; 第三步:判断查到到数据是否为截图文件;在这里有一个很难处理到问题,在华为荣耀手机上,截图预览图生成的同时就会通知存储内容变化,而小米则是在截图预览图消失后通知变化; 解决方案:
// 获取各列的索引 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开发的各个知识点。
![在这里插入图片描述](https://img-blog.csdnimg.cn/06e41b3932164f0db07014d54e6e5626.png) 相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
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 源码分析——图片加载流程
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
从一个膜拜大神的 Demo 开始
Kotlin 写 Gradle 脚本是一种什么体验?
Kotlin 编程的三重境界
Kotlin 高阶函数
Kotlin 泛型
Kotlin 扩展
Kotlin 委托
协程“不为人知”的调试技巧
图解协程:suspend
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使用指南
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。