Android截屏功能是一个常用的功能,可以方便的用来分享或者发送给好友,本文介绍了如何实现app内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,进行对图片的二次操作,例如添加二维码,公司logo等一系列*。
项目地址
测试截图:
截屏原理
Android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了。
判断依据
当ContentObserver监听到媒体数据库的数据改变, 在有数据改变时 获取最后插入数据库的一条图片数据, 如果符合以下规则, 则认为截屏了:
- 时间判断,图片的生成时间在开始监听之后,并与当前时间相隔10秒内:开始监听后生成的图片才有意义,相隔10秒内说明是刚刚生成的
- 尺寸判断,图片的尺寸没有超过屏幕的尺寸:图片尺寸超过屏幕尺寸,不可能是截屏图片
- 路径判断,图片路径符合包含特定的关键词:这一点是关键,截屏图片的保存路径通常包含“screenshot”
这些判断是为了增加截屏检测结果的可靠性,防止误报,防止遗漏。其中截屏图片的路径正常Android系统保存的路径格式, 例如我的是:“外部存储器/storage/emulated/0/Pictures/Screenshots/Screenshot_2017-08-03-15-42-58.png”,但Android系统碎片化严重,加上其他第三方截屏APP等,所以路径关键字除了检查是否包含“screenshot”外,还可以适当增加其他关键字,详见最后的监听器完整代码。这种监听截屏的方法也不是100%准确,例如某些被root的机器使用第三方截屏APP自定义保存路径,还比如通过ADB命令在电脑上获取手机屏幕快照均不能监听到,但这也是目前可行性最高的方法,对于绝大多数用户都比较靠谱。
代码描述
监听截屏
public class ScreenShotListenManager {
private static final String TAG = "ScreenShotListenManager";
/**
* 读取媒体数据库时需要读取的列
*/
private static final String[] MEDIA_PROJECTIONS = {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
};
/**
* 读取媒体数据库时需要读取的列, 其中 WIDTH 和 HEIGHT 字段在 API 16 以后才有
*/
private static final String[] MEDIA_PROJECTIONS_API_16 = {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.WIDTH,
MediaStore.Images.ImageColumns.HEIGHT,
};
/**
* 截屏依据中的路径判断关键字
*/
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 static Point sScreenRealSize;
/**
* 已回调过的路径
*/
private final static List<String> sHasCallbackPaths = new ArrayList<String>();
private Context mContext;
private OnScreenShotListener mListener;
private long mStartListenTime;
/**
* 内部存储器内容观察者
*/
private MediaContentObserver mInternalObserver;
/**
* 外部存储器内容观察者
*/
private MediaContentObserver mExternalObserver;
/**
* 运行在 UI 线程的 Handler, 用于运行监听器回调
*/
private final Handler mUiHandler = new Handler(Looper.getMainLooper());
private ScreenShotListenManager(Context context) {
if (context == null) {
throw new IllegalArgumentException("The context must not be null.");
}
mContext = context;
// 获取屏幕真实的分辨率
if (sScreenRealSize == null) {
sScreenRealSize = getRealS