Flutter for OpenHarmony三方库适配实战:flutter_screenshot_callback 截屏监听回调
截屏监听是移动应用安全防护的重要功能,用于检测用户截屏行为并做出相应处理。在金融应用、社交软件、企业办公等场景中,截屏监听可以有效保护敏感信息。在 Flutter for OpenHarmony 应用开发中,是一个跨平台的截屏监听插件,提供了完整的截屏事件监听能力。flutter_screenshot_callback 是一个实用的跨平台截屏监听插件,为 OpenHarmony 应用提供了完整的截
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发
一、flutter_screenshot_callback 库概述
截屏监听是移动应用安全防护的重要功能,用于检测用户截屏行为并做出相应处理。在金融应用、社交软件、企业办公等场景中,截屏监听可以有效保护敏感信息。在 Flutter for OpenHarmony 应用开发中,flutter_screenshot_callback 是一个跨平台的截屏监听插件,提供了完整的截屏事件监听能力。
flutter_screenshot_callback 库特点
flutter_screenshot_callback 库基于各平台原生 API 实现,提供了以下核心特性:
实时监听:实时监听系统截屏事件,用户截屏时立即触发回调。
路径获取:回调中返回截屏图片的存储路径,可用于后续处理。
权限处理:自动处理权限请求,权限被拒绝时提供回调通知。
跨平台一致:统一的 API 接口,支持 Android、iOS、OpenHarmony 三大平台。
功能支持对比
| 功能 | Android | iOS | OpenHarmony |
|---|---|---|---|
| 截屏监听 | ✅ | ✅ | ✅ |
| 获取截图路径 | ✅ | ❌ | ✅ |
| 权限检测 | ✅ | ❌ | ✅ |
| 权限被拒回调 | ✅ | ❌ | ✅ |
使用场景:金融应用安全防护、社交软件隐私保护、企业办公数据安全、敏感信息保护等。
二、安装与配置
2.1 添加依赖
在项目的 pubspec.yaml 文件中添加 flutter_screenshot_callback 依赖:
dependencies:
flutter_screenshot_callback:
git:
url: https://atomgit.com/openharmony-sig/fluttertpc_screenshot_callback.git
然后执行以下命令获取依赖:
flutter pub get
2.2 权限配置
flutter_screenshot_callback 在 OpenHarmony 平台上需要配置 ohos.permission.READ_IMAGEVIDEO 权限。
2.3.1 添加权限声明
在 ohos/entry/src/main/module.json5 文件的 requestPermissions 数组中添加:
{
"module": {
"name": "entry",
"type": "entry",
"requestPermissions": [
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:screenshot_callback_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
2.3.2 添加权限说明字符串
在 ohos/entry/src/main/resources/base/element/string.json 中添加:
{
"string": [
{
"name": "screenshot_callback_reason",
"value": "用于监听截屏事件,保护敏感信息安全"
}
]
}
三、API 详解
3.1 ScreenshotCallback 类
ScreenshotCallback 是插件的主类,提供截屏监听的核心功能。
class ScreenshotCallback {
static const MethodChannel _channel = MethodChannel('screenshot_callback');
IScreenshotCallback? _iScreenshotCallback;
}
3.2 startScreenshot 方法
开始监听截屏事件。
Future<void> startScreenshot()
功能说明:
- 启动截屏监听服务
- 自动请求必要的权限
- 权限被拒绝时触发
deniedPermission回调
使用示例:
final screenshotCallback = ScreenshotCallback();
await screenshotCallback.startScreenshot();
3.3 stopScreenshot 方法
停止监听截屏事件。
Future<void> stopScreenshot()
功能说明:
- 停止截屏监听服务
- 释放相关资源
- 建议在页面销毁时调用
使用示例:
await screenshotCallback.stopScreenshot();
3.4 setInterfaceScreenshotCallback 方法
设置截屏回调接口。
void setInterfaceScreenshotCallback(IScreenshotCallback iScreenshotCallback)
参数说明:
iScreenshotCallback:实现IScreenshotCallback接口的回调对象
使用示例:
screenshotCallback.setInterfaceScreenshotCallback(MyScreenshotHandler());
3.5 IScreenshotCallback 接口
IScreenshotCallback 是截屏事件的回调接口,需要开发者实现。
abstract class IScreenshotCallback {
void screenshotCallback(String data);
void deniedPermission();
}
3.5.1 screenshotCallback 方法
截屏成功时触发,返回截图文件路径。
void screenshotCallback(String data)
参数说明:
data:截图文件的存储路径
使用示例:
void screenshotCallback(String data) {
print('检测到截屏,图片路径: $data');
}
3.5.2 deniedPermission 方法
权限被拒绝时触发。
void deniedPermission()
使用示例:
void deniedPermission() {
print('读取相册权限被拒绝,无法监听截屏');
}
四、底层实现原理
4.1 Flutter 端实现
Flutter 端通过 MethodChannel 与原生层通信:
class ScreenshotCallback {
static const MethodChannel _channel = MethodChannel('screenshot_callback');
static const String FLUTTER_START_SCREENSHOT = "startListenScreenshot";
static const String FLUTTER_STOP_SCREENSHOT = "stopListenScreenshot";
static const String NATIVE_SCREENSHOT_CALLBACK = "screenshotCallback";
static const String NATIVE_DENIED_PERMISSION = "deniedPermission";
Future<dynamic> methodCallHandler(MethodCall call) async {
switch (call.method) {
case NATIVE_SCREENSHOT_CALLBACK:
String path = call.arguments;
_iScreenshotCallback?.screenshotCallback(path);
break;
case NATIVE_DENIED_PERMISSION:
_iScreenshotCallback?.deniedPermission();
break;
}
}
}
4.2 OpenHarmony 原生实现
原生层使用 OpenHarmony 的 PhotoAccessHelper 监听相册变化:
ScreenshotCallbackPlugin.ets:
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
export default class ScreenshotCallbackPlugin implements FlutterPlugin, MethodCallHandler, OnScreenShotListener {
private channel: MethodChannel | null = null;
private mScreenShotListenManager: ScreenShotListenManager | null = null;
onShot(imagePath: String): void {
this.channel?.invokeMethod("screenshotCallback", imagePath);
}
onScreenCapturedWithDeniedPermission(): void {
this.channel?.invokeMethod("deniedPermission", null);
}
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method == "startListenScreenshot") {
this.mScreenShotListenManager?.startListen(this.windowClass);
result.success(null);
} else if (call.method == "stopListenScreenshot") {
this.mScreenShotListenManager?.stopListen();
result.success(null);
} else {
result.notImplemented();
}
}
}
ScreenShotListenManager.ets:
export class ScreenShotListenManager {
private phAccessHelper: photoAccessHelper.PhotoAccessHelper | null = null;
private static SCREENSHOT_PREFIX = "Screenshot";
private onCallback: Callback<photoAccessHelper.ChangeData> = (changeData) => {
if (changeData && changeData.type == photoAccessHelper.NotifyType.NOTIFY_ADD && changeData.uris) {
for (let photo of changeData.uris) {
if (photo && photo.indexOf(SCREENSHOT_PREFIX) > 0) {
this.mListener && this.mListener.onShot(photo)
break
}
}
}
}
async startListen(windowClass: window.Window | null): Promise<void> {
abilityAccessCtrl.createAtManager()
.requestPermissionsFromUser(this.uiAbility?.context, ['ohos.permission.READ_IMAGEVIDEO'], (err, data) => {
if (err) {
this.mListener && this.mListener.onScreenCapturedWithDeniedPermission()
} else {
this.phAccessHelper?.registerChange(
photoAccessHelper.DefaultChangeUri.DEFAULT_PHOTO_URI,
true,
this.onCallback
);
}
});
}
stopListen(): void {
this.phAccessHelper?.unRegisterChange(
photoAccessHelper.DefaultChangeUri.DEFAULT_PHOTO_URI,
this.onCallback
);
}
}
4.3 实现原理
OpenHarmony 平台的截屏监听实现原理:
- 权限请求:首先请求
ohos.permission.READ_IMAGEVIDEO权限 - 注册监听:使用
PhotoAccessHelper.registerChange注册相册变化监听 - 检测截图:当相册新增文件时,检查文件名是否包含 “Screenshot” 关键字
- 回调通知:检测到截图文件后,通过 MethodChannel 回调 Flutter 层
五、最佳实践
5.1 在敏感页面启用监听
建议在进入敏感页面时启用截屏监听:
class SensitivePage extends StatefulWidget {
State<SensitivePage> createState() => _SensitivePageState();
}
class _SensitivePageState extends State<SensitivePage> implements IScreenshotCallback {
final _screenshotCallback = ScreenshotCallback();
void initState() {
super.initState();
_screenshotCallback.setInterfaceScreenshotCallback(this);
_screenshotCallback.startScreenshot();
}
void dispose() {
_screenshotCallback.stopScreenshot();
super.dispose();
}
void screenshotCallback(String data) {
_showScreenshotWarning();
}
void deniedPermission() {
_showPermissionDialog();
}
void _showScreenshotWarning() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('安全提示'),
content: const Text('检测到您进行了截屏操作,请注意保护敏感信息。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('我知道了'),
),
],
),
);
}
void _showPermissionDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('权限不足'),
content: const Text('需要读取相册权限才能监听截屏,请在设置中开启。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
// 打开系统设置
},
child: const Text('去设置'),
),
],
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('敏感信息页面')),
body: const Center(child: Text('这是敏感内容')),
);
}
}
5.2 结合隐私窗口使用
可以结合 privacy_window 插件,实现更全面的保护:
class SecurePage extends StatefulWidget {
State<SecurePage> createState() => _SecurePageState();
}
class _SecurePageState extends State<SecurePage> implements IScreenshotCallback {
final _screenshotCallback = ScreenshotCallback();
final _privacyWindow = PrivacyWindow();
void initState() {
super.initState();
_privacyWindow.setPrivacyWindow();
_screenshotCallback.setInterfaceScreenshotCallback(this);
_screenshotCallback.startScreenshot();
}
void dispose() {
_screenshotCallback.stopScreenshot();
_privacyWindow.unSetPrivacyWindow();
super.dispose();
}
void screenshotCallback(String data) {
_logScreenshotEvent(data);
}
void deniedPermission() {
print('截屏监听权限被拒绝');
}
void _logScreenshotEvent(String path) {
print('用户在敏感页面截屏: $path');
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('安全页面')),
body: const Center(child: Text('此页面已开启截屏保护和监听')),
);
}
}
5.3 全局截屏监听
可以在应用级别设置全局截屏监听:
class ScreenshotService {
static final ScreenshotService _instance = ScreenshotService._internal();
factory ScreenshotService() => _instance;
ScreenshotService._internal();
final _screenshotCallback = ScreenshotCallback();
final List<Function(String)> _listeners = [];
void init() {
_screenshotCallback.setInterfaceScreenshotCallback(_ScreenshotHandler(this));
_screenshotCallback.startScreenshot();
}
void addListener(Function(String) listener) {
_listeners.add(listener);
}
void removeListener(Function(String) listener) {
_listeners.remove(listener);
}
void _onScreenshot(String path) {
for (var listener in _listeners) {
listener(path);
}
}
void _onPermissionDenied() {
print('截屏监听权限被拒绝');
}
}
class _ScreenshotHandler implements IScreenshotCallback {
final ScreenshotService _service;
_ScreenshotHandler(this._service);
void screenshotCallback(String data) {
_service._onScreenshot(data);
}
void deniedPermission() {
_service._onPermissionDenied();
}
}
六、注意事项
6.1 权限要求
OpenHarmony 平台必须配置 ohos.permission.READ_IMAGEVIDEO 权限,否则无法监听截屏。
6.2 权限被拒处理
当用户拒绝授权时,会触发 deniedPermission 回调,应引导用户去设置开启权限。
6.3 生命周期管理
建议在页面生命周期中管理监听:
initState:启动监听dispose:停止监听
6.4 iOS 平台限制
iOS 平台不支持获取截图路径,screenshotCallback 中的 data 参数为空字符串。
6.5 性能考虑
截屏监听会持续监听相册变化,建议在不需要时及时停止监听以节省资源。
七、完整代码示例

以下是一个完整的可运行示例,展示了 flutter_screenshot_callback 库的核心功能:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenshot_callback/flutter_screenshot_callback.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Screenshot Callback Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> implements IScreenshotCallback {
final _screenshotCallback = ScreenshotCallback();
bool _isListening = false;
String _lastScreenshotPath = '';
List<String> _screenshotHistory = [];
bool _hasPermission = true;
void initState() {
super.initState();
_screenshotCallback.setInterfaceScreenshotCallback(this);
}
void dispose() {
if (_isListening) {
_screenshotCallback.stopScreenshot();
}
super.dispose();
}
void _toggleListening() {
setState(() {
if (_isListening) {
_screenshotCallback.stopScreenshot();
_isListening = false;
} else {
_screenshotCallback.startScreenshot();
_isListening = true;
}
});
}
void screenshotCallback(String data) {
setState(() {
_lastScreenshotPath = data;
_screenshotHistory.insert(0, data);
if (_screenshotHistory.length > 10) {
_screenshotHistory.removeLast();
}
});
_showScreenshotDetectedDialog(data);
}
void deniedPermission() {
setState(() {
_hasPermission = false;
_isListening = false;
});
_showPermissionDeniedDialog();
}
void _showScreenshotDetectedDialog(String path) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.screenshot, color: Colors.orange),
SizedBox(width: 8),
Text('检测到截屏'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('截图已保存到相册'),
const SizedBox(height: 8),
const Text(
'文件路径:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
path,
style: const TextStyle(fontSize: 12),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
void _showPermissionDeniedDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.error_outline, color: Colors.red),
SizedBox(width: 8),
Text('权限不足'),
],
),
content: const Text(
'需要读取相册权限才能监听截屏事件。\n\n'
'请前往系统设置开启权限。',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('去设置'),
),
],
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('截屏监听'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildStatusCard(),
const SizedBox(height: 16),
_buildControlButton(),
const SizedBox(height: 24),
_buildHistoryCard(),
],
),
),
);
}
Widget _buildStatusCard() {
Color statusColor;
String statusText;
IconData statusIcon;
if (!_hasPermission) {
statusColor = Colors.red;
statusText = '权限被拒绝';
statusIcon = Icons.error_outline;
} else if (_isListening) {
statusColor = Colors.green;
statusText = '正在监听';
statusIcon = Icons.screenshot_monitor;
} else {
statusColor = Colors.grey;
statusText = '未启动监听';
statusIcon = Icons.screenshot;
}
return Card(
color: statusColor.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(statusIcon, color: statusColor, size: 32),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
statusText,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
if (_lastScreenshotPath.isNotEmpty)
Text(
'最近截屏: ${_lastScreenshotPath.split('/').last}',
style: const TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
);
}
Widget _buildControlButton() {
return ElevatedButton.icon(
onPressed: _hasPermission ? _toggleListening : null,
icon: Icon(_isListening ? Icons.stop : Icons.play_arrow),
label: Text(_isListening ? '停止监听' : '开始监听'),
style: ElevatedButton.styleFrom(
backgroundColor: _isListening ? Colors.red : Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
);
}
Widget _buildHistoryCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.history, color: Colors.teal),
const SizedBox(width: 8),
const Text(
'截屏历史',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Spacer(),
Text(
'共 ${_screenshotHistory.length} 次',
style: const TextStyle(color: Colors.grey),
),
],
),
const Divider(),
if (_screenshotHistory.isEmpty)
const Center(
child: Padding(
padding: EdgeInsets.all(32),
child: Text(
'暂无截屏记录',
style: TextStyle(color: Colors.grey),
),
),
)
else
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _screenshotHistory.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final path = _screenshotHistory[index];
return ListTile(
leading: const Icon(Icons.image),
title: Text(
path.split('/').last,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
path,
style: const TextStyle(fontSize: 10),
overflow: TextOverflow.ellipsis,
),
);
},
),
],
),
),
);
}
}
运行此示例后,您将看到一个完整的截屏监听演示界面,可以测试截屏检测功能。
八、总结
flutter_screenshot_callback 是一个实用的跨平台截屏监听插件,为 OpenHarmony 应用提供了完整的截屏事件监听能力。
优点
- 实时监听:实时检测用户截屏行为
- 路径获取:返回截图文件存储路径
- 权限处理:自动处理权限请求和拒绝回调
- 跨平台一致:统一的 API 接口
适用场景
- 金融应用安全防护
- 社交软件隐私保护
- 企业办公数据安全
- 敏感信息保护
最佳实践
- 在敏感页面启用监听
- 结合 privacy_window 实现双重保护
- 权限被拒时引导用户开启
- 页面销毁时停止监听
注意事项
-
OpenHarmony 需要配置 READ_IMAGEVIDEO 权限
-
及时停止监听以节省资源
九、参考资料
更多推荐



所有评论(0)