监听Android系统截屏

1. 原理

因为Android系统没有提供截屏的相关API,所以需要我们自己去实现。国内的Android手机都是使用定制系统的,截图方式五花八门,采用对截图按键的监听的方案并不合适。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为截屏了。

需要ContentObserver监听的资源URI:

  • 内部存储器内容:MediaStore.Images.Media.INTERNAL_CONTENT_URI
  • 外部存储器内容:MediaStore.Images.Media.EXTERNAL_CONTENT_URI

2. 具体实现

根据以上原理,我们可以封装一个工具类来处理系统截屏,具体代码如下:

package com.fantasy.blogdemo.screenshot;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.util.Log;

/**
 * 系统截屏监听工具,监听系统截屏,然后对截图进行处理
 * 
 *     author  : Fantasy
 *     version : 1.0, 2019-06-05
 *     since   : 1.0, 2019-06-05
 * 
*/ public class ScreenShot { private static final String TAG = "ScreenShot"; private static final String[] MEDIA_PROJECTIONS = { MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.DATE_TAKEN, MediaStore.Images.ImageColumns.DATE_ADDED }; /** * 截屏依据中的路径判断关键字 */ 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 ContentResolver mContentResolver; private CallbackListener mCallbackListener; private MediaContentObserver mInternalObserver; private MediaContentObserver mExternalObserver; private static ScreenShot mInstance; private ScreenShot() { } /** * 获取 ScreenShot 对象 * * @return ScreenShot对象 */ public static ScreenShot getInstance() { if (mInstance == null) { synchronized (ScreenShot.class) { mInstance = new ScreenShot(); } } return mInstance; } /** * 注册 * * @param context 上下文 * @param callbackListener 回调监听 */ public void register(Context context, CallbackListener callbackListener) { mContentResolver = context.getContentResolver(); mCallbackListener = callbackListener; HandlerThread handlerThread = new HandlerThread(TAG); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()); mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, handler); mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, handler); mContentResolver.registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, mInternalObserver); mContentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mExternalObserver); } /** * 注销 */ public void unregister() { if (mContentResolver != null) { mContentResolver.unregisterContentObserver(mInternalObserver); mContentResolver.unregisterContentObserver(mExternalObserver); } } private void handleMediaContentChange(Uri uri) { Cursor cursor = null; try { // 数据改变时,查询数据库中最后加入的一条数据 cursor = mContentResolver.query(uri, 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 dateAddedIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_ADDED); // 处理获取到的第一行数据 handleMediaRowData(cursor.getString(dataIndex), cursor.getLong(dateAddedIndex)); } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } } /** * 处理监听到的资源 */ private void handleMediaRowData(String path, long dateAdded) { long duration = 0; long step = 100; // 发现个别手机会自己修改截图文件夹的文件,截屏功能会误以为是用户在截屏操作,进行捕获,所以加了一个时间判断 if (!isTimeValid(dateAdded)) { Log.d(TAG, "图片插入时间大于1秒,不是截屏"); return; } // 设置最大等待时间为500ms,因为魅族的部分手机保存截图有延迟 while (!checkScreenShot(path) && duration <= 500) { try { duration += step; Thread.sleep(step); } catch (Exception e) { e.printStackTrace(); } } if (checkScreenShot(path)) { if (mCallbackListener != null) { mCallbackListener.onShot(path); } } } /** * 判断时间是否合格,图片插入时间小于1秒才有效 */ private boolean isTimeValid(long dateAdded) { return Math.abs(System.currentTimeMillis() / 1000 - dateAdded) <= 1; } private boolean checkScreenShot(String path) { if (path == null) { return false; } path = path.toLowerCase(); for (String keyword : KEYWORDS) { if (path.contains(keyword)) { return true; } } return false; } /** * 媒体内容观察者 */ private class MediaContentObserver extends ContentObserver { private Uri mUri; MediaContentObserver(Uri uri, Handler handler) { super(handler); mUri = uri; } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); //Log.d("ScreenShot", "图片数据库发生变化:" + selfChange); handleMediaContentChange(mUri); } } /** * 回调监听器 */ public interface CallbackListener { /** * 截屏 * * @param path 图片路径 */ void onShot(String path); } }

3. 使用方式

在 AndroidManifest.xml 中添加权限:

在Android 6.0及以上系统需要动态申请权限,这里不提供代码。有兴趣想知道我怎么实现的,可以看看我提供的demo(demo的访问地址在文末提供)。

获取实例:

private ScreenShot mScreenShot = ScreenShot.getInstance();

注册并处理截屏:

mScreenShot.register(mContext, new ScreenShot.CallbackListener() {
    @Override
    public void onShot(String path) {
        // 捕获到系统截屏,path是截屏的绝对路径
    }
});

不需要监听系统截屏的话,需要取消注册:

mScreenShot.unregister();

好了,以上就是 ScreenShot 工具类的使用方式。想看完整代码的同学,可以到我的GitHub上面看看BlogDemo。

 

如果想进一步交流和学习的同学,可以加一下QQ群哦!

你可能感兴趣的:(Android)