当前位置: 首页>編程日記>正文

Android 截屏监听分享

Android 截屏监听分享

目录

前言

一、FileObserver对比ContentObserver

二、本文通过ContentObserver实现

三、实测兼容性


前言

网上一般列举的有三种方法

  1. 利用FileObserver监听某个目录中资源变化情况。
  2. 利用ContentObserver监听全部资源的变化。
  3. 监听截屏快捷按键 ( 由于厂商自定义Android系统的多样性,再加上快捷键的不同以及第三方应用,监听截屏快捷键这事基本不靠谱,可以直接忽略 )。

一、FileObserver对比ContentObserver

FileObserver:通过FileObserver监听截屏文件夹,当有新的截屏文件产生时,调用设定的回调函数执行相关操作。

实现简单,但是不同手机默认的截屏路径、事件名称可能不同,需要做适配。

ContentObserver:通过ContentObserver监听图片多媒体的变化,当手机上有新图片文件产生时会通过MediaProvider类向图片数据库插入一条记录,监听图片插入事件来获得图片的URI。

事件监听一致,但是需要对插入的图片进行过滤。

二、本文通过ContentObserver实现

截屏帮助类

/*** @author Alex.yelj* 2020-01-16 11:01 Thursday* Description: 截屏帮助类*/
class ScreenShotHelper {companion object {const val TAG = "ScreenShotLog"/*** 读取媒体数据库时需要读取的列*/val MEDIA_PROJECTIONS = arrayOf(MediaStore.Images.ImageColumns.DATA,MediaStore.Images.ImageColumns.DATE_TAKEN)/*** 读取媒体数据库时需要读取的列,其中 width、height 字段在 API 16 之后才有*/val MEDIA_PROJECTIONS_API_16 = arrayOf(MediaStore.Images.ImageColumns.DATA,MediaStore.Images.ImageColumns.DATE_TAKEN,MediaStore.Images.ImageColumns.WIDTH,MediaStore.Images.ImageColumns.HEIGHT)/*** 截屏路径判断的关键字*/val KEYWORDS = arrayOf("screenshot", "screen_shot", "screen-shot", "screen shot","screencapture", "screen_capture", "screen-capture", "screen capture","screencap", "screen_cap", "screen-cap", "screen cap")fun showLog(msg: String) {Log.d(TAG, msg)}}
}

截屏监听器

/*** @author Alex.yelj* 2020-01-15 17:28 Wednesday* Description: 截屏监听*/
class ScreenShotListener constructor(context: Context?) {private var mContext: Contextprivate var mScreenRealSize: Point? = nullprivate val mHasCallbackPaths: ArrayList<String> = ArrayList()private var mListener: OnScreenShotListener? = nullprivate var mStartListenTime: Long = 0/*** 内部存储器内容观察者*/private var mInternalObserver: MediaContentObserver? = null/*** 外部存储器内容观察者*/private var mExternalObserver: MediaContentObserver? = null/*** 运行在 UI 线程的 Handler, 用于运行监听器回调*/private var mUiHandler = Handler(Looper.getMainLooper())init {ScreenShotHelper.showLog("init")assertInMainThread()requireNotNull(context) { "The context must not be null." }mContext = contextif (mScreenRealSize == null) {mScreenRealSize = getRealScreenSize()if (mScreenRealSize != null) {ScreenShotHelper.showLog("Screen Real Size: " + mScreenRealSize!!.x + " * " + mScreenRealSize!!.y)} else {ScreenShotHelper.showLog("Get screen real size failed.")}}}/*** 单例*/companion object : SingletonHolder<ScreenShotListener, Context>(::ScreenShotListener)/*** 开启监听*/fun startListener() {assertInMainThread()// 记录开始监听的时间戳mStartListenTime = System.currentTimeMillis()// 创建内容观察者mInternalObserver =MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler)mExternalObserver =MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler)// 注册内容观察者mContext.contentResolver.registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI,false,mInternalObserver)mContext.contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,false,mExternalObserver)}fun stopListener() {assertInMainThread()// 注销内容观察者if (mInternalObserver != null) {try {mContext.contentResolver.unregisterContentObserver(mInternalObserver!!)} catch (e: Exception) {e.printStackTrace()}mInternalObserver = null}if (mExternalObserver != null) {try {mContext.contentResolver.unregisterContentObserver(mExternalObserver!!)} catch (e: Exception) {e.printStackTrace()}mExternalObserver = null}// 清空数据mStartListenTime = 0mListener = null}/*** 处理媒体数据库的内容改变*/fun handleMediaContentChange(contentUri: Uri) {var cursor: Cursor? = nulltry {cursor = mContext.contentResolver.query(contentUri,if (Build.VERSION.SDK_INT < 16) ScreenShotHelper.MEDIA_PROJECTIONS else ScreenShotHelper.MEDIA_PROJECTIONS_API_16,null, null,"${MediaStore.Images.ImageColumns.DATE_ADDED} desc limit 1")if (cursor == null) {ScreenShotHelper.showLog("Deviant logic.")return}if (!cursor.moveToFirst()) {ScreenShotHelper.showLog("Cursor no data.")return}// 获取各列的索引val dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)val dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN)var widthIndex = -1var heightIndex = -1if (Build.VERSION.SDK_INT >= 16) {widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH)heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT)}// 获取行数据val data = cursor.getString(dataIndex)val dateTaken = cursor.getLong(dateTakenIndex)var width = 0var height = 0if (widthIndex >= 0 && heightIndex >= 0) {width = cursor.getInt(widthIndex)height = cursor.getInt(heightIndex)} else {val size = getImageSize(data)width = size.xheight = size.y}// 处理获取到的第一行数据handleMediaRowData(data, dateTaken, width, height)} catch (e: Exception) {ScreenShotHelper.showLog("Exception: ${e.message}")e.printStackTrace()} finally {if (cursor != null && !cursor.isClosed) {cursor.close()}}}private fun getImageSize(imagePath: String): Point {val options = BitmapFactory.Options()options.inJustDecodeBounds = trueBitmapFactory.decodeFile(imagePath, options)return Point(options.outWidth, options.outHeight)}/*** 处理获取到的一行数据*/private fun handleMediaRowData(data: String, dateTaken: Long, width: Int, height: Int) {if (checkScreenShot(data, dateTaken, width, height)) {ScreenShotHelper.showLog("ScreenShot: path = $data; size = $width * $height; date = $dateTaken")if (mListener != null && !checkCallback(data)) {mListener!!.onScreenShot(data)}} else {// 如果在观察区间媒体数据库有数据改变,又不符合截屏规则,则输出到 log 待分析ScreenShotHelper.showLog("Media content changed, but not screenshot: path = $data; size = $width * $height; date = $dateTaken")}}/*** 判断指定的数据行是否符合截屏条件*/private fun checkScreenShot(data: String?, dateTaken: Long, width: Int, height: Int): Boolean {// 判断依据一: 时间判断// 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于10秒, 则认为当前没有截屏if (dateTaken < mStartListenTime || System.currentTimeMillis() - dateTaken > 10 * 1000) {return false}// 判断依据二: 尺寸判断if (mScreenRealSize != null) {// 如果图片尺寸超出屏幕, 则认为当前没有截屏if (!(width <= mScreenRealSize!!.x && height <= mScreenRealSize!!.y)|| (height <= mScreenRealSize!!.x && width <= mScreenRealSize!!.y)) {return false}}// 判断依据三: 路径判断if (data.isNullOrEmpty()) {return false}val lowerData = data.toLowerCase(Locale.getDefault())// 判断图片路径是否含有指定的关键字之一, 如果有, 则认为当前截屏了for (keyWork in ScreenShotHelper.KEYWORDS) {if (lowerData.contains(keyWork)) {return true}}return false}/*** 判断是否已回调过, 某些手机ROM截屏一次会发出多次内容改变的通知; <br></br>* 删除一个图片也会发通知, 同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏.*/private fun checkCallback(imagePath: String): Boolean {if (mHasCallbackPaths.contains(imagePath)) {ScreenShotHelper.showLog("ScreenShot: imgPath has done; imagePath = $imagePath")return true}// 大概缓存15~20条记录便可if (mHasCallbackPaths.size >= 20) {for (i in 0..4) {mHasCallbackPaths.removeAt(0)}}mHasCallbackPaths.add(imagePath)return false}/*** 获取屏幕分辨率*/private fun getRealScreenSize(): Point? {var screenSize: Point? = nulltry {screenSize = Point()val windowManager = mContext.getSystemService(Context.WINDOW_SERVICE) as WindowManagerval defaultDisplay = windowManager.defaultDisplayif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {defaultDisplay.getRealSize(screenSize)} else {try {val mGetRawW = Display::class.java.getMethod("getRawWidth")val mGetRawH = Display::class.java.getMethod("getRawHeight")screenSize.set(mGetRawW.invoke(defaultDisplay) as Int,mGetRawH.invoke(defaultDisplay) as Int)} catch (e: Exception) {screenSize.set(defaultDisplay.width, defaultDisplay.height)e.printStackTrace()}}} catch (e: Exception) {e.printStackTrace()}return screenSize}private fun assertInMainThread() {if (Looper.myLooper() != Looper.getMainLooper()) {val stackTrace = Thread.currentThread().stackTracevar methodMsg: String? = nullif (stackTrace != null && stackTrace.size >= 4) {methodMsg = stackTrace[3].toString()}ScreenShotHelper.showLog("Call the method must be in main thread: $methodMsg")}}/*** 媒体内容观察者*/private inner class MediaContentObserver(var contentUri: Uri, handler: Handler) :ContentObserver(handler) {override fun onChange(selfChange: Boolean) {super.onChange(selfChange)handleMediaContentChange(contentUri)}}/*** 设置截屏监听器回调*/fun setListener(listener: OnScreenShotListener) {this.mListener = listener}/*** 截屏监听接口*/interface OnScreenShotListener {fun onScreenShot(picPath: String)}
}

使用

  1. 若要监听整个APP的所有页面,则将监听器加入到BaseActivity,在页面的onResume中开启监听,在onPause停止监听。
  2. 需要读取内部存储(READ_EXTERNAL_STORAGE)权限
/*** @author Alex.yelj* 2020-01-16 14:56 Thursday* Description:*/
class ScreenShotActivity : AppCompatActivity() {private lateinit var screenShotListener: ScreenShotListenervar isHasScreenShotListener = falseoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_screen_shot)screenShotListener = ScreenShotListener.getInstance(this)// 申请权限val permission = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)if (ActivityCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, permission, 1001)}}override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode == 1001) {if (grantResults[0] == PermissionChecker.PERMISSION_GRANTED) {customToast("权限申请成功")} else {customToast("权限申请失败")}}}override fun onResume() {super.onResume()startScreenShotListen()}override fun onPause() {super.onPause()stopScreenShotListen()}private fun startScreenShotListen() {if (!isHasScreenShotListener) {screenShotListener.setListener(object : ScreenShotListener.OnScreenShotListener {override fun onScreenShot(picPath: String) {customToast("监听截屏成功")Log.d(ScreenShotHelper.TAG, picPath)}})screenShotListener.startListener()isHasScreenShotListener = true}}private fun stopScreenShotListen() {if (isHasScreenShotListener) {screenShotListener.stopListener()isHasScreenShotListener = false}}
}

三、实测兼容性

 

来自这位老哥的GitHub,谢谢!
 


https://www.fengoutiyan.com/post/13675.html

相关文章:

  • 苹果怎么取消截屏分享提示
  • 安卓不允许截屏
  • 华为截屏分享怎么取消
  • android如何截屏快捷键
  • 怎样关闭截屏分享
  • 安卓截图工具
  • 安卓监听
  • 怎么打开安卓截屏
  • 鏡像模式如何設置在哪,圖片鏡像操作
  • 什么軟件可以把圖片鏡像翻轉,C#圖片處理 解決左右鏡像相反(旋轉圖片)
  • 手機照片鏡像翻轉,C#圖像鏡像
  • 視頻鏡像翻轉軟件,python圖片鏡像翻轉_python中鏡像實現方法
  • 什么軟件可以把圖片鏡像翻轉,利用PS實現圖片的鏡像處理
  • 照片鏡像翻轉app,java實現圖片鏡像翻轉
  • 什么軟件可以把圖片鏡像翻轉,python圖片鏡像翻轉_python圖像處理之鏡像實現方法
  • matlab下載,matlab如何鏡像處理圖片,matlab實現圖像鏡像
  • 圖片鏡像翻轉,MATLAB:鏡像圖片
  • 鏡像翻轉圖片的軟件,圖像處理:實現圖片鏡像(基于python)
  • canvas可畫,JavaScript - canvas - 鏡像圖片
  • 圖片鏡像翻轉,UGUI優化:使用鏡像圖片
  • Codeforces,CodeForces 1253C
  • MySQL下載安裝,Mysql ERROR: 1253 解決方法
  • 勝利大逃亡英雄逃亡方案,HDU - 1253 勝利大逃亡 BFS
  • 大一c語言期末考試試題及答案匯總,電大計算機C語言1253,1253《C語言程序設計》電大期末精彩試題及其問題詳解
  • lu求解線性方程組,P1253 [yLOI2018] 扶蘇的問題 (線段樹)
  • c語言程序設計基礎題庫,1253號C語言程序設計試題,2016年1月試卷號1253C語言程序設計A.pdf
  • 信奧賽一本通官網,【信奧賽一本通】1253:抓住那頭牛(詳細代碼)
  • c語言程序設計1253,1253c語言程序設計a(2010年1月)
  • 勝利大逃亡英雄逃亡方案,BFS——1253 勝利大逃亡
  • 直流電壓測量模塊,IM1253B交直流電能計量模塊(艾銳達光電)
  • c語言程序設計第三版課后答案,【渝粵題庫】國家開放大學2021春1253C語言程序設計答案
  • 18轉換為二進制,1253. 將數字轉換為16進制
  • light-emitting diode,LightOJ-1253 Misere Nim
  • masterroyale魔改版,1253 Dungeon Master
  • codeformer官網中文版,codeforces.1253 B
  • c語言程序設計考研真題及答案,2020C語言程序設計1253,1253計算機科學與技術專業C語言程序設計A科目2020年09月國家開 放大學(中央廣播電視大學)
  • c語言程序設計基礎題庫,1253本科2016c語言程序設計試題,1253電大《C語言程序設計A》試題和答案200901
  • 肇事逃逸車輛無法聯系到車主怎么辦,1253尋找肇事司機