Android 休眠与唤醒完全指南:从手机到 IoT 设备的实战解析

本文介绍 Android 的休眠与唤醒机制,亮点在于不仅覆盖通用移动手机设备,还延伸至当前热门的闺蜜机、智能音箱等 IoT 设备。

一、前言

不知道各位开发朋友是否遇到过下面的场景:

  • 设置了 8 点的定时提醒,熄屏后不响了。
  • 音乐在后台播放,几分钟后被掐断了。
  • 闹钟时间到了,但 APP 并没有被拉起来。
  • APP 里点了"上传",然后锁屏把手机放进口袋。过了一段时间后再打开,进度条卡在 30% 不动。

这些现象的背后都是一个原因:休眠和唤醒机制没处理好------Android 在屏幕熄灭后,会逐步收紧对 CPU、网络和后台任务的控制。

本篇会从内核一直讲到 Android 应用层,设备形态除了常见的手机,也会提到投影仪、闺蜜机、TV盒子等 IoT 设备------它们同样运行了 Android 系统,上面的电源管理逻辑和手机既有共性,也有大量差异。

二、熄屏、休眠、睡眠、Doze

先看四个词汇,这是三个不同深度的状态。如果你是 Windows 用户,大概率看到过 Windows 系统设置里的电源设置:关闭屏幕、睡眠和休眠超时。

windows 11 家庭版截图

本篇文章之所以提到它,是因为很多初学者还是会去用 Windows 的电源设置来理解 Android 的,这是不对的:

Windows术语 技术状态 耗电/唤醒 对应Android
关闭屏幕 只灭显示器 系统全醒 熄屏(Screen Off)
睡眠 S3(老机器)/Modern Standby S0ix(新笔记本) 内存保活,秒醒 ⭐休眠 / 睡眠(内核 Suspend)
休眠 S4 内存写入硬盘、整机断电 ⭐Android 手机没有

🎓ACPI 把系统电源状态编号 S0~S5,S3 = System/Sleep State 3、S4 = System/Sleep State 4 ...

​ Android 中文下【休眠/睡眠】经常在混着用,其实都指 Linux Suspend。本文会默认使用【休眠】一词。

Android 自己的三个状态:

  • 熄屏(Screen Off):关闭背光,CPU和系统可以全速跑(如音乐播放 APP)。
  • 休眠/睡眠(内核 Suspend):Android 里两词同义,都指 Linux 内核 Suspend,CPU 停摆、内存自动刷新保活、外设断电,需要唤醒才能拉醒。
  • Doze:Android 6.0 后新增的,是框架层的省电节流,CPU 周期醒、限网、推迟闹钟。

三、Android 休眠机制

搞清楚术语定义,我们来看下面一张 Android 电源分层架构模型:

最底层是 Linux 内核的 suspend。当系统判定"没有人在干活"时,CPU 会进入低功耗状态:屏幕熄灭、CPU 降频,甚至完全停摆------这就是我们说的"休眠"。内核用一个叫 wakeup source(唤醒源) 的机制来记账:只要还有唤醒源被持有,系统就睡不下去;所有唤醒源都释放了,才会真正进入 suspend。

Android 在 kernel 之上封装了一层 WakeLock 。应用通过 PowerManager 申请一把"锁",持有期间系统就不敢彻底休眠,这也是为何熄屏后还可以让 CPU 继续跑的原理。

但 Google 很快发现,一些 APP 滥用 WakeLock,为了保活把手机搞得"永远不睡",这带来了电池续航崩溃、发热严重的后果。于是 Android 6.0(Marshmallow)引入了 Doze 模式 和 App Standby ,在系统层面强制约束后台行为;Android 7 到 12 又一路加码,最终把"精准定时后台任务"这条路几乎堵死,转向引导开发者使用 WorkManager 这类批处理框架。

四、WakeLock 实战

WakeLock 是应用阻止系统休眠最直接的手段。先看它有哪些类型:

类型 行为
PARTIAL_WAKE_LOCK 保持 CPU 运行,屏幕和键盘灯可以熄灭。最常用、最安全
FULL_WAKE_LOCK(已废弃) 保持屏幕和 CPU 都亮着,极费电,已不推荐
SCREEN_DIM_WAKE_LOCK/SCREEN_BRIGHT_WAKE_LOCK(已废弃) 保持屏幕暗/亮,已废弃

⚠️ FULL_*、SCREEN_* 系列在 API 17 起就被标记废弃。现在唯一建议使用的是 PARTIAL_WAKE_LOCK,它适合"屏幕可以灭、但 CPU 不能停"的场景,比如音乐播放、后台录音、长耗时下载。

4.1 安全获取和释放 WakeLock

首先就是在 AndroidManifest.xml 声明权限:

xml 复制代码
<uses-permission android:name="android.permission.WAKE_LOCK" />

接下来我们展示将 WakeLock 封装到一个工具类:

kotlin 复制代码
class WakeLockHelper(context: Context) {

    private val powerManager =
        context.getSystemService(Context.POWER_SERVICE) as PowerManager

    private var wakeLock: PowerManager.WakeLock? = null

    /**
     * 获取 PARTIAL_WAKE_LOCK。
     * @param timeoutMs 超时自动释放,防止忘记 release 会导致永远不休眠。
     */
    fun acquire(timeoutMs: Long = 10 * 60 * 1000L) {
        // 已经持有就不再重复获取,避免引用计数混乱
        if (wakeLock?.isHeld == true) return

        wakeLock = powerManager.newWakeLock(
            PowerManager.PARTIAL_WAKE_LOCK,
            "MyApp::BackgroundWork"   // 包名风格 tag,便于在 bugreport 里定位
        ).apply {
            setReferenceCounted(false) // 非引用计数,release 一次即释放
            acquire(timeoutMs)
        }
    }

    fun release() {
        wakeLock?.let {
            if (it.isHeld) it.release()
        }
        wakeLock = null
    }
}

调用时务必用 try/finally 兜底,确保异常路径也能释放:

kotlin 复制代码
val helper = WakeLockHelper(context)
helper.acquire()
try {
    doCpuIntensiveWork()   // 你的后台工作
} finally {
    helper.release()       // ⭐ 无论成功失败都释放
}

4.2 WakeLock 的常见误用

  • 在 Activity 里持有 WakeLock 却不释放,用户退出页面后手机一直转;
  • 用 FULL_WAKE_LOCK 来"保持屏幕常亮"------其实用 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 更合适,且不需要任何权限;
  • 把 WakeLock 当"保活"手段长期持有,结果被 Doze 和厂商杀进程策略双重制裁。

五、Doze 模式与 App Standby

Doze 模式 会在设备满足"息屏 + 静止 + 不充电"持续一段时间后触发。一旦进入 Doze,系统会严格限制你的 APP:

  • 暂停网络访问;
  • 忽略 WakeLock;
  • 推迟 AlarmManager 的闹钟(白名单除外);
  • 限制 GPS/WiFi 扫描;
  • 停止 JobScheduler 和 SyncAdapter。

但 Doze 并没有"一刀切"地永久冻结后台,系统会周期性地短暂退出Doze,打开一个维护窗口,在这个期间你可以把积压的网络请求、同步任务一次性跑完。

App Standby 则针对"长时间没有被用户主动打开"的APP,它会单独限制其后台网络。

🎓 维护窗口的间隔会随着闲置时间不断拉长,下面图示里的水平绿柱就是不断拉长的间隔。

5.1 Doze白名单

我们可以引导用户把 APP 加入电池优化白名单(官方命名:电池优化豁免列表 ),它可以带来一种有条件的、部分豁免。名单内的 APP 在设备进入 Doze 时,不会被完全冻结网络和唤醒锁,从而可以继续执行一些任务。

但正如我提到的,它的作用有限。如常规的 AlarmManager 闹钟仍然不会触发,JobScheduler 任务也依然会被推迟。而且无论是国内的 ROM 厂商、国外的 Google Play 对这个操作都有各种限制,终端用户也可以轻松关掉它。

kotlin 复制代码
fun requestIgnoreBatteryOptimization(context: Context) {
    val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
    val packageName = context.packageName
    if (pm.isIgnoringBatteryOptimizations(packageName)) return

    // 跳转系统设置页,由用户手动授权(无法静默加入)
    val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
        data = Uri.parse("package:$packageName")
    }
    context.startActivity(intent)
}

5.2 App Standby

Doze 看的是"设备整体闲不闲",而 App Standby 看的是"这个 APP 你多久没用了"。

一个 APP 什么时候算"被 Standby"?简单说就是"用户很久没主动启动它、它没有前台服务在跑、用户也没和它发的通知交互过、当前不在前台"。满足这些,系统就判定"这 APP 现在不重要",开始延迟它的后台网络、推迟 JobScheduler / SyncAdapter,连普通闹钟(set / setInexact)也会被往后推。

Android 9(Pie, API 28)把"用得越少、限制越狠"做成了分级桶(bucket)------系统按你近期的使用频率,把每个 APP 动态丢进一个桶,桶越低级、限制越重:

桶 含义 限制强度(约值,随版本调整)
Active(活跃) 正在用 / 刚用过 几乎无限制
Working Set(常用) 经常用,但此刻不在前台 轻限制:作业与网络短暂延迟(分钟级)
Frequent(偶尔) 隔几天用一次 中限制:延迟加大(小时级)
Rare(很少) 几乎不打开 重限制:后台作业一天最多跑几次,网络几乎掐断
Restricted(受限,Android 12 加) 用户主动限制 / 长期不互动 最狠:连 FCM 高优先级消息都限量

六、如何在休眠中"叫醒"系统

了解过休眠机制,下面开始介绍唤醒。

6.1 精确闹钟:AlarmManager

"到点必须叫醒系统干活"的业务十分常见------闹钟、用药提醒、代金券抢购提醒,可以使用 AlarmManager 的 wakeup 类型闹钟。

Android 12(API 31)起:精确闹钟需要声明 SCHEDULE_EXACT_ALARM 权限(闹钟/计时类 APP 可用 USE_EXACT_ALARM),所以先去打开 AndroidManifest.xml 声明权限:

xml 复制代码
// 大部分"需要精确"的普通应用, Android 12 起
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
// 闹钟/日历/计时器为唯一核心功能的 App,Android 13 起
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
kotlin 复制代码
@SuppressLint("ScheduleExactAlarm")
fun scheduleWakeupAlarm(context: Context) {
    val alarmManager =
        context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

    val intent = Intent(context, WakeupReceiver::class.java)
    val pendingIntent = PendingIntent.getBroadcast(
        context,
        0,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )

    // 5 分钟后触发一次,RTC_WAKEUP 会在息屏下唤醒 CPU
    val triggerAt = System.currentTimeMillis() + 5 * 60 * 1000L

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // ⭐ setExactAndAllowWhileIdle 能在 Doze 下准时触发
        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            triggerAt,
            pendingIntent
        )
    } else {
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAt, pendingIntent)
    }
}

接收端(WakeupReceiver.kt)要注意:onReceive 运行在主线程,且 10 秒内必须返回。

记得添加依赖:implementation("androidx.work:work-runtime-ktx:2.9.1")

kotlin 复制代码
class WakeupReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // 只做"叫醒 + 派发",重活在 Worker 里跑
        val work = OneTimeWorkRequestBuilder<WakeupWorker>().build()
        WorkManager.getInstance(context).enqueue(work)
    }
}

6.2 非精确、可延迟的批量任务:WorkManger

如果任务不要求太精确,如日志上报、图片压缩、数据同步,则优先使用 WorkManager。它会自动在系统条件允许时批量执行(包括Doze 期间),最省电也最不容易被系统干掉,并且自动兼容 Doze、App Standby 和厂商限制。

kotlin 复制代码
class WakeupWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 在这里做真正的后台工作,运行在后台线程
        uploadLogs()
        return Result.success()
    }

    private fun uploadLogs() {
        // 你的业务逻辑
    }
}

// 调度一个每 15 分钟一次的周期任务(WorkManager 最短间隔就是 15 分钟)
fun schedulePeriodic(context: Context) {
    val request = PeriodicWorkRequestBuilder<WakeupWorker>(15, TimeUnit.MINUTES)
        .build()
    WorkManager.getInstance(context)
        .enqueueUniquePeriodicWork(
            "daily-sync",
            ExistingPeriodicWorkPolicy.UPDATE,
            request
        )
}

七、嵌入式 IoT 类型设备的休眠和唤醒

这几年 Android 系统被大量用在投影仪、拉杆音箱、视频机、闺蜜机、TV 盒子上。不同于手机这类"省电优先"的设备,闺蜜机、拉杆音箱等有自己的电源管理目标。

7.1 IoT 终端设备和手机不一样

手机是"电池 + 随时随身",电源管理第一目标是省电。但许多消费级 IoT 终端设备不是:

  • 投影仪:大多一直插电,但待机时必须把灯泡/光机彻底断电以散热和节电,SoC 进入深度 suspend,靠红外遥控或电源键唤醒。

  • 智能拉杆音箱:带大电池,但"关机"状态下必须几乎零耗电,同时又要能被蓝牙连接、AUX 接入、按键"秒唤醒"。

这些设备同样有"休眠-唤醒",但关注点更多从"省电"变成了"待机低功耗 + 可靠唤醒 + 外设协同"。

7.2 全局不休眠

还有些设备就要求:全局不休眠。这类设备通常预装系统级 APP 或拥有系统签名,可以直接改全局设置:

kotlin 复制代码
// 需要系统权限(WRITE_SETTINGS / 系统签名应用),普通第三方 App 会抛异常
Settings.Global.putInt(
    context.contentResolver,
    Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
    BatteryManager.BATTERY_PLUGGED_AC or BatteryManager.BATTERY_PLUGGED_USB
)

// 或者把休眠超时直接设为最大值
Settings.System.putInt(
    context.contentResolver,
    Settings.System.SCREEN_OFF_TIMEOUT,
    Int.MAX_VALUE
)

7.3 瑞芯微平台的唤醒

我们以瑞芯微平台为例,瑞芯微平台(RK3399 / RK3566 / RK3568 / RK3588 等)的 Android 系统,休眠由 Linux 内核的 suspend 驱动,Android 通过 system/core/libsuspend 写 /sys/power/autosleep 触发。设备能不能被某个外设唤醒,由设备树(Device Tree)里是否把它标记为唤醒源决定------这是 BSP / 固件工程师的地盘,有兴趣的朋友可以继续自行搜索学习,本篇讲解的不多。

典型的可唤醒外设:

  • 电源键 / 实体按键 :gpio-keys 里标记 wakeup-source;

  • 红外遥控(IR) :投影仪最常见的唤醒方式,IR 驱动节点加 wakeup-source;

  • RTC:PMIC(如 RK817)内置 RTC,可做定时开机/唤醒;

  • 网络:以太网 PHY 的 WoL(Wake-on-LAN)、部分 WiFi 唤醒;

  • 蓝牙:UART 主机的 wake GPIO,实现"蓝牙连接即唤醒"。

下面是一个示意性的设备树片段(不同板级 SDK 节点名会有差异,但模式一致),展示如何把红外和电源键声明为唤醒源:

text 复制代码
// 红外遥控:待机时靠它唤醒
&ir_rx {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&ir_rx_gpio>;
    wakeup-source;            // ⭐ 关键:让 IR 成为唤醒源
};

// 实体电源键
gpio_keys: gpio-keys {
    compatible = "gpio-keys";
    power-key {
        gpios = <&gpio0 RK_PA5 GPIO_ACTIVE_LOW>;
        linux,code = <KEY_POWER>;
        label = "power";
        wakeup-source;        // ⭐ 电源键可唤醒
    };
};

// PMIC 内置 RTC:可做定时唤醒
&rk817 {
    rk817_rtc: rtc {
        wakeup-source;
    };
};

7.4 待机墙和快速恢复

投影仪、闺蜜机不能真的"黑屏睡着",行业惯例是做低功耗待机页(Standby Screen):显示时钟/海报的低亮度界面,配合 Dream Service(屏保框架)实现,既省电又保留了"一按即亮"的体验,还规避了深睡后被杀进程的风险。

7.5 应用层如何做

在嵌入式设备上做 APP(比如给投影仪写一个 launcher、给音箱写一个播放器),我们选择的"武器"依然是 PowerManager 和 WakeLock,但用法要贴合手中的设备:

  1. 播放 / 推流时持 PARTIAL_WAKE_LOCK:和手机一样,屏幕可以灭,但 CPU 不能停。投影仪放片、音箱播歌时务必持有并在结束时释放。

  2. 主动休眠用 PowerManager.goToSleep() :注意它和 WakeLock 方向相反------WakeLock 阻止睡,goToSleep() 主动让系统睡(常用于"遥控器待机键"逻辑)。

  3. 远程唤醒 :远程唤醒要靠局域网指令:设备开放一个本地 socket / HTTP 服务,或被同局域网 App 发 UDP 包唤醒+ 内核把对应网口配置为 WoL 唤醒源。

  4. 和固件团队对齐唤醒源清单 :APP 再怎么写,设备整体能不能被某外设唤醒,最终取决于设备树 里的 wakeup-source。这是上层和 BSP 的接口契约,APP 开发前先要获取这份清单。

八、调试技巧

一些常见的调试命令:

bash 复制代码
# 查看当前 WakeLock 持有情况(谁在阻止休眠,一目了然)
adb shell dumpsys power | grep -i wake

# 查看 Wakefulness 状态与休眠原因
adb shell dumpsys power | grep -E "mWakefulness|Last go to sleep reason"

# 模拟进入 Doze(测试闹钟/任务是否会被推迟)
adb shell dumpsys deviceidle force-idle deep

# 观察 Doze 状态变化
adb shell dumpsys deviceidle get deep

# 退出模拟,恢复设备
adb shell dumpsys deviceidle unforce

# 查看系统支持的休眠状态(mem / freeze)
cat /sys/power/state

# 手动强制进入 suspend(调试用,生产别乱敲)
echo mem > /sys/power/state

# 看某个输入设备是否允许作为唤醒源(yes/no)
cat /sys/class/input/eventX/device/power/wakeup

# 内核层所有唤醒源的命中统计,定位"是谁一直把系统叫醒"
cat /sys/kernel/debug/wakeup_sources

九、结语

Android 休眠与唤醒的本质,是应用需求与系统省电策略的博弈 。理解熄屏、休眠、Doze 三层模型后,如何设计业务就清晰了:保持屏幕常亮清晰优先考虑 FLAG_KEEP_SCREEN_ON,穿透休眠用精确闹钟 AlarmManager,可延迟任务交给 WorkManager,系统级需求走设备定制。手机场景要"顺从"Doze,而投影仪、TV 盒子、音箱这类常插电设备则要"驯服"休眠,做出符合产品形态的待机体验。把这些机制吃透,我们写出的就不只是能跑的代码,而是让用户设备既可靠又省电的代码。

十、参考资料

相关推荐
mmsx1 小时前
Android 三类「看着无关」的故障:大事务、UI 阻塞与 ViewModel 作用域
android·ui
通信侠2 小时前
android 16触摸提示音Back虚拟按键无声
android
GY-933 小时前
【Android 】App工程结构和配置
android
国科安芯3 小时前
ASP4644系列抗辐射电源模块的国产化进程、供应链安全与产业生态综述
人工智能·物联网·安全·电池·电源芯片·低轨卫星·抗辐射
360智汇云3 小时前
隔离VPC标准路径:出公网链路的设计与实现
android·服务器·数据库
Acrel12344 小时前
电力物联网中的边缘计算网关:从数据采集到边缘决策的架构实践
物联网·架构·边缘计算
nece0014 小时前
win10家庭版安装Android模拟器(AVD 安卓虚拟设备)
android·模拟器
武汉万象奥科4 小时前
T153芯片解析:ARM+RISC-V异构架构,集成三路千兆网
arm开发·嵌入式硬件·物联网·arm核心板
GY-934 小时前
【Android之Compose开发基础】创建首个Android项目
android