如何在Android NDK原生C++程序中接收系统广播?
核心原因
Android 触发关机/重启流程时,不会给所有进程按标准Linux逻辑发送可被sigaction捕获的SIGTERM:系统会优先分发关机、重启类广播,随后在短时间内通过lmk、进程强杀逻辑清理所有运行中进程,多数场景下进程根本等不到SIGTERM就被SIGKILL直接终止,这也是你之前写的信号回调不触发的根本原因。
纯Native C++环境没有Java层的Context类,无法直接调用SDK暴露的registerReceiver接口,但有两种稳定的实现路径。
实现方案
方案1:通过Native Binder接口直接注册系统广播(无Java依赖)
Android系统服务的Binder接口对native层完全开放,你可以直接调用ActivityManager的广播注册接口,不需要写任何Java代码:
- 编译配置添加依赖:Android.mk/Android.bp中链接
libbinder、libutils两个库 - 实现广播接收的Binder回调:
#include <binder/BBinder.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <utils/String16.h> #include <atomic> // 全局标记,避免重复触发退出逻辑 std::atomic<bool> is_shutdown_triggered{false}; void do_graceful_shutdown() { if (is_shutdown_triggered.exchange(true)) return; // 这里写你的优雅退出逻辑:刷磁盘缓存、关闭文件句柄、上报埋点等 } class ShutdownReceiver : public BBinder { public: status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override { // onReceive回调对应的transact code固定为1 if (code == 1) { do_graceful_shutdown(); return NO_ERROR; } return BBinder::onTransact(code, data, reply, flags); } };
- 程序启动时注册广播:
void register_shutdown_broadcast() { // 必须启动binder线程池,否则收不到系统回调 ProcessState::self()->startThreadPool(); sp<IBinder> am = defaultServiceManager()->getService(android::String16("activity")); if (!am) return; Parcel data, reply; data.writeInterfaceToken(android::String16("android.app.IActivityManager")); data.writeStrongBinder(sp<BBinder>::make<ShutdownReceiver>()); // 写入要监听的广播Action data.writeString16(android::String16("android.intent.action.ACTION_SHUTDOWN")); data.writeString16(android::String16("android.intent.action.REBOOT")); // 标记为前台广播接收器,提高回调优先级 data.writeInt32(0x10000000); // FLAG_RECEIVER_FOREGROUND // 调用registerReceiver接口,Android 10-14版本对应transact code为23,低版本需要对照对应版本的IActivityManager.aidl调整 am->transact(23, data, &reply, 0); }
注意:该方案用到的是Android隐藏Binder接口,不同大版本的transact序号可能有变动,适合内置到系统镜像的原生程序使用。
方案2:系统属性轮询兜底(全版本兼容,零适配成本)
如果不想适配版本差异,可以直接用系统原生进程通用的检测逻辑:系统触发关机/重启时会第一时间设置sys.powerctl系统属性,开一个低优先级线程轮询该属性即可,从Android 5到Android 14全版本通用,surfaceflinger、logd等系统核心进程都用这个逻辑做关机前处理:
#include <sys/system_properties.h> #include <pthread.h> #include <cstring> void* powerctl_watcher(void*) { char last_val[PROP_VALUE_MAX] = {0}; while (!is_shutdown_triggered.load()) { char cur_val[PROP_VALUE_MAX] = {0}; __system_property_get("sys.powerctl", cur_val); if (cur_val[0] != '\0' && strcmp(cur_val, last_val) != 0) { if (strstr(cur_val, "shutdown") || strstr(cur_val, "reboot")) { do_graceful_shutdown(); break; } strncpy(last_val, cur_val, PROP_VALUE_MAX - 1); } usleep(200 * 1000); // 200ms轮询一次,几乎无功耗影响 } return nullptr; } void start_shutdown_watcher() { pthread_t watcher_thread; pthread_create(&watcher_thread, nullptr, powerctl_watcher, nullptr); pthread_detach(watcher_thread); }
注意事项
- 优雅退出逻辑总耗时不要超过3s,系统触发关机流程后5s左右就会向所有进程发送SIGKILL,过长的逻辑会被直接打断。
- 如果你的native程序是普通三方应用通过JNI拉起的,最稳妥的方案还是在Java层注册BroadcastReceiver,收到关机广播后通过JNI回调native的退出逻辑,不需要适配隐藏接口。
- 不要把SIGTERM作为唯一的兜底触发条件,多数厂商定制ROM在关机流程中会跳过SIGTERM环节直接强杀进程。
内容的提问来源于stack exchange,提问作者Bigcat
相关产品推荐
相关产品推荐

