功能需求:
App内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,进行对图片的二次操作,例如添加二维码,公司logo等一系列操作。
首先来app界面图及截屏监听图添加效果图

主要是利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了,下面看看截屏管理类

ScreenShotListenManager.java

package com.test.base;import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;import com.test.R;import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;/*** @CreateDate: 2020/9/17* @Author:lp* @Description:*/
public class ScreenShotListenManager {private static final String TAG = "ScreenShotListenManager";/*** 读取媒体数据库时需要读取的列*/private static final String[] MEDIA_PROJECTIONS = {MediaStore.Images.ImageColumns.DATA,MediaStore.Images.ImageColumns.DATE_TAKEN,MediaStore.Images.ImageColumns.DATE_ADDED};/*** 读取媒体数据库时需要读取的列, 其中 WIDTH 和 HEIGHT 字段在 API 16 以后才有*/private static final String[] MEDIA_PROJECTIONS_API_16 = {MediaStore.Images.ImageColumns.DATA,MediaStore.Images.ImageColumns.DATE_TAKEN,MediaStore.Images.ImageColumns.DATE_ADDED,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", "Screenshots", "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 ScreenShotListenManager(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 ScreenShotListenManager newInstance(Context context) {assertInMainThread();return new ScreenShotListenManager(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);//Android Q(10) ContentObserver 不回调 onChangeif (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {// 注册内容观察者mContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI,true,mInternalObserver);mContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,true,mExternalObserver);} else {// 注册内容观察者mContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI,false,mInternalObserver);mContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,false,mExternalObserver);}}/*** 停止监听*/public void stopListen() {assertInMainThread();// 注销内容观察者if (mInternalObserver != null) {try {mContext.getContentResolver().unregisterContentObserver(mInternalObserver);} catch (Exception e) {e.printStackTrace();}mInternalObserver = null;}if (mExternalObserver != null) {try {mContext.getContentResolver().unregisterContentObserver(mExternalObserver);} catch (Exception e) {e.printStackTrace();}mExternalObserver = null;}// 清空数据mStartListenTime = 0;
//        sHasCallbackPaths.clear();//切记!!!:必须设置为空 可能mListener 会隐式持有Activity导致释放不掉mListener = null;}/*** 处理媒体数据库的内容改变*/private void handleMediaContentChange(Uri contentUri) {Cursor cursor = null;try {// 数据改变时查询数据库中最后加入的一条数据cursor = mContext.getContentResolver().query(contentUri,Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,null,null,MediaStore.Images.ImageColumns.DATE_MODIFIED + " 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();}}}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) {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) {/** 判断依据一: 时间判断*/// 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于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) {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;}/*** 获取截屏并进行二次处理** @param context* @param screenFilePath* @return*/public Bitmap createScreenShotBitmap(Context context, String screenFilePath) {View v = LayoutInflater.from(context).inflate(R.layout.screen_view_layout, null);ImageView iv = (ImageView) v.findViewById(R.id.svl_pic);Bitmap bitmap = BitmapFactory.decodeFile(screenFilePath);iv.setImageBitmap(bitmap);//整体布局Point point = getRealScreenSize();v.measure(View.MeasureSpec.makeMeasureSpec(point.x, View.MeasureSpec.EXACTLY),View.MeasureSpec.makeMeasureSpec(point.y, View.MeasureSpec.EXACTLY));v.layout(0, 0, point.x, point.y);//增加的高度120是二次编辑增加的高度(二维码及文本的高度)Bitmap result = Bitmap.createBitmap(v.getWidth(), v.getHeight() + dp2px(context, 120), Bitmap.Config.ARGB_8888);Canvas c = new Canvas(result);c.drawColor(Color.WHITE);// Draw view to canvasv.draw(c);return result;}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;}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);handleMediaContentChange(mContentUri);}}}

里面用到的screen_view_layout布局文件是自定义的二次编辑图片布局

screen_view_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/svl_root"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><ImageViewandroid:id="@+id/svl_pic"android:layout_width="match_parent"android:layout_height="match_parent" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/dp_120"android:gravity="center"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/dp_80"android:layout_height="@dimen/dp_80"android:src="@mipmap/qr_code_icon" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="@dimen/dp_35"android:gravity="center"android:text="二维码"android:textColor="@color/color_132644"android:textSize="@dimen/sp_28" /></LinearLayout></LinearLayout>
</ScrollView>

到这里基本就已经完成了,下面看看如果使用,如果是单个页面就在那个Activity中添加监听就行,如果是整个app都要用就放在BaseActivity中方法不变在onResume开始监听,onPause取消监听

@Overrideprotected void onResume() {super.onResume();startScreenShotListen();}@Overrideprotected void onPause() {super.onPause();stopScreenShotListen();}private ScreenShotListenManager screenShotListenManager;private boolean isHasScreenShotListener = false;/*** 监听*/private void startScreenShotListen() {if (!isHasScreenShotListener && screenShotListenManager != null) {screenShotListenManager.setListener(new ScreenShotListenManager.OnScreenShotListener() {@Overridepublic void onShot(String imagePath) {//得到二次编辑的图片screenShotListenManager.createScreenShotBitmap(BaseActivity.this,imagePath)}});screenShotListenManager.startListen();isHasScreenShotListener = true;}}/*** 停止监听*/public void stopScreenShotListen() {if (isHasScreenShotListener && screenShotListenManager != null) {screenShotListenManager.stopListen();isHasScreenShotListener = false;}}

到这里就结束啦,加油骚年

Android App中监听系统截屏(截屏监听功能)相关推荐

  1. android fragment界面滑动切换效果,Android App中使用ViewPager+Fragment实现滑动切换效果...

    在android应用中,多屏滑动是一种很常见的风格,没有采用viewpager的代码实现会很长,如果采用ViewPager,代码就会短很多,但是使用ViewPager也有弊端:需要导入android- ...

  2. 墨迹天气php,Android_仿墨迹天气在Android App中实现自定义zip皮肤更换,在这里谈一下墨迹天气的换肤 - phpStudy...

    仿墨迹天气在Android App中实现自定义zip皮肤更换 在这里谈一下墨迹天气的换肤实现方式,不过首先声明我只是通过反编译以及参考了一些网上其他资料的方式推测出的换肤原理, 在这里只供参考. 若大 ...

  3. 低功耗蓝牙(BLE)在 Android APP 中的应用

    低功耗蓝牙(BLE)在 Android APP 中的应用 前言 最近公司接了一个新项目,用户可以把自己的乐器跟Phone或Pad连接起来,当弹奏乐器的时候,会把演奏情况同步反馈到设备上,方便用户练习, ...

  4. 通过Appium获取Android app中webview

    因为要测试Android app中嵌入的web页面,所以需要从native切换到webview.网上查了好多帖子,都用到类似下面代码: //判断是否有 WEBVIEW Set<String> ...

  5. Android开发中怎样调用系统Email发送邮件(多种调用方式)

    在Android中调用其他程序进行相关处理,几乎都是使用的Intent,所以,Email也不例外,所谓的调用Email,只是说Email可以接收Intent并做这些事情 我们都知道,在Android中 ...

  6. android 发广播屏蔽home键,如何在Android App中屏蔽(拦截)Home按键及其他按键

    如何在Android App中屏蔽(拦截)Home按键及其他按键 (2013-09-08 13:29:11) 标签: 关键 按键 关键点 方法 安卓 it 编写一个Acitivity,如下所示: 关键 ...

  7. 编译FFmpeg4.1.3并移植到Android app中使用(最详细的FFmpeg-Android编译教程)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/bobcat_kay/article/d ...

  8. Android App兼容 10.0 系统

    北京时间2019年3月14日Google正式对外发布Android Q Beta 1及预览版SDK,这意味着安卓开发者们又即将迎来一年一度的新版本适配工作了.Android Q 为开发者们带来了许多新 ...

  9. 在Android App中集成Google登录

    技术文章 来源:码农网 发布:2016-09-19 浏览:194 摘要:今天,几乎所有的web和移动app都自带谷歌和Facebook登录,这对app开发者和用户来说是一个非常有用的功能,因为几乎每个 ...

  10. android 唱歌打分源码,Android App中使用RatingBar实现星级打分功能的教程

    RatingBar简单介绍RatingBar是基于SeekBar(拖动条)和ProgressBar(状态条)的扩展,用星形来显示等级评定,在使用默认RatingBar时,用户可以通过触摸/拖动/按键( ...

最新文章

  1. linux 编译错误 configure: error: no usable python found at /usr/bin/python2.7
  2. NDuiker项目第2天总结
  3. 三菱plc编程实例3000_三菱入门PLC编程PLC系统程序包括哪些
  4. html中加法,javascript 实现加法运算详解
  5. 数据库工作笔记001---mysql 修改字符集_修改排序规则
  6. hive报错(1)MoveTask
  7. 结合element-ui封装的一个分页函数
  8. Atitit 未来趋势把控的书籍 attilax总结 v3
  9. DE10-Nano Kit
  10. linux分区挂载到内存,ubuntu下SD卡分区与挂载
  11. [计算机毕业设计]深度学习的图标型验证码识别系统
  12. (附源码)计算机毕业设计SSM会议管理系统
  13. 论文转换成引用的参考文献格式
  14. Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制
  15. What Would Warren Do?
  16. Python学习之路36-使用future处理并发
  17. 创新型中小企业申报流程
  18. HTML5编写的小游戏
  19. 【PTA-训练day20】L2-032 彩虹瓶 + L1-080 乘法口诀数列
  20. 王者荣耀测试自己本命英雄软件,王者荣耀中谁是你的本命英雄测试地址 趣推测试王者荣耀中谁是你的本命英雄...

热门文章

  1. Python计算1000以内素数的和
  2. 【python算法系列三】 希尔排序算法
  3. vue plus.webview 实现 类似微信右上角 关闭小程序
  4. 12333提交显示服务器异常,ora-600[12333]错误小结及跟踪方法
  5. ObjectOutputStream and ObjectInputStream 序列化 transient
  6. golang后端面试题总结
  7. 与其在家坐月子,不如来投稿模板赚钱吧!
  8. 从人生迷茫到确定方向,为什么他能做到?
  9. Activiti工作流框架学习笔记(一)
  10. WebGL坐标系及基础几何概念