监听Android系统截屏

1. 原理

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

须要ContentObserver监听的资源URI:android

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

2. 具体实现

根据以上原理,咱们能够封装一个工具类来处理系统截屏,具体代码以下:git

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;

/**
 * 系统截屏监听工具,监听系统截屏,而后对截图进行处理
 * <pre>
 *     author  : Fantasy
 *     version : 1.0, 2019-06-05
 *     since   : 1.0, 2019-06-05
 * </pre>
 */
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 中添加权限:github

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

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

获取实例:并发

private ScreenShot mScreenShot = ScreenShot.getInstance();

注册并处理截屏:app

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

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

mScreenShot.unregister();

好了,以上就是 ScreenShot 工具类的使用方式。想看完整代码的同窗,能够到个人GitHub上面看看BlogDemo工具

 

若是想进一步交流和学习的同窗,能够加一下QQ群哦!oop