Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
clx_listener_screen
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
openSourceLibrary
clx_listener_screen
Commits
9e872968
提交
9e872968
authored
11月 21, 2025
作者:
祁增奎
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
增加Android端实现,未测试,慎用
上级
cbccb2ae
隐藏空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
230 行增加
和
1 行删除
+230
-1
AndroidManifest.xml
android/src/main/AndroidManifest.xml
+4
-0
ClxListenerScreenPlugin.kt
...in/com/clx/clx_listener_screen/ClxListenerScreenPlugin.kt
+222
-0
build.gradle
example/android/app/build.gradle
+1
-1
pubspec.yaml
pubspec.yaml
+3
-0
没有找到文件。
android/src/main/AndroidManifest.xml
0 → 100644
浏览文件 @
9e872968
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"com.clx.clx_listener_screen"
>
<uses-permission
android:name=
"android.permission.DETECT_SCREEN_CAPTURE"
/>
</manifest>
android/src/main/kotlin/com/clx/clx_listener_screen/ClxListenerScreenPlugin.kt
0 → 100644
浏览文件 @
9e872968
package
com.clx.clx_listener_screen
import
android.app.Activity
import
android.content.Context
import
android.database.ContentObserver
import
android.hardware.display.DisplayManager
import
android.net.Uri
import
android.os.Build
import
android.os.Handler
import
android.os.Looper
import
android.provider.MediaStore
import
android.view.Display
import
android.view.WindowManager
import
androidx.annotation.NonNull
import
io.flutter.embedding.engine.plugins.FlutterPlugin
import
io.flutter.embedding.engine.plugins.activity.ActivityAware
import
io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import
io.flutter.plugin.common.EventChannel
import
io.flutter.plugin.common.MethodCall
import
io.flutter.plugin.common.MethodChannel
import
io.flutter.plugin.common.MethodChannel.MethodCallHandler
import
io.flutter.plugin.common.MethodChannel.Result
/** ClxListenerScreenPlugin */
class
ClxListenerScreenPlugin
:
FlutterPlugin
,
MethodCallHandler
,
EventChannel
.
StreamHandler
,
ActivityAware
{
private
lateinit
var
methodChannel
:
MethodChannel
private
lateinit
var
eventChannel
:
EventChannel
private
var
context
:
Context
?
=
null
private
var
activity
:
Activity
?
=
null
private
var
eventSink
:
EventChannel
.
EventSink
?
=
null
private
var
displayManager
:
DisplayManager
?
=
null
private
var
contentObserver
:
ContentObserver
?
=
null
private
var
lastRecordingStatus
=
0
override
fun
onAttachedToEngine
(
@NonNull
flutterPluginBinding
:
FlutterPlugin
.
FlutterPluginBinding
)
{
context
=
flutterPluginBinding
.
applicationContext
methodChannel
=
MethodChannel
(
flutterPluginBinding
.
binaryMessenger
,
"clx_flutter_visible"
)
methodChannel
.
setMethodCallHandler
(
this
)
eventChannel
=
EventChannel
(
flutterPluginBinding
.
binaryMessenger
,
"clx_flutter_screen_listener"
)
eventChannel
.
setStreamHandler
(
this
)
}
override
fun
onMethodCall
(
@NonNull
call
:
MethodCall
,
@NonNull
result
:
Result
)
{
if
(
call
.
method
==
"secureTextEntry"
)
{
val
arguments
=
call
.
arguments
as
?
Map
<
String
,
Any
>
val
show
=
arguments
?.
get
(
"show"
)
as
?
Boolean
?:
true
setSecureTextEntry
(
show
)
result
.
success
(
true
)
}
else
{
result
.
notImplemented
()
}
}
private
fun
setSecureTextEntry
(
isSecure
:
Boolean
)
{
activity
?.
let
{
act
->
if
(
isSecure
)
{
act
.
window
.
addFlags
(
WindowManager
.
LayoutParams
.
FLAG_SECURE
)
}
else
{
act
.
window
.
clearFlags
(
WindowManager
.
LayoutParams
.
FLAG_SECURE
)
}
}
}
override
fun
onDetachedFromEngine
(
@NonNull
binding
:
FlutterPlugin
.
FlutterPluginBinding
)
{
methodChannel
.
setMethodCallHandler
(
null
)
eventChannel
.
setStreamHandler
(
null
)
context
=
null
}
// ActivityAware
override
fun
onAttachedToActivity
(
binding
:
ActivityPluginBinding
)
{
activity
=
binding
.
activity
registerScreenCaptureCallback
()
}
override
fun
onDetachedFromActivityForConfigChanges
()
{
unregisterScreenCaptureCallback
()
activity
=
null
}
override
fun
onReattachedToActivityForConfigChanges
(
binding
:
ActivityPluginBinding
)
{
activity
=
binding
.
activity
registerScreenCaptureCallback
()
}
override
fun
onDetachedFromActivity
()
{
unregisterScreenCaptureCallback
()
activity
=
null
}
private
fun
registerScreenCaptureCallback
()
{
if
(
Build
.
VERSION
.
SDK_INT
>=
34
)
{
// Android 14 (UPSIDE_DOWN_CAKE)
activity
?.
registerScreenCaptureCallback
(
context
!!
.
mainExecutor
,
screenCaptureCallback
)
}
}
private
fun
unregisterScreenCaptureCallback
()
{
if
(
Build
.
VERSION
.
SDK_INT
>=
34
)
{
activity
?.
unregisterScreenCaptureCallback
(
screenCaptureCallback
)
}
}
private
val
screenCaptureCallback
by
lazy
{
if
(
Build
.
VERSION
.
SDK_INT
>=
34
)
{
Activity
.
ScreenCaptureCallback
{
activity
?.
runOnUiThread
{
eventSink
?.
success
(
mapOf
(
"type"
to
"shot"
))
}
}
}
else
{
throw
IllegalStateException
(
"API level too low"
)
}
}
// StreamHandler
override
fun
onListen
(
arguments
:
Any
?,
events
:
EventChannel
.
EventSink
?)
{
eventSink
=
events
startListening
()
}
override
fun
onCancel
(
arguments
:
Any
?)
{
stopListening
()
eventSink
=
null
}
private
fun
startListening
()
{
context
?.
let
{
ctx
->
// Screenshot Detection (File based, for < Android 14 or when FLAG_SECURE is OFF)
contentObserver
=
object
:
ContentObserver
(
Handler
(
Looper
.
getMainLooper
()))
{
override
fun
onChange
(
selfChange
:
Boolean
,
uri
:
Uri
?)
{
super
.
onChange
(
selfChange
,
uri
)
handleContentChange
(
uri
)
}
}
ctx
.
contentResolver
.
registerContentObserver
(
MediaStore
.
Images
.
Media
.
EXTERNAL_CONTENT_URI
,
true
,
contentObserver
!!
)
// Screen Recording Detection
displayManager
=
ctx
.
getSystemService
(
Context
.
DISPLAY_SERVICE
)
as
DisplayManager
displayManager
?.
registerDisplayListener
(
displayListener
,
Handler
(
Looper
.
getMainLooper
()))
// Initial check
checkRecordingStatus
()
}
}
private
fun
stopListening
()
{
context
?.
let
{
ctx
->
contentObserver
?.
let
{
ctx
.
contentResolver
.
unregisterContentObserver
(
it
)
}
contentObserver
=
null
displayManager
?.
unregisterDisplayListener
(
displayListener
)
displayManager
=
null
}
}
private
fun
handleContentChange
(
uri
:
Uri
?)
{
if
(
uri
==
null
)
return
val
uriString
=
uri
.
toString
().
lowercase
()
if
(
uriString
.
contains
(
"screenshot"
))
{
eventSink
?.
success
(
mapOf
(
"type"
to
"shot"
))
return
}
val
projection
=
arrayOf
(
MediaStore
.
Images
.
Media
.
DISPLAY_NAME
,
MediaStore
.
Images
.
Media
.
DATA
)
try
{
context
?.
contentResolver
?.
query
(
uri
,
projection
,
null
,
null
,
null
)
?.
use
{
cursor
->
if
(
cursor
.
moveToFirst
())
{
val
nameIndex
=
cursor
.
getColumnIndex
(
MediaStore
.
Images
.
Media
.
DISPLAY_NAME
)
val
dataIndex
=
cursor
.
getColumnIndex
(
MediaStore
.
Images
.
Media
.
DATA
)
val
name
=
if
(
nameIndex
>=
0
)
cursor
.
getString
(
nameIndex
)
else
""
val
path
=
if
(
dataIndex
>=
0
)
cursor
.
getString
(
dataIndex
)
else
""
if
(
name
.
contains
(
"screenshot"
,
ignoreCase
=
true
)
||
path
.
contains
(
"screenshot"
,
ignoreCase
=
true
))
{
eventSink
?.
success
(
mapOf
(
"type"
to
"shot"
))
}
}
}
}
catch
(
e
:
Exception
)
{
// Ignore
}
}
private
val
displayListener
=
object
:
DisplayManager
.
DisplayListener
{
override
fun
onDisplayAdded
(
displayId
:
Int
)
=
checkRecordingStatus
()
override
fun
onDisplayRemoved
(
displayId
:
Int
)
=
checkRecordingStatus
()
override
fun
onDisplayChanged
(
displayId
:
Int
)
=
checkRecordingStatus
()
}
private
fun
checkRecordingStatus
()
{
val
isRecording
=
isScreenRecording
()
val
status
=
if
(
isRecording
)
1
else
2
val
statusName
=
if
(
isRecording
)
"开始录制"
else
"结束录制"
if
(
lastRecordingStatus
!=
status
)
{
lastRecordingStatus
=
status
eventSink
?.
success
(
mapOf
(
"type"
to
"record"
,
"status"
to
status
,
"statusName"
to
statusName
))
}
}
private
fun
isScreenRecording
():
Boolean
{
val
displays
=
displayManager
?.
displays
?:
return
false
for
(
display
in
displays
)
{
if
(
display
.
flags
and
Display
.
FLAG_PRESENTATION
!=
0
)
{
return
true
}
}
return
false
}
}
example/android/app/build.gradle
浏览文件 @
9e872968
...
@@ -45,7 +45,7 @@ android {
...
@@ -45,7 +45,7 @@ android {
applicationId
"com.clx.example.clx_listener_screen_example"
applicationId
"com.clx.example.clx_listener_screen_example"
// You can update the following values to match your application needs.
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion
flutter
.
minSdkVersion
minSdkVersion
21
targetSdkVersion
flutter
.
targetSdkVersion
targetSdkVersion
flutter
.
targetSdkVersion
versionCode
flutterVersionCode
.
toInteger
()
versionCode
flutterVersionCode
.
toInteger
()
versionName
flutterVersionName
versionName
flutterVersionName
...
...
pubspec.yaml
浏览文件 @
9e872968
...
@@ -22,3 +22,6 @@ flutter:
...
@@ -22,3 +22,6 @@ flutter:
platforms
:
platforms
:
ios
:
ios
:
pluginClass
:
CLXListenerScreenPlugin
pluginClass
:
CLXListenerScreenPlugin
android
:
package
:
com.clx.clx_listener_screen
pluginClass
:
ClxListenerScreenPlugin
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论