公司的项目由于安全需要,对某一特定的页面需要监听是否被用户截屏了。
简单搜了一下,很少有这方面的问题,没办法,只能自己折腾了。
目前想到三种思路:
1、监听广播
当然,前提是系统在截屏的时候发送某一广播,然而并没有。
2、监听按键
Android手机按下“电源键+音量减”会进行截屏,此外大部分手机状态栏下拉的页面中也会有截屏按钮。遗憾的是,监听这两处的操作并不是一件让人开心的事儿~~。
3、监听手机中图片的变化
开始只想到了MediaStore这个类,可以通过它拿到手机中的所有图片,每隔一段时间监听图片数量。这似乎是个不错的主意,直到我转角遇到了ContentObserver。
从名字就可以知道,它是一个内容观察者。通过给ContentProvider注册ContentObserver,可以实现对数据的监听。
- public class ScreenshotContentObserver extends ContentObserver {
- private Context mContext;
- private int imageNum;
- private static ScreenshotContentObserver instance;
- private ScreenshotContentObserver(Context context) {
- super(null);
- mContext = context;
- }
- public static void startObserve() {
- if (instance == null) {
- instance = new ScreenshotContentObserver(Facade.context());
- }
- instance.register();
- }
- public static void stopObserve() {
- instance.unregister();
- }
- private void register() {
- mContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, this);
- }
- private void unregister() {
- mContext.getContentResolver().unregisterContentObserver(this);
- }
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- String[] columns = {
- MediaStore.MediaColumns.DATE_ADDED,
- MediaStore.MediaColumns.DATA,
- };
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- columns,
- null,
- null,
- MediaStore.MediaColumns.DATE_MODIFIED + " desc");
- if (cursor == null) {
- return;
- }
- int count = cursor.getCount();
- if (imageNum == 0) {
- imageNum = count;
- } else if (imageNum >= count) {
- return;
- }
- imageNum = count;
- if (cursor.moveToFirst()) {
- String filePath = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA));
- long addTime = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED));
- if (matchAddTime(addTime) && matchPath(filePath) && matchSize(filePath)) {
- doReport(filePath);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (cursor != null) {
- try {
- cursor.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- /**
- * 添加时间与当前时间不超过1.5s,大部分时候不超过1s。
- *
- * @param addTime 图片添加时间,单位:秒
- */
- private boolean matchAddTime(long addTime) {
- return System.currentTimeMillis() - addTime * 1000 < 1500;
- }
- /**
- * 尺寸不大于屏幕尺寸(发现360奇酷手机可以对截屏进行裁剪)
- */
- private boolean matchSize(String filePath) {
- Point size = Util.getScreenWidthAndHeight(mContext);//获取屏幕尺寸
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(filePath, options);
- return size.x >= options.outWidth && size.y >= options.outHeight;
- }
- /**
- * 已调查的手机截屏图片的路径中带有screenshot
- */
- private boolean matchPath(String filePath) {
- String lower = filePath.toLowerCase();
- return lower.contains("screenshot");
- }
- private void doReport(String filePath) {
- //删除截屏
- File file = new File(filePath);
- file.delete();
- //TODO:
- }
- }
上面通过register()和unregister()两个静态方法进行监听器的注册和反注册。建议在onStart()方法中进行注册,在onStop()方法中进行反注册,因为截屏并不会引起当前页面生命周期的变化。
在onChange()回调方法中,通过查询,拿到最近添加的那张图片,从创建时间、尺寸、路径3个方面进行匹配,判断是否是截屏图片。
- 创建时间:大多时候,截屏图片的创建时间和当前系统时间不超过1000ms
- 图片尺寸:大多数手机截屏之后,直接保存图片,所以尺寸和屏幕尺寸一致。但有些手机,比如360奇酷手机,截屏后允许用户裁剪。所以图片尺寸的判断放宽到不大于屏幕尺寸
- 图片路径:目前,大多数手机的截屏路径中包含“screenshot”,还未发现例外
匹配成功后,就可以在doReport中做自己想做的事了~~