Android App内监听截图加二维码功能代码

Android截屏功能是一个常用的功能,可以方便的用来分享或者发送给好友,本文介绍了如何实现app内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,进行对图片的二次操作,例如添加二维码,公司logo等一系列*。

项目地址

测试截图:

截屏原理

android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了。

判断依据

当ContentObserver监听到媒体数据库的数据改变, 在有数据改变时 获取最后插入数据库的一条图片数据, 如果符合以下规则, 则认为截屏了:

  • 时间判断,图片的生成时间在开始监听之后,并与当前时间相隔10秒内:开始监听后生成的图片才有意义,相隔10秒内说明是刚刚生成的
  • 尺寸判断,图片的尺寸没有超过屏幕的尺寸:图片尺寸超过屏幕尺寸,不可能是截屏图片
  • 路径判断,图片路径符合包含特定的关键词:这一点是关键,截屏图片的保存路径通常包含“screenshot”

这些判断是为了增加截屏检测结果的可靠性,防止误报,防止遗漏。其中截屏图片的路径正常Android系统保存的路径格式, 例如我的是:“外部存储器/storage/emulated/0/Pictures/Screenshots/Screenshot_2017-08-03-15-42-58.png”,但Android系统碎片化严重,加上其他第三方截屏APP等,所以路径关键字除了检查是否包含“screenshot”外,还可以适当增加其他关键字,详见最后的监听器完整代码。这种监听截屏的方法也不是100%准确,例如某些被root的机器使用第三方截屏APP自定义保存路径,还比如通过ADB命令在电脑上获取手机屏幕快照均不能监听到,但这也是目前可行性最高的方法,对于绝大多数用户都比较靠谱。

代码描述

监听截屏

public class ScreenShotListenManager {
  private static final String TAG = "ScreenShotListenManager";
  /**
   * 读取媒体数据库时需要读取的列
   */
  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"
  };
  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);
    // 注册内容观察者
    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_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();
      }
    }
  }
  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;
  }
  public Bitmap createScreenShotBitmap(Context context, String screenFilePath) {
    View v = LayoutInflater.from(context).inflate(R.layout.share_screenshot_layout, null);
    ImageView iv = (ImageView) v.findViewById(R.id.iv);
    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);
//    Bitmap result = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.RGB_565);
    Bitmap result = Bitmap.createBitmap(v.getWidth(), v.getHeight() + dp2px(context, 140), Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(result);
    c.drawColor(Color.WHITE);
    // Draw view to canvas
    v.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;
    }
    @Override
    public void onChange(boolean selfChange) {
      super.onChange(selfChange);
      handleMediaContentChange(mContentUri);
    }
  }
}

全局使用

我们需求是要在APP中全局都能监听截屏操作,所以,我们只需要在BaseActivity中进行监听就可以了。

@Override
protected void onResume() {
  super.onResume();
  startScreenShotListen();
}
@Override
protected void onPause() {
  super.onPause();
  stopScreenShotListen();
}
/**
 * 监听
 */
private void startScreenShotListen() {
  if (!isHasScreenShotListener && screenShotListenManager != null) {
    screenShotListenManager.setListener(new ScreenShotListenManager.OnScreenShotListener() {
      @Override
      public void onShot(String imagePath) {
        path = imagePath;
        Log.d("msg", "BaseActivity -> onShot: " + "获得截图路径:" + imagePath);
        MyDialog ksDialog = MyDialog.getInstance()
            .init(BaseActivity.this, R.layout.dialog_layout)
            .setCancelButton("取消", null)
            .setPositiveButton("查看", new MyDialog.OnClickListener() {
              @Override
              public void OnClick(View view) {
                Bitmap screenShotBitmap = screenShotListenManager.createScreenShotBitmap(mContext, path);
                // 此处只要分享这个合成的Bitmap图片就行了
                // 为了演示,故写下面代码
                screenShotIv.setImageBitmap(screenShotBitmap);
              }
            });
        screenShotIv = (ImageView) ksDialog.getView(R.id.iv);
        progressBar = (ProgressBar) ksDialog.getView(R.id.avLoad);
        mHandler.postDelayed(new Runnable() {
          @Override
          public void run() {
            progressBar.setVisibility(View.GONE);
            Glide.with(mContext).load(path).into(screenShotIv);
          }
        }, 1500);
      }
    });
    screenShotListenManager.startListen();
    isHasScreenShotListener = true;
  }
}
/**
 * 停止监听
 */
private void stopScreenShotListen() {
  if (isHasScreenShotListener && screenShotListenManager != null) {
    screenShotListenManager.stopListen();
    isHasScreenShotListener = false;
  }
}

至此APP内监听截屏操作就完成了,我们需要在baseActivity中执行监听并执行相应操作,不需要写更多代码。

源码地址>>

总结

以上所述是小编给大家介绍的Android App内监听截图加二维码功能代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 月下载量上千次Android实现二维码生成器app源码分享

    在360上面上线了一个月,下载量上千余次.这里把代码都分享出来,供大家学习哈!还包括教大家如何接入广告,赚点小钱花花,喜欢的帮忙顶一个,大神见了勿喷,小学僧刚学Android没多久.首先介绍这款应用:APP是一款二维码生成器,虽然如何制作二维码教程网上有很多,我这里再唠叨一下并把我的所有功能模块代码都分享出来. 在这里我们需要一个辅助类RGBLuminanceSource,这个类Google也提供了,我们直接粘贴过去就可以使用了 package com.njupt.liyao; import c

  • Android App内监听截图加二维码功能代码

    Android截屏功能是一个常用的功能,可以方便的用来分享或者发送给好友,本文介绍了如何实现app内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,进行对图片的二次操作,例如添加二维码,公司logo等一系列*. 项目地址 测试截图: 截屏原理 android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现.Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可

  • Android详细讲解谷歌推出的官方二维码扫描库

    相信二维码扫描现在大家都已经不稀奇了,几乎所有的App里都会支持这个功能. 这里我要问大家一个问题,你们都是如何在自己的App中加入二维码扫描功能的呢? 相信会有一大部分朋友说,使用的是ZXing或者ZBar这种开源库. 但是不知道大家有没有思考过,二维码功能这么常见,为什么Google却没有提供一个官方的二维码扫描库呢? 反正我是没思考过.有需求,找开源,这可能已经成了很多Android开发者的常态化思维. 但令我没想到的是,官方的二维码扫描库,它真的要来了. 由于我是Google的GDE,有

  • Android 二维码扫描和生成二维码功能

    在APP开发中,常遇到二维码扫描功能和生成二维码的需求.Android大部分是集成了zxing这个开源项目的扫码功能. 开源项目地址 下面给大家介绍一下具体的集成步骤 集成步骤 参考demo 1.demo展示如下: 1.1demo首页 1.2扫描界面 可以根据需求修改,我实际项目中界面截图如下: 1.3生成二维码 2.引入文件 2.1 下载demo,拷贝demo中的com.google.zxing5个包和com.utils包引入到自己的项目中. 2.2 拷贝本项目demo中的布局activity

  • Android WebView实现长按保存图片及长按识别二维码功能

    先来简单说一下本文所要实现的功能:用户在浏览网页的时候,长按某一区域,识别如果是图片,则弹出弹框,出现保存图片的功能.同时识别图片是否是二维码,如果是则在弹框中追加识别二维码功能. 细节上:保存图片的弹框要显示在手指长按的位置:选择图片保存后,可以让用户直接去相册查看:选择识别二维码,判断是是不是网址,是的话可以让用户选择复制或访问,否则可以让用户选择复制或搜索. 然后再来看一下效果图: 保存图片 save.gif 识别包含普通文字的二维码: text.gif 识别包含网址的二维码: code.

  • Android 点击生成二维码功能实现代码

    先看效果: 输入内容,点击生成二维码: 点击logo图案: 代码: QRCodeUtil: package com.example.administrator.zxing; import android.graphics.Bitmap; import android.graphics.Canvas; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zx

  • Android studio 实现手机扫描二维码功能

    安卓手机版本在6.0以后需要动态获取相机权限 1.获取相机权限 <!-- 获取手机相机的权限 --> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.FLASHLIGHT" /> 2.添加依赖 implementation 'cn.yipianfengye.a

  • Android生成条形码和二维码功能

    背景: 随着移动互联网的普及以及智能终端设备的广泛应用,移动支付变得越来越便捷,通过扫描二维码代替传统的刷卡行为.那么作为开发者而言生成二维码成为了一项必备技能. 准备: 使用zxing包 implementation "com.google.zxing:core:3.3.1" 核心代码: package com.wangpengpro.h5test.utils; import android.graphics.Bitmap; import com.google.zxing.Barco

  • 微信小程序webview实现长按点击识别二维码功能示例

    本文实例讲述了微信小程序webview实现长按点击识别二维码功能.分享给大家供大家参考,具体如下: 场景:微信小程序,使用webview控件.需求:点击图片后长按图片出现"识别二维码" 1.JS代码: <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script> <script type="text/javascript"> $(fu

  • Flutter实现扫二维码功能

    本文实例为大家分享了Flutter实现扫二维码功能的具体代码,供大家参考,具体内容如下 首先在pubspec.yaml中添加: dependencies:   qrscan: ^0.3.2   event_bus: ^2.0.0 在androd清单文件中加入以下权限: <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="andr

  • Yii2.0实现生成二维码功能实例

    本文实例讲述了Yii2.0实现生成二维码功能.分享给大家供大家参考,具体如下: 通过composer安装: 1.下面的方法是通过composer加载 php composer.phar require "2amigos/yii2-qrcode-helper" "*" 或者添加 "2amigos/yii2-qrcode-helper" : "*" 到对应项目的composer.json文件中 通过归档文件安装: 不习惯用comp

随机推荐