You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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("电源键监控已启动")),
      ),
    );
  }
}

关键注意事项

  1. 电池优化豁免: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);
}
  1. 长按关机无法完全屏蔽:系统会在长按电源键约5秒后强制弹出关机对话框,你的告警会在3秒时优先触发,用户可手动取消关机。
  2. 测试场景:测试熄屏状态时,需确保应用已启动前台服务,且已授予所有权限。

若无法实现完全屏蔽系统关机的原因:
Android的电源键长按逻辑由系统框架(如PhoneWindowManager)处理,第三方应用的按键监听优先级低于系统,因此无法彻底拦截系统的关机触发,只能在系统执行关机逻辑前优先触发自定义告警。

内容的提问来源于stack exchange,提问作者Muhammad Arsalan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:43:17