需求:拦截系统截屏事件,并触发相应操作。
1.用户触发系统截屏事件时,获取到相应的截图。
2.当系统弹窗消失时,显示缩小版的截图弹框。
分析:Android没有提供直接获取截屏事件的API,那么该如何做呢?
当系统截取完图片后,会将图片保存在默认的路径下,那么我们可以通过这些路径和存储时间等来判断是否为截屏图片
效果图:
实现:
1. 配置权限
因为我们需要通过文件变化获取截屏图片,因此需要加上响应权限,6.0以上需要通过动态权限的方式添加
2. 代码实现
1、第一步
首先需要创建一个内部存储器内容观察者和一个外部存储器内容观察者,用来监听系统内部的文件变化
/**
* 媒体内容观察者(观察媒体数据库的改变)
*/
private class MediaContentObserver extends ContentObserver {
//内部存储的内容/样式URI
private Uri mContentUri;
/**
* 构造函数
*
* @param contentUri 内部存储的内容/样式URI
* @param handler 运行在 UI 线程的 Handler, 用于运行监听器回调
*/
public MediaContentObserver(Uri contentUri, Handler handler) {
super(handler);
mContentUri = contentUri;
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//处理媒体数据库的内容改变
handleMediaContentChange(mContentUri);
}
}
2、第二步
当获取到媒体数据库中的数据后,处理媒体数据库的内容
/**
* 处理媒体数据库的内容改变
*/
private void handleMediaContentChange(Uri contentUri) {
Cursor cursor = null;
try {
// 数据改变时查询数据库中最后加入的一条数据
cursor = mContext.getContentResolver().query(
contentUri,
Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
null,
null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
);
if (cursor == null) {
Log.e(TAG, "Deviant logic.");
return;
}
if (!cursor.moveToFirst()) {
Log.d(TAG, "Cursor no data.");
return;
}
// 获取各列的索引
//图片存储的文件路径索引
int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
//图片的存储时间索引
int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
int widthIndex = -1;
int heightIndex = -1;
if (Build.VERSION.SDK_INT >= 16) {
//图片的宽索引
widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
//图片的高索引
heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
}
// 获取行数据
//图片存储的文件路径
String data = cursor.getString(dataIndex);
//图片的存储时间
long dateTaken = cursor.getLong(dateTakenIndex);
//图片的宽
int width = 0;
//图片的高
int height = 0;
if (widthIndex >= 0 && heightIndex >= 0) {
width = cursor.getInt(widthIndex);
height = cursor.getInt(heightIndex);
} else {
// API 16 之前, 宽高要手动获取
Point size = getImageSize(data);
width = size.x;
height = size.y;
}
// 处理获取到的第一行数据
handleMediaRowData(data, dateTaken, width, height);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭cursor
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
}
3、第三步
处理得到的数据,检查是否是截屏图片
/**
* 判断指定的数据行是否符合截屏条件
*
* @param imagePath 图片的存储路径
* @param dateTaken 图片的存储时间
* @param width 图片的宽度
* @param height 图片的高度
* @return true:该图片为截屏图片;false:该图片不是截屏图片
*/
private boolean checkScreenShot(String imagePath, long dateTaken, int width, int height) {
/*
* 判断依据一: 时间判断
*/
// 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于10秒, 则认为当前没有截屏
if (dateTaken < mStartListenTime || (System.currentTimeMillis() - dateTaken) > 10 * 1000) {
return false;
}
/*
* 判断依据二: 尺寸判断
*/
if (sScreenRealSize != null) {
// 如果图片尺寸超出屏幕, 则认为当前没有截屏
if (!((width <= sScreenRealSize.x && height <= sScreenRealSize.y) ||
(height <= sScreenRealSize.x && width <= sScreenRealSize.y))) {
return false;
}
}
/*
* 判断依据三: 路径判断
*/
if (TextUtils.isEmpty(imagePath)) {
return false;
}
imagePath = imagePath.toLowerCase();
// 判断图片路径是否含有指定的关键字之一, 如果有, 则认为当前截屏了
for (String keyWork : SCREEN_PATHS) {
if (imagePath.contains(keyWork)) {
return true;
}
}
return false;
}
4、第四步
如果是则回传给UI,否则做出响应处理
/**
* 处理获取到的一行数据
*
* @param imagePath 图片存储路径
* @param dateTaken 图片存储时间
* @param width 图片的宽度
* @param height 图片的高度
*/
private void handleMediaRowData(String imagePath, long dateTaken, int width, int height) {
if (checkScreenShot(imagePath, dateTaken, width, height)) {
Log.d(TAG, "ScreenShot: 路径 = " + imagePath + "; 大小 = " + width + " * " + height + "; 时间 = " + dateTaken);
if (mListener != null && !checkCallback(imagePath)) {
mListener.onShot(imagePath);
}
} else {
// 如果在观察区间媒体数据库有数据改变,又不符合截屏规则,则输出到 log 待分析
Log.w(TAG, "Media content changed, but not screenshot: path = " + imagePath + "; size = " + width + " * " + height + "; date = " + dateTaken);
}
}
代码结构相对简单,仅一个工具类即可实现
代码具体的实现细节,是参照上面分析的信息做出来的,如果还有疑问,欢迎留言。
附上Demo源码:https://pan.baidu.com/s/1nw8YeLV