Flutter SOS应用:长按/双击电源键触发告警功能开发求助
实现方案说明
首先明确核心限制:Android系统中,长按电源键触发关机/重启是系统级优先级行为,第三方应用无法完全阻止该逻辑,但可以实现以下效果:
- 监听电源键的双击/长按事件,在3-5秒窗口内优先触发SOS告警
- 尽量延迟系统关机弹窗的出现(无法彻底屏蔽)
- 保证应用在前台、后台、熄屏状态下持续监听事件
以下是完整的Android原生代码+Flutter Method Channel集成方案,你只需将代码复制到对应位置即可快速实现功能:
1. Android端配置与代码实现
1.1 更新AndroidManifest.xml
在android/app/src/main/AndroidManifest.xml中添加权限和服务声明:
<!-- 前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 唤醒锁:保证熄屏时服务不休眠 --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- 解锁屏幕权限:熄屏时监听按键 --> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <application ...> <!-- 声明电源键监听服务 --> <service android:name=".PowerKeyListenerService" android:foregroundServiceType="specialUse" /> </application>
1.2 编写PowerKeyListenerService.java
在android/app/src/main/java/[你的包名]/目录下创建该服务类:
import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.PowerManager; import android.view.KeyEvent; import androidx.core.app.NotificationCompat; import io.flutter.plugin.common.MethodChannel; public class PowerKeyListenerService extends Service { private static final String CHANNEL_ID = "PowerKeyListener"; private static final int NOTIFICATION_ID = 1001; private PowerManager.WakeLock wakeLock; private long lastPressTime = 0; private long longPressStart = 0; private boolean longPressTriggered = false; private static MethodChannel methodChannel; // 给Flutter端传递MethodChannel实例 public static void setMethodChannel(MethodChannel channel) { methodChannel = channel; } @Override public void onCreate() { super.onCreate(); createNotificationChannel(); // 启动前台服务(Android 8.0+后台服务必须前台化) startForeground(NOTIFICATION_ID, buildForegroundNotification()); // 获取唤醒锁,防止熄屏时服务被休眠 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SOSApp:WakeLock"); wakeLock.acquire(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; // 服务被杀死后自动重启 } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); } // 监听电源键按下事件 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_POWER) { long currentTime = System.currentTimeMillis(); // 处理双击逻辑:两次按下间隔≤500ms if (currentTime - lastPressTime < 500) { sendEventToFlutter("doublePress"); lastPressTime = 0; // 重置避免连续触发 return true; } lastPressTime = currentTime; // 处理长按逻辑:记录开始时间,线程检测时长 if (event.getAction() == KeyEvent.ACTION_DOWN) { longPressStart = currentTime; longPressTriggered = false; new Thread(() -> { try { Thread.sleep(3000); // 等待3秒触发告警 if (!longPressTriggered && System.currentTimeMillis() - longPressStart >= 3000) { sendEventToFlutter("longPress"); longPressTriggered = true; Thread.sleep(2000); // 延迟2秒,优先触发告警 } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } return true; } return super.onKeyDown(keyCode, event); } // 监听电源键抬起事件,重置长按标记 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_POWER && System.currentTimeMillis() - longPressStart < 3000) { longPressTriggered = false; } return super.onKeyUp(keyCode, event); } // 向Flutter发送事件回调 private void sendEventToFlutter(String eventType) { if (methodChannel != null) { methodChannel.invokeMethod("onPowerKeyEvent", eventType); } } // 创建通知渠道(Android 8.0+必填) private void createNotificationChannel() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "电源键监控", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } // 构建前台服务通知 private Notification buildForegroundNotification() { return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("SOS监控中") .setContentText("正在监听电源键事件") .setSmallIcon(R.mipmap.ic_launcher) .setPriority(NotificationCompat.PRIORITY_LOW) .build(); } }
1.3 修改MainActivity.java关联Method Channel
import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "com.example.sosapp/power_key"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL); // 传递MethodChannel给监听服务 PowerKeyListenerService.setMethodChannel(channel); channel.setMethodCallHandler((call, result) -> { if (call.method.equals("startService")) { // 启动电源键监听服务 startService(new Intent(this, PowerKeyListenerService.class)); result.success(null); } else { result.notImplemented(); } }); } }
2. Flutter端代码实现
在main.dart中初始化Method Channel并处理告警逻辑:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { static const platform = MethodChannel('com.example.sosapp/power_key'); @override void initState() { super.initState(); _startPowerListener(); _setupEventListening(); } // 启动Android端监听服务 Future<void> _startPowerListener() async { try { await platform.invokeMethod('startService'); } on PlatformException catch (e) { print("启动监听服务失败: ${e.message}"); } } // 监听Android端传递的电源键事件 void _setupEventListening() { platform.setMethodCallHandler((call) async { switch (call.method) { case 'onPowerKeyEvent': String event = call.arguments; if (event == 'doublePress') { _triggerSOS("双击电源键触发SOS"); } else if (event == 'longPress') { _triggerSOS("长按电源键触发SOS"); } break; } }); } // 触发SOS告警逻辑(示例用弹窗,实际可替换为通知、位置发送等) void _triggerSOS(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.red), ); // 这里添加你的告警逻辑:比如调用flutter_local_notifications发送通知、上传位置到服务器等 } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("SOS应急应用")), body: const Center(child: Text("电源键监控已启动")), ), ); } }
关键注意事项
- 电池优化豁免:Android 10+对后台服务限制严格,必须引导用户将应用加入电池优化豁免列表,否则服务会被系统杀死。可添加以下代码跳转至设置页面:
// 在MainActivity中添加 Intent intent = new Intent(); String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (!pm.isIgnoringBatteryOptimizations(packageName)) { intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + packageName)); startActivity(intent); }
- 长按关机无法完全屏蔽:系统会在长按电源键约5秒后强制弹出关机对话框,你的告警会在3秒时优先触发,用户可手动取消关机。
- 测试场景:测试熄屏状态时,需确保应用已启动前台服务,且已授予所有权限。
若无法实现完全屏蔽系统关机的原因:
Android的电源键长按逻辑由系统框架(如PhoneWindowManager)处理,第三方应用的按键监听优先级低于系统,因此无法彻底拦截系统的关机触发,只能在系统执行关机逻辑前优先触发自定义告警。
内容的提问来源于stack exchange,提问作者Muhammad Arsalan
相关产品推荐
相关产品推荐

