美文网首页
安卓监听截屏

安卓监听截屏

作者: songjk | 来源:发表于2024-07-08 20:05 被阅读0次

安卓监听截屏,适用于安卓13已下,安卓14以上系统提供了截屏的通知

在activity的onResume中调用 startListen

在activity的onPause中调用 startListen

public class ScreenShotHelper {
private static final String TAG = "ScreenShotHelper";

/**
 * 读取媒体数据库时需要读取的列
 */
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","Screenshot"
};

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 ScreenShotHelper(Context context) {
    if (context == null) {
        throw new IllegalArgumentException("The context must not be null.");
    }
    mContext = context;

    // 获取屏幕真实的分辨率
    if (sScreenRealSize == null) {
        sScreenRealSize = getRealScreenSize();
        if (sScreenRealSize != null) {
            Log.d(TAG, "Screen Real Size: " + sScreenRealSize.x + " * " + sScreenRealSize.y);
        } else {
            Log.w(TAG, "Get screen real size failed.");
        }
    }
}

public static ScreenShotHelper newInstance(Context context) {
    assertInMainThread();
    return new ScreenShotHelper(context);
}

/**
 * 启动监听
 */
public void startListen() {
    assertInMainThread();

   //        sHasCallbackPaths.clear();

    // 记录开始监听的时间戳
    mStartListenTime = System.currentTimeMillis();

    // 创建内容观察者
    mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler);
    mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler);

    // 注册内容观察者
    mContext.getApplicationContext().getContentResolver().registerContentObserver(
            MediaStore.Images.Media.INTERNAL_CONTENT_URI,
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q,
            mInternalObserver
    );
    mContext.getApplicationContext().getContentResolver().registerContentObserver(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q,
            mExternalObserver
    );
}

/**
 * 停止监听
 */
public void stopListen() {
    assertInMainThread();

    // 注销内容观察者
    if (mInternalObserver != null) {
        try {
            mContext.getApplicationContext().getContentResolver().unregisterContentObserver(mInternalObserver);
        } catch (Exception e) {
            e.printStackTrace();
        }
        mInternalObserver = null;
    }
    if (mExternalObserver != null) {
        try {
            mContext.getApplicationContext().getContentResolver().unregisterContentObserver(mExternalObserver);
        } catch (Exception e) {
            e.printStackTrace();
        }
        mExternalObserver = null;
    }

    // 清空数据
    mStartListenTime = 0;
//        sHasCallbackPaths.clear();

    //切记!!!:必须设置为空 可能mListener 会隐式持有Activity导致释放不掉
    mListener = null;
}

/**
 * 处理媒体数据库的内容改变
 */
private void handleMediaContentChange(Uri contentUri) {
    Log.d(TAG,"handleMediaContentChange"+contentUri);
    Cursor cursor = null;
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            String order = MediaStore.Images.ImageColumns.DATE_ADDED + " desc ";
            Bundle queryArgs = createSqlQueryBundle(null, null, order, 1, 0);
            cursor = mContext.getApplicationContext().getContentResolver().query(
                    contentUri,
                    Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
                    queryArgs,
                    null
            );
        }else {
            String order = MediaStore.Images.ImageColumns.DATE_ADDED + " desc ";
            cursor = mContext.getApplicationContext().getContentResolver().query(
                    contentUri,
                    Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
                    null,
                    null,
                    MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
            );
        }
        // 数据改变时查询数据库中最后加入的一条数据


        if (cursor == null) {
            Log.e(TAG, "Deviant logic.");
            return;
        }
        if (!cursor.moveToFirst()) {
            Log.d(TAG, "Cursor no data.");
            return;
        }

        // 获取各列的索引
        int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
        int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
        int widthIndex = -1;
        int heightIndex = -1;
        if (Build.VERSION.SDK_INT >= 16) {
            widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
            heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
        }

        // 获取行数据
        String data = cursor.getString(dataIndex);
        long dateTaken = cursor.getLong(dateTakenIndex);
        int width = 0;
        int height = 0;
        if (widthIndex >= 0 && heightIndex >= 0) {
            width = cursor.getInt(widthIndex);
            height = cursor.getInt(heightIndex);
        } else {
            // API 16 之前, 宽高要手动获取
            Point size = getImageSize(data);
            width = size.x;
            height = size.y;
        }

        // 处理获取到的第一行数据
        handleMediaRowData(data, dateTaken, width, height);

    } catch (Exception e) {
        e.printStackTrace();

    } finally {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }
}
public static Bundle createSqlQueryBundle(String selection, String[] selectionArgs, String sortOrder, int limitCount, int offset) {
    if (selection == null && selectionArgs == null && sortOrder == null) {
        return null;
    } else {
        Bundle queryArgs = new Bundle();
        if (selection != null) {
            queryArgs.putString("android:query-arg-sql-selection", selection);
        }

        if (selectionArgs != null) {
            queryArgs.putStringArray("android:query-arg-sql-selection-args", selectionArgs);
        }

        if (sortOrder != null) {
            queryArgs.putString("android:query-arg-sql-sort-order", sortOrder);
        }

        if (Build.VERSION.SDK_INT >= 30) {
            queryArgs.putInt("android:query-arg-limit", limitCount);
            queryArgs.putInt("android:query-arg-offset", offset);
        }

        return queryArgs;
    }
}
private Point getImageSize(String imagePath) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, options);
    return new Point(options.outWidth, options.outHeight);
}

/**
 * 处理获取到的一行数据
 */
private void handleMediaRowData(String data, long dateTaken, int width, int height) {
    Log.d(TAG,"handleMediaRowData"+data);
    if (checkScreenShot(data, dateTaken, width, height)) {
        Log.d(TAG, "ScreenShot: path = " + data + "; size = " + width + " * " + height
                + "; date = " + dateTaken);
        if (mListener != null && !checkCallback(data)) {
            mListener.onShot(data);
        }
    } else {
        // 如果在观察区间媒体数据库有数据改变,又不符合截屏规则,则输出到 log 待分析
        Log.w(TAG, "Media content changed, but not screenshot: path = " + data
                + "; size = " + width + " * " + height + "; date = " + dateTaken);
    }
}

/**
 * 判断指定的数据行是否符合截屏条件
 */
private boolean checkScreenShot(String data, long dateTaken, int width, int height) {
    Log.d(TAG,"checkScreenShot"+data);
    /*
     * 判断依据一: 时间判断
     */
    // 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于10秒, 则认为当前没有截屏
    if (dateTaken < mStartListenTime || (System.currentTimeMillis() - dateTaken) > 10 * 1000) {
        return false;
    }

    /*
     * 判断依据二: 尺寸判断
     */
    if (sScreenRealSize != null) {
        // 如果图片尺寸超出屏幕, 则认为当前没有截屏
        if (!((width <= sScreenRealSize.x && height <= sScreenRealSize.y)
                || (height <= sScreenRealSize.x && width <= sScreenRealSize.y))) {
            return false;
        }
    }

    /*
     * 判断依据三: 路径判断
     */
    if (TextUtils.isEmpty(data)) {
        return false;
    }
    data = data.toLowerCase();
    // 判断图片路径是否含有指定的关键字之一, 如果有, 则认为当前截屏了
    for (String keyWork : KEYWORDS) {
        if (data.contains(keyWork)) {
            return true;
        }
    }

    return false;
}

/**
 * 判断是否已回调过, 某些手机ROM截屏一次会发出多次内容改变的通知; <br/>
 * 删除一个图片也会发通知, 同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏.
 */
private boolean checkCallback(String imagePath) {
    if (sHasCallbackPaths.contains(imagePath)) {
        Log.d(TAG, "ScreenShot: imgPath has done"
                + "; imagePath = " + imagePath);
        return true;
    }
    // 大概缓存15~20条记录便可
    if (sHasCallbackPaths.size() >= 20) {
        for (int i = 0; i < 5; i++) {
            sHasCallbackPaths.remove(0);
        }
    }
    sHasCallbackPaths.add(imagePath);
    return false;
}

/**
 * 获取屏幕分辨率
 */
private Point getRealScreenSize() {
    Point screenSize = null;
    try {
        screenSize = new Point();
        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        Display defaultDisplay = windowManager.getDefaultDisplay();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            ((Display) defaultDisplay).getRealSize(screenSize);
        } else {
            try {
                Method mGetRawW = Display.class.getMethod("getRawWidth");
                Method mGetRawH = Display.class.getMethod("getRawHeight");
                screenSize.set(
                        (Integer) mGetRawW.invoke(defaultDisplay),
                        (Integer) mGetRawH.invoke(defaultDisplay)
                );
            } catch (Exception e) {
                screenSize.set(defaultDisplay.getWidth(), defaultDisplay.getHeight());
                e.printStackTrace();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return screenSize;
}


private int dp2px(Context ctx, float dp) {
    float scale = ctx.getResources().getDisplayMetrics().density;
    return (int) (dp * scale + 0.5f);
}

/**
 * 设置截屏监听器
 */
public void setListener(OnScreenShotListener listener) {
    mListener = listener;
}

public interface OnScreenShotListener {
    void onShot(String imagePath);
}

private static void assertInMainThread() {
    if (Looper.myLooper() != Looper.getMainLooper()) {
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        String methodMsg = null;
        if (elements != null && elements.length >= 4) {
            methodMsg = elements[3].toString();
        }
        throw new IllegalStateException("Call the method must be in main thread: " + methodMsg);
    }
}

/**
 * 媒体内容观察者(观察媒体数据库的改变)
 */
private class MediaContentObserver extends ContentObserver {

    private Uri mContentUri;

    public MediaContentObserver(Uri contentUri, Handler handler) {
        super(handler);
        mContentUri = contentUri;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        handleMediaContentChange(mContentUri);
    }
}


}

相关文章

  • adb实用命令

    截屏: windows批处理命令: linux(MAC)命令: 安装APK: 卸载apk: 清理app缓存: 安卓...

  • 截屏状态监听 - iOS

    既接到电话状态监听的需求之后再次添加了截屏状态的监听,当使用 App 时若用户执行截屏操作需要对当前状态进行监听操...

  • Android开发小技巧 | 一句命令搞定截屏

    -- 作者 谢恩铭 转载请注明出处 一句命令搞定截屏 在安卓开发中, 我们很多时候都要用到截屏这个功能。 有时是为...

  • 实现android截屏

    仅仅实现了安卓平台的截屏,ios学习中: PS:有时候截屏不需要UI,此时需要新建摄像机,单单负责渲染UI,ARC...

  • ios 禁止截屏、录屏 监听事件做弹框提示

    ios不能做到禁止截屏和录屏,只能使用通知监听到截屏或录屏事件,来做一些处理,比如停止播放视频等。 如果想要监听整...

  • iOS 截屏&长截屏

    截屏在 iOS 开发中经常用到,本篇文章讲的是监听用户截屏操作,并且获取截屏图片,如果当前是UIScrollVie...

  • 安卓android Q(10)截屏截图监听适配 ContentO

    目前主流的截屏监听使用的是ContentObserver添加权限 写好工具类 使用方式 目前测试对于Android...

  • Android 监听系统截屏操作(已适配Android Q)

    Android系统没有提供现成的Api来监听到用户的截屏操作,所以我们需要自己实现对用户截屏的监听。 针对这个特殊...

  • Android 手势密码锁的使用细说

    监听屏幕锁屏和解锁事件 启动时进行检测 基类中全局进行监听 构造广播监听锁屏截屏事件 应用Application里...

  • Android监听手机截屏事件

    1、前言 网上监听手机截屏方法教程很多了,我就记录下在学习项目中,项目用到的监听截屏方法。项目用ContentOb...

网友评论

      本文标题:安卓监听截屏

      本文链接:https://www.haomeiwen.com/subject/zrracjtx.html