这种方案网上有很多教程,这里主要记录我在实现过程中遇到的问题,先上完整代码
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import com.greenleaf.tools.BaseLog;
import com.greenleaf.tools.MobileApplication;
/**
* Created by zhujianyu.
* on Date: 2023/3/13.
* Description:
*/
public class ScreenShotHelper implements OnMediaContentListener {
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 final MediaContentObserver mInternalObserver;
/**
* 外部存储器内容观察者
*/
private String lastData;
private volatile OnScreenShotListener listener;
private final MediaContentObserver mExternalObserver;
private final ContentResolver mResolver;
private final Handler mHandler;
private final 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 static class Instance {
static ScreenShotHelper mInstance = new ScreenShotHelper();
}
public static ScreenShotHelper get() {
return Instance.mInstance;
}
private ScreenShotHelper(){
HandlerThread mHandlerThread = new HandlerThread("Screenshot_Observer");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
// 初始化
mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mHandler);
mInternalObserver.setMediaContentListener(this);
mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mHandler);
mExternalObserver.setMediaContentListener(this);
mResolver = MobileApplication.getInstance().getContentResolver();
// 添加监听
mResolver.registerContentObserver(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
true,
mInternalObserver
);
mResolver.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
true,
mExternalObserver
);
}
//添加事件
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 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;
}
/**
* 处理媒体内容变更
* @param contentUri 媒体内容路径
*/
@Override
public void handleMediaContentChange(Uri contentUri) {
Cursor cursor = null;
try {
// 数据改变时查询数据库中最后加入的一条数据
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Bundle bundle = new Bundle();
bundle.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, new String[]{MediaStore.Images.ImageColumns.DATE_ADDED});
bundle.putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING);
bundle.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
cursor = mResolver.query(contentUri, MEDIA_PROJECTIONS, bundle, null);
}else {
String sortOrder = MediaStore.Images.ImageColumns.DATE_ADDED+" DESC limit 1";
cursor = mResolver.query(contentUri, MEDIA_PROJECTIONS, null, null, sortOrder);
}
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);
// 获取行数据
final String data = cursor.getString(dataIndex);
long dateTaken = cursor.getLong(dateTakenIndex);
if (data.length() > 0) {
long currentTime = System.currentTimeMillis() - dateTaken;
//当前图片是截图,且截图时间在3秒内
if (checkScreenShot(data) && currentTime < 3 * 1000) {
mHandler.removeCallbacks(shotCallBack);
lastData = data;
mHandler.postDelayed(shotCallBack, 500);
}
}
} catch (Exception e) {
BaseLog.e(e.getMessage());
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
}
}
这种方案主要是通过监听系统图片数据库中数据的变化,拿到图片路径与关键字对比,判断图片是否是截图,主要看一下这个函数
handleMediaContentChange(Uri contentUri)
每次图片数据有变化会执行,包括截图、图片重命名、图片删除、有其他图片保存
这里是我遇到的问题,部分手机会报:java.lang.IllegalArgumentException: Invalid token limit异常,主要出现在Android11以上会出现,所以使用一下方法解决
// 数据改变时查询数据库中最后加入的一条数据
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Bundle bundle = new Bundle();
bundle.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, new String[]{MediaStore.Images.ImageColumns.DATE_ADDED});
bundle.putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING);
bundle.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
cursor = mResolver.query(contentUri, MEDIA_PROJECTIONS, bundle, null);
}else {
String sortOrder = MediaStore.Images.ImageColumns.DATE_ADDED+" DESC limit 1";
cursor = mResolver.query(contentUri, MEDIA_PROJECTIONS, null, null, sortOrder);
}
第二个问题,每个Activity只能监听到一次,解决方法放在onWindowFocusChanged中初始化
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
//初始化系统截屏监听
ScreenShotHelper.get().setScreenShotListener(this);
}
}