Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
DCloud
Hello UTS
提交
0e2b8991
H
Hello UTS
项目概览
DCloud
/
Hello UTS
通知
1598
Star
27
Fork
9
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
2
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
H
Hello UTS
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
2
Issue
2
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
0e2b8991
编写于
9月 28, 2022
作者:
杜庆泉
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
android 截屏示例完善
上级
694551f9
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
268 addition
and
214 deletion
+268
-214
pages/SystemAPI/SystemAPI.vue
pages/SystemAPI/SystemAPI.vue
+23
-9
uni_modules/uts-screenshot-listener/utssdk/app-android/index.uts
...ules/uts-screenshot-listener/utssdk/app-android/index.uts
+245
-205
uni_modules/uts-screenshot-listener/utssdk/app-android/libs/androidx.annotation-1.2.0.jar
...ner/utssdk/app-android/libs/androidx.annotation-1.2.0.jar
+0
-0
未找到文件。
pages/SystemAPI/SystemAPI.vue
浏览文件 @
0e2b8991
...
...
@@ -4,7 +4,9 @@
<view
class=
"uni-btn-v uni-common-mt"
>
<button
type=
"primary"
@
tap=
"testGetBatteryCapacity"
>
获取电池电量
</button>
<button
type=
"primary"
@
tap=
"testGotoDemoActivity"
>
跳转至新的原生页面
</button>
<button
type=
"primary"
@
tap=
"testScreenShotListen"
>
监听截图事件
</button>
<button
type=
"primary"
@
tap=
"testScreenShotPremission"
>
准备截屏监听权限
</button>
<button
type=
"primary"
@
tap=
"testScreenShotListen"
>
监听截屏事件
</button>
<button
type=
"primary"
@
tap=
"testScreenShotOff"
>
关闭截屏监听
</button>
<image
:src=
"screenImage"
class=
"screenImage"
mode=
"aspectFit"
></image>
</view>
</view>
...
...
@@ -14,7 +16,8 @@
import
gotoDemoActivity
from
"
@/uni_modules/uts-nativepage
"
;
import
{
requestPremission
,
initAppLifecycle
onUserCaptureScreen
,
offUserCaptureScreen
}
from
"
@/uni_modules/uts-screenshot-listener
"
;
export
default
{
...
...
@@ -37,26 +40,37 @@
}
})
},
testScreenShot
Liste
n
()
{
testScreenShot
Premissio
n
()
{
// 请求写入储存的权限
requestPremission
();
},
testScreenShotListen
()
{
var
that
=
this
;
initAppLifecycle
({
onImageCatchChange
:
function
(
imagePath
)
{
console
.
log
(
imagePath
);
that
.
screenImage
=
imagePath
onUserCaptureScreen
(
function
(
res
)
{
console
.
log
(
res
);
that
.
screenImage
=
res
uni
.
showToast
({
icon
:
"
none
"
,
title
:
'
截屏捕捉成功
'
})
}
});
});
// 提示已经开始监听,注意观察
uni
.
showToast
({
icon
:
"
none
"
,
title
:
'
截屏监听已开启,注意观察下方Image组件
'
})
},
testScreenShotOff
()
{
var
that
=
this
;
offUserCaptureScreen
(
function
(
res
)
{
console
.
log
(
res
);
});
// 提示已经开始监听,注意观察
uni
.
showToast
({
icon
:
"
none
"
,
title
:
'
截屏监听已关闭
'
})
},
testGotoDemoActivity
()
{
gotoDemoActivity
();
...
...
uni_modules/uts-screenshot-listener/utssdk/app-android/index.uts
浏览文件 @
0e2b8991
...
...
@@ -26,7 +26,10 @@ import BitmapFactory from "android.graphics.BitmapFactory";
import Display from "android.view.Display";
import Locale from "java.util.Locale";
import WindowManager from "android.view.WindowManager";
import FileObserver from "android.os.FileObserver";
import File from "java.io.File";
import RequiresApi from "androidx.annotation.RequiresApi";
import Environment from "android.os.Environment";
/**
* 读取媒体数据库时需要读取的列,其中 width、height 字段在 API 16 之后才有
...
...
@@ -49,245 +52,226 @@ const KEYWORDS = arrayOf(
/**
* android 10版本以上通过文件监听实现
*/
@RequiresApi(Build.VERSION_CODES.Q)
class ScreenFileObserver extends FileObserver {
allScreen: File;
constructor(screenFile: File) {
super(screenFile)
this.allScreen = screenFile;
}
override onEvent(event: Int, path?: String): void {
if (event == FileObserver.CREATE) {
var newPath: string = new File(allScreen, path).path;
let currentTime = System.currentTimeMillis();
if ((currentTime - lastFileObserverTime) < 1000) {
// 本地截屏行为比上一次超过1000ms,才认为是一个有效的时间
return;
}
lastFileObserverTime = System.currentTimeMillis()
listenOption!.onImageCatchChange(newPath)
}
}
}
/**
* 屏幕尺寸
*/
let mScreenRealSize: Point
|
null = getRealScreenSize();
let mScreenRealSize: Point
|
null = getRealScreenSize();
let mHasCallbackPaths: ArrayList<string> = new ArrayList()
let mStartListenTime: number = 0;
let mUiHandler: Handler = Handler(Looper.getMainLooper())
/**
* 内部文件监听器
* 内部
媒体
文件监听器
*/
let mInternalObserver: MediaContentObserver
|
null = null;
let mInternalObserver: MediaContentObserver
|
null = null;
/**
* 外部文件监听器
* 外部
媒体
文件监听器
*/
let mExternalObserver: MediaContentObserver|null = null;
let mExternalObserver: MediaContentObserver | null = null;
/**
* 开启监听
*/
function startListener(): void {
* android 10 版本使用的文件监听器
*/
let screenOB: ScreenFileObserver | null = null;
// 记录开始监听的时间戳
mStartListenTime = System.currentTimeMillis()
type onImageCatchOptions = {
onImageCatchChange: (res: string) => void;
};
// 创建内容观察者
mInternalObserver =
MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler)
mExternalObserver =
MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler)
let listenOption: onImageCatchOptions = new onImageCatchOptions();
// 注册内容观察者
getUniActivity()!.contentResolver.registerContentObserver(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
false,
mInternalObserver!
)
getUniActivity()!.contentResolver.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
false,
mExternalObserver!
)
}
let lastFileObserverTime: number = 0;
/**
* 停止监听
*/
function stopListener() {
/**
* 处理媒体数据库的内容改变
*/
function handleMediaContentChange(contentUri: Uri) {
let cursor: Cursor | null = null;
try {
cursor = getUniActivity()!.contentResolver.query(
contentUri,
MEDIA_PROJECTIONS_API_16,
null, null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
)!
if (cursor == null) {
return
}
// 注销内容观察者
if (mInternalObserver != null) {
try {
getUniActivity()!.contentResolver.unregisterContentObserver(mInternalObserver!)
} catch (e) {
e.printStackTrace()
}
mInternalObserver = null
if (!cursor.moveToFirst()) {
return
}
if (mExternalObserver != null) {
try {
getUniActivity()!.contentResolver.unregisterContentObserver(mExternalObserver!)
} catch (e) {
e.printStackTrace()
}
mExternalObserver = null
// 获取各列的索引
let dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
let dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN)
let widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH)
let heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT)
// 获取行数据
let data = cursor.getString(dataIndex)
let dateTaken = cursor.getLong(dateTakenIndex)
let width = 0;
let height = 0;
if (widthIndex >= 0 && heightIndex >= 0) {
width = cursor.getInt(widthIndex)
height = cursor.getInt(heightIndex)
} else {
let size = getImageSize(data)
width = size.x
height = size.y
}
// 清空数据
mStartListenTime = 0
// 处理获取到的第一行数据
handleMediaRowData(data, dateTaken, width, height)
} catch (e) {
e.printStackTrace()
} finally {
if (cursor != null && !cursor.isClosed) {
cursor.close()
}
}
}
/**
* 处理媒体数据库的内容改变
*/
function handleMediaContentChange(contentUri: Uri) {
let cursor: Cursor|null = null;
try {
cursor = getUniActivity()!.contentResolver.query(
contentUri,
MEDIA_PROJECTIONS_API_16,
null, null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
)!
if (cursor == null) {
return
}
if (!cursor.moveToFirst()) {
return
}
// 获取各列的索引
let dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
let dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN)
var widthIndex = -1
var heightIndex = -1
if (Build.VERSION.SDK_INT >= 16) {
widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH)
heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT)
}
/**
* 获取媒体库内置图像的大小
*/
function getImageSize(imagePath: string): Point {
let options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(imagePath, options)
return Point(options.outWidth, options.outHeight)
}
// 获取行数据
let data = cursor.getString(dataIndex)
let dateTaken = cursor.getLong(dateTakenIndex)
var width = 0
var height = 0
if (widthIndex >= 0 && heightIndex >= 0) {
width = cursor.getInt(widthIndex)
height = cursor.getInt(heightIndex)
} else {
let size = getImageSize(data)
width = size.x
height = size.y
}
/**
* 处理获取到的一行数据
*/
function handleMediaRowData(data: String, dateTaken: Long, width: Int, height: Int) {
// 处理获取到的第一行数据
handleMediaRowData(data, dateTaken, width, height)
} catch (e) {
console.log(e);
e.printStackTrace()
} finally {
if (cursor != null && !cursor.isClosed) {
cursor.close()
}
if (checkScreenShot(data, dateTaken, width, height)) {
if (!checkCallback(data)) {
listenOption!.onImageCatchChange(data)
}
} else {
// 如果在观察区间媒体数据库有数据改变,又不符合截屏规则
}
}
function getImageSize(imagePath: string): Point {
let options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(imagePath, options)
return Point(options.outWidth, options.outHeight)
}
/**
* 处理获取到的一行数据
*/
function handleMediaRowData(data: String, dateTaken: Long, width: Int, height: Int) {
if (checkScreenShot(data, dateTaken, width, height)) {
if (!checkCallback(data)) {
listenOption!.onImageCatchChange(data)
}
} else {
// 如果在观察区间媒体数据库有数据改变,又不符合截屏规则,则输出到 log 待分析
}
/**
* 判断指定的数据行是否符合截屏条件
*/
function checkScreenShot(data?: string, dateTaken: Long, width: Int, height: Int): boolean {
// 判断依据一: 时间判断
// 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于10秒, 则认为当前没有截屏
if (dateTaken < mStartListenTime || System.currentTimeMillis() - dateTaken > 10 * 1000) {
return false
}
/**
* 判断指定的数据行是否符合截屏条件
*/
function 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 == null) {
// 判断依据二: 尺寸判断
if (mScreenRealSize != null) {
// 如果图片尺寸超出屏幕, 则认为当前没有截屏
if (!(width <= mScreenRealSize!.x && height <= mScreenRealSize!.y)
|| (height <= mScreenRealSize!.x && width <= mScreenRealSize!.y)
) {
return false
}
}
let lowerData = data.lowercase(Locale.getDefault())
// 判断图片路径是否含有指定的关键字之一, 如果有, 则认为当前截屏了
for (keyWork in KEYWORDS) {
if (lowerData.contains(keyWork)) {
return true
}
}
// 判断依据三: 路径判断
if (data == null) {
return false
}
/**
* 判断是否已回调过, 某些手机ROM截屏一次会发出多次内容改变的通知; <br></br>
* 删除一个图片也会发通知, 同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏.
*/
function checkCallback(imagePath: String): boolean {
if (mHasCallbackPaths.contains(imagePath)) {
let lowerData = data.lowercase(Locale.getDefault())
// 判断图片路径是否含有指定的关键字之一, 如果有, 则认为当前截屏了
for (keyWork in KEYWORDS) {
if (lowerData.contains(keyWork)) {
return true
}
// 大概缓存15~20条记录便可
if (mHasCallbackPaths.size >= 20) {
// for (i of 4) {
// mHasCallbackPaths.removeAt(0)
// }
}
mHasCallbackPaths.add(imagePath)
return false
}
return false
}
/**
* 获取屏幕分辨率
*/
function getRealScreenSize(): Point | null {
let screenSize:Point = Point();
try {
let windowManager = getUniActivity()!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
let defaultDisplay = windowManager.defaultDisplay
defaultDisplay.getRealSize(screenSize)
} catch (e) {
e.printStackTrace()
}
/**
* 判断是否已回调过, 某些手机ROM截屏一次会发出多次内容改变的通知; <br></br>
* 删除一个图片也会发通知, 同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏.
*/
function checkCallback(imagePath: String): boolean {
return screenSize
if (mHasCallbackPaths.contains(imagePath)) {
return true
}
// 大概缓存15~20条记录便可
if (mHasCallbackPaths.size >= 20) {
// for (i of 4) {
// mHasCallbackPaths.removeAt(0)
// }
}
mHasCallbackPaths.add(imagePath)
return false
}
/**
* 获取屏幕分辨率
*/
function getRealScreenSize(): Point | null {
let screenSize: Point = Point();
try {
let windowManager = getUniActivity()!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
let defaultDisplay = windowManager.defaultDisplay
defaultDisplay.getRealSize(screenSize)
} catch (e) {
e.printStackTrace()
}
return screenSize
}
/**
* 媒体内容观察者
*/
class MediaContentObserver extends ContentObserver {
contentUri: Uri;
handler: Handler;
constructor(contentUri: Uri, handler: Handler){
constructor(contentUri: Uri, handler: Handler)
{
super(handler)
this.contentUri = contentUri
this.handler = handler
}
override onChange(selfChange: Boolean) {
super.onChange(selfChange)
handleMediaContentChange(contentUri)
...
...
@@ -311,35 +295,91 @@ export function requestPremission() {
return { name: "requestPremission" };
}
type onImageCatchOptions = {
onImageCatchChange: (res: string) => void;
};
let listenOption:onImageCatchOptions|null = null;
/**
*
初始化生命周期
监听
*
开启截图
监听
*/
export function initAppLifecycle(option:onImageCatchOptions) {
// onImageCatchListener = onImageCatch;
listenOption = option;
startListener()
onAppActivityPause(() => {
stopListener()
});
/**
* activity 得到焦点的周期回调
* 说明文档:https://uniapp.dcloud.net.cn/plugin/uts-plugin.html#onappactivityresume
*/
onAppActivityResume(() => {
startListener()
});
export function onUserCaptureScreen(success: (res: string) => void) {
listenOption.onImageCatchChange = success;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// android 10 以上版本,使用监听文件的方式,更加可靠
var directory_screenshot: File;
var directory_pictures = File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_PICTURES);
var directory_dcim = File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DCIM);
if (Build.MANUFACTURER.equals("Xiaomi", ignoreCase = true)) {
directory_screenshot = File(directory_dcim, "Screenshots");
} else {
directory_screenshot = File(directory_pictures, "Screenshots");
}
if (screenOB != null) {
screenOB!.stopWatching()
}
screenOB = new ScreenFileObserver(directory_screenshot)
screenOB!.startWatching()
} else {
// android 10 以下版本,采用监听系统媒体库的方式
mStartListenTime = System.currentTimeMillis()
// 创建内容观察者
mInternalObserver =
new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler)
mExternalObserver =
new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler)
// 注册内容观察者
getUniActivity()!.getContentResolver()!.registerContentObserver(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
false,
mInternalObserver!
)
getUniActivity()!.getContentResolver()!.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
false,
mExternalObserver!
)
}
}
/**
* 关闭截屏监听
*/
export function offUserCaptureScreen(success: (res: string) => void) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// android 10以上,关闭监听通过移除文件监听器实现
if (screenOB != null) {
screenOB!.stopWatching()
screenOB = null
}
lastFileObserverTime = 0;
} else {
// android 10以下,注销内容观察者
if (mInternalObserver != null) {
try {
getUniActivity()!.contentResolver.unregisterContentObserver(mInternalObserver!)
} catch (e) {
e.printStackTrace()
}
mInternalObserver = null
}
if (mExternalObserver != null) {
try {
getUniActivity()!.contentResolver.unregisterContentObserver(mExternalObserver!)
} catch (e) {
e.printStackTrace()
}
mExternalObserver = null
}
// 清空数据
mStartListenTime = 0
}
success("");
}
...
...
uni_modules/uts-screenshot-listener/utssdk/app-android/libs/androidx.annotation-1.2.0.jar
0 → 100644
浏览文件 @
0e2b8991
文件已添加
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录