夜雨聆风学习资料网

ARTICLE · 1049594

低功耗和长续航漫谈(二十三):软件省电(上)——先问该不该做

低功耗和长续航漫谈(二十三):软件省电(上)——先问该不该做

低功耗和长续航漫谈(二十三):软件省电(上)——先问该不该做

省电这件事,软件侧的空间在哪里?

直觉会指向"把活干得更省"——换更快的算法、把计算交给专用电路、少醒几次,都是实在手段,也都有收益。

更大的空间在另一个方向上:有些活,本来就不该干。

一个早就下线的功能,留下的定时器还在按原来的节奏醒;一个没人看的页面,还在按秒刷新。这类活不是"干得费",而是根本不该有人干——把它清理掉,省下的不是百分之几十,是全部。

软件省电因此可以归结成三个问题,而且得按顺序问:

这件事还要不要做?

要是得做,能不能等会儿做、几件一起做?

能更高效地去做吗?

这一篇先问第一个:这件事还要不要存在。

一、三个问题,一个不能反的顺序

问题有先后,但不分大小。

第一问,还要不要做。一件事只要不发生,它的电一分不花——省下的是全部。一个每天准时唤醒上百次的定时器被删掉,省的不是它每次那几个百分点,是这个任务身上所有的电。

不过这一问要分两层。

前一层是这件事压根不发生。定时器删掉,它身上的电全部归零。

后一层是事情要做,但是做多了。屏幕停在静止画面时把刷新率降下来、没有声音时让音频功放进待机、核闲下来就关掉。这一层算做多了,多出来的那部分电原本不必花。

两层之间有一条清楚的分界:谁来判定。后一层的判据是现成的——画面有没有在变、有没有声音、有没有活在等——系统自己就能量、能判、能执行,不用问谁。前一层不行:没有任何一个指标能替你回答"这件事是不是压根不该发生"。

第二问,能不能等会儿做、几件一起做。活还是要干的,但干活的时机和批次可以挑。省下来的是每次醒来那一整套进出的开销:供回电域、恢复时钟、调度任务、重新初始化外设。活没少干,少的是"醒过来"这件事本身。

第三问,能更高效地去做吗。活是必须干的、时机也定死了,只能在这一件事内部抠——换个算法、换个执行者、少搬一次数据。省下来的是单次执行里的一部分

三个问题不是同一件事的三档选择。一件该消失的活,只有第一问能把它归零;一件必须干的活,问第一问本身就是白问——能省的只在后两个问题里。

顺序反了会怎样,看两个例子。一段早就没有业务方的任务,优化到极致,它的电也不是零——它还在醒,只是每次醒得省一点;把它删掉,这件事才彻底不存在。反过来,一段该保留的代码就算写得足够省,只要站错了位置,省下来的电往往还不够多出来的那一次唤醒。

最别扭的地方在这儿:本该拿掉的活,恰恰是最没人去动的那一批。

要去掉一件事,难在评估。你说这个功能没人用了,对面一句就能顶回来:"万一有人用呢?"这句话没法被证伪:你说没人用,怎么证明"没人"?

而工程上花力气最多的,偏偏是高效做这一问。它是三个问题里最像技术活的一个:有工具、有指标、能测、能画图、能写进季度目标——改完一个算法,当晚就能看到曲线变化;而删掉一个功能,你半年都说不清省了多少。

于是最常见的场景就出现了:花大力气优化一段本来就该删掉的代码。代码本身被磨得很漂亮,可它从一开始就不该在那儿。

三个问题的顺序不能反。前面的问题没问,后面的答案就没有意义。

而这一条顺序不是技术问题:彻底的那一层,答案不在代码里,在组织里。

图1 三类活与判定者

二、先分清"没做对"和"不该有"

第一问的判据可以压成一句话:它现在还在替谁干活?

答不上来的,就进了候选名单。注意是候选,不是判决——它还能不能留,取决于有没有人认领它。

动手之前先分两批东西,它们样子很像——不分开,这件事就会被读成"给删功能找理由"。

有一批看起来很像"不做"的,其实是"没做对":监听器注册了忘了反注册、异常路径上漏了释放、任务跑完没有退出。它们的外在表现和"没人用的常驻任务"几乎一样——都在用户不知情的时候醒着。但性质完全不同:这是一类缺陷,处置方式是让它按该停的时候停,不是砍掉需求。

两者混在一起,讨论马上就会跑偏:你提一个"这条任务该下线了"的建议,对面拿一堆积压的缺陷来反驳,最后议题被归结成"少给用户做功能"——本来是在清理垃圾,说着说着变成在降低产品质量。

所以先把分界线画在这里:该停没停,是缺陷;该不该有,是归属。这里要处理的是后者。

两者的处置路径也完全不同。缺陷走缺陷该走的路:修代码、补检查、改工具链——它的目标是"让该停的时候真能停下来",不改变产品要做什么。这类活可以排期、可以验收,也最适合顺手做掉:注册的地方顺手写清反注册,任务启动的地方顺手写清退出条件。

而"该不该有"走的是另一条路:它得先有人回答"这条任务替谁干活",再走一遍能反悔的回收流程。这类活没法靠代码评审发现,因为代码本身看不出它还有没有主人。

三、这些活藏在哪五个地方

这些活不会自己冒出来,但位置是固定的——一处一处看过去,比翻后台列表有效得多。

第一处:应用自己起来,以及被别的应用拉起来。

一个进程只要起来了,它就有了按自己的节奏执行代码的资格——唤醒处理器、联网、读传感器,都从这一步开始。所以先问一句:这个应用,用户上一回主动打开是什么时候?几个月没被打开过,却一直在后台留下记录,说明它起来这件事,没有人在等。

还有一层:点开一个应用,它顺手把几个不必同时在场的应用一起拉起来。用户只点了一个,起来的却是一串。

很多国产系统的设置里有"自启动"和"关联启动"这两项,各家叫法不完全一样,位置都在权限或应用管理里。苹果那边没有"自启动"这个概念,对应的开关叫"后台 App 刷新",同样是一个应用一个应用地关。留着它们的理由只剩一种:这条消息,用户需要当下就知道。

规则:没有理由自己起的,就别让它自己起。

顺便澄清一个常见的误会:把后台卡片划掉,并不等于阻止它运行。划掉卡片关掉的只是界面本身,收回来的是"现在这个页面",不是"以后还能不能起来"的资格——只要自启动或关联启动的资格还在,它照样会被系统事件唤醒,也会被别的应用拉起来。真关掉之后再打开,界面和数据还要从头装一遍。所以有用的顺序是:先收回资格,再谈清不清。

第二处:按固定周期去"看一眼"的扫描。

有些器件只要开着,就会按自己的节奏去看周围有没有东西——看的时候要发射、要接收,无线芯片和主处理器都得起来。

三种最常见:没连耳机、手表这类设备时的蓝牙、没有要连的热点时的 WiFi、以及不需要知道你在哪儿的应用手里那把"始终允许"的定位权限。

判断还是那一句:这一次看回来的结果,今天有人用上了吗?处置分两类:开关类的直接收回来,不用就关;权限类的改成"仅在使用期间"——需要的时候它自然拿得到,不需要的时候它看不到你。

规则:不用的时候,让它停下来。

第三处:用不上的通知。

一条通知要走完全程,得唤醒处理器、联网取内容、点亮屏幕,可能还要震一下。

判断标准不在"重不重要",在有没有人在等它:私信、工作消息、验证码,有人在等;热点资讯、活动推荐、商品降价,没有人在等。系统权限管理里,通知是按应用逐个给的,需要的留着,不需要的关掉。

桌面上的小红点也是同一回事:它要有数,就得有人去问。没人在等的事,不必让它一直去问。

规则:没有人在等的消息,不必把屏幕叫亮。

第四处:看不见结果的后台上报。

这一类最容易被忽略,因为它不在用户看得见的任何地方产生结果:页面停留了多久、点了哪里、设备是什么型号、崩溃发生在哪一行——采集的目的在产品分析,不在用户手上的任何一件事。

它也很少自己单独醒来,多半是搭在别的后台活动上顺路做掉——所以它不显眼,但每次都在。

处理它,最管用的是把"后台活动"这类资格收回来——这类上报靠的正是应用还能在后台干活。系统层面还有一项独立的开关:体验改进计划、诊断数据上传,在设置里直接关掉。

规则:先问一句"这一次上报,谁的桌上会用到它"——答不上来的,先关掉。

第五处:留在屏幕上的附加效果。

前面四处都是"这件事可以不发生"。还有一类不一样:它能发生,只是不必按最高规格发生。

息屏显示、动态壁纸、界面动画、每次点击的震动反馈——它们都属于"让画面更好看、手感更舒服"的那一层,不影响手机能不能用。这类开关做的是把"一直按最高规格给"改成"用到才给":画面停在不动的地方,就不必一直重画。

规则:用到才给,不用的时候不必一直给。

五处看完,有一个共同点:判断它们该不该留,不需要任何数据——只要问一句"这件事的结果,还有谁用得上",问不出下文的就进候选

四、它们凭什么还在

这五处只是"上哪儿找"。找出来之后还有第二个问题:这些活凭什么还在?

这一问不能用"藏在哪儿"来分。按藏身处分,好处是能对着自己的手机一条条核过去,但核完只能点头。按"这件事当初是谁申请的、现在还有没有主人"分,才知道该去找谁:同样是一段白醒的代码,有的一个人改几行就够,有的得把几个团队凑到一起。

第一类:业务没了,当初申请它的人也不在了。

已经下线的功能留下的定时任务与心跳,上线时为了某个实验留下的分支,接进来却从没被调用的第三方组件。它们按照原来的节奏准时醒来,检查一件早就没有下文的事,然后老老实实睡回去。

这一类最难收回来。它的失主是空的:当初提需求的人调走了,写代码的人换了项目,而代码还在跑。更麻烦的是,没有一个人有动机去动一份"已经不属于任何人"的代码——动了它,好处说不清;万一动了出事,责任倒是现成的。它们的来历五花八门,共同点是:没有人在等它的结果。

第二类:申请的人还在,只是忘了还。

界面退出了,定时器还在数秒;页面销毁了,动画还在跑;功能已经不用了,保活服务还挂着;后台能力在清单里声明了,实际没有对应的活。事情结束了,该还的没还。

这一类的失主是清楚的,技术上也不难办,偏偏它最常见。

它的样子每个人都见过:一个页面已经滑走了,它上面每秒刷新一次的计时器还在跳;导航退出之后,位置更新还在照原来的频率上来。写这些代码的时候,脑子里只有"什么时候开始",没有"什么时候结束"。

根子不在懒。是从写第一行代码起,就没有"什么时候该归还"这道检查项——注意力全在"什么时候该申请"上。等出了问题回头查,会发现代码里压根找不到归还这个动作的,不在少数。

有一个机制细节把"忘了还"的后果放大了:同一把锁可以被好几个地方分别申请,系统按申请的次数记账,每一份都还回去它才真正放行。还了九十九次都不算数——只要漏掉最后一份,前面所有的归还全作废。

第三类:还在干,但没人看。

采集了之后没人查的监控项,算出来从没被展示过的结果,界面已经退到后台、甚至被别的页面完全盖住了,还在照常刷新。

这一类最隐蔽,因为它每一次都在正常工作——不崩溃、不报错、日志干净,从各个监控看过去都是绿的。它的问题只记在电池那本账上,而没有人会天天去翻那本账。

它往往是这样长出来的:一个日志上报的开关,上线时为了排查问题打开,问题解决了没人关;一套性能监控,采集了十几个指标,真正被看过的不过其中两三个。每一条单拿出来都不算什么,合起来就是一份稳定的、没人认领的常驻开销。

这里要和"画得费"划一条线:"同一帧里画了几层"是另一个问题,这一类说的是"这个页面已经不该继续刷新"。前者的解法是把画面画简单些,后者的解法是让它别再画。

三类摆在一起,共同点不是"软件不老实"。第一、二类里,那些活当初可能都提得有理有据——业务确实需要它、数据确实要上报,错的只是没人收。只有第三类,是活本身就不该干。

所以这一问要回答的不是"怎么让这些活干得更省",而是"这些活凭什么还在"。

五、另一种毛病,系统自己就能发现

上面这三类都不好抓。但还有一类毛病长得完全不一样——该停没停——它不但看得见,还有一整套工具在等着。

一个真实的案例。2026 年 3 月,可穿戴设备厂商 WHOOP 在官方工程博客上复盘了一次后台耗电排查。起点不是用户投诉,而是一封通知:他们的应用被 Google Play 的后台指标页标记为"部分唤醒锁使用过量"。

而用户毫无感觉。按 WHOOP 自己的说法,支持工单里电池续航没有异常趋势,应用评论也没有反常——信号只存在于数据里。他们被标记的比例是 15%,当时门槛写的是 5%。

排查是一层层收窄的。第一层来自平台:指标页给出超标的比例,并把最大贡献者指到一个名字上——那是一个通用执行器,系统统一的后台任务框架本身,还不是具体哪个任务;平台同时给出的归因数据,能显示出具体是哪些任务在持锁、时间怎么分布。平台能给的,到这一层也就是这些了:一份按持锁时长排出来的名单,上面写着名字与时长。第二层靠他们自己:给每个后台任务都埋了运行时长、超时率、重试与取消的记录,两个任务立刻浮出来——一个同步传感器数据,一个周期检查固件更新。第三层看日志,出问题的那几次都集中在"手环和手机断开"的场景。第四层,他们给同一个任务的两种触发方式各打一个标记,本来想排除其中一个,结果发现两者一样糟——说明问题不在怎么调度,在这个任务自己的逻辑里。

改完之后,后台任务的平均运行时长从半分钟左右降到几秒,P95 从两分半左右降到几秒——被掐掉的主要是长尾,而持锁超标的一直是这批长尾。

这个案例正好把工具能覆盖到哪儿划出来了。平台能给的是三样东西:一个超标的比例、一份按持锁时长排出来的名单、一条门槛。名单上写的是名字与时长,它能替你发现"做得过分",也能把嫌疑范围缩到几个具体任务上——但这份名单是按"谁持锁久"排的,不是按"谁还被需要"排的。

而前面那三类活,连被认出来的资格都没有。耗电排行按累计电量排,它们每一次的开销小得可怜:单次小,排行榜上看不见;频率看着正常,所以是"正常工作";一直这么跑着,就成了背景。三个凑在一起才是问题,可这三个特征,没有一条能被门槛筛出来。

想让它们露出来,只能换一个问法:不问"谁耗电多",改问——这些定时的唤醒,是替谁在干活?前一个问法下它们连被怀疑的资格都没有,换成后一个,第一次就露出来了。

找没有主人的任务,比找耗电大户有效。前者问的是归属,后者问的是数量——而这一类问题的答案,不在数量上。

这不是个别现象。一项公开研究统计过一批安卓应用:五百个应用里,一百八十七个显式地操作了组件的唤醒与睡眠;其中八十六个被完整反编译逐个分析,工具报出五十五个有问题,再扣掉误报,还剩四十二个是真的

样本是十多年前的安卓应用,别把它当成今天行业里的比例看。但它至少说明一件事:这类毛病不是个别现象,而且"忘了还"在里面是登记在册的一类。

六、加功能有流程,删功能没有

删功能的账难算。加一件事有路径:要评审、要估收益、要排版本,做完还要验收;删一件事不在谁的任务列表里——没有人给它排期,做成了也算不上成绩。

"删"要一直往后排,卡在三个具体的地方。

收益算不清。加一件事,收益能写进目标:上线了什么、提升了什么,数字摆在那里。删一件事,省下的是"本来就不该花的电"——一个没有发生的开销,记不成一份功劳,也写不进任何一份周报。

做成了算不到谁头上。删掉之后一切照旧:待机曲线本来就是这样,崩溃率本来就这么低。一个做完看不见的动作,不会被排进任何一期。

可万一出事,责任是现成的。万一真有用户还在用它,回滚、复盘全落在当初提这件事的人身上。收益是大家的,风险是自己一个人的——这笔账谁都会算。

在用户手上是另一回事:权限管理里点一下,判据就在自己手上——用不上,就是理由。团队要把它收回来,得拆成一串可以反悔的动作:先关开关,再观测,最后才删代码——不可逆的动作被推到最后,下决定就容易多了。

这三步里只有最后一步是硬的。关掉开关、看一段时间,都能原样退回来,不需要谁拍板,也不需要谁签字;等到数据摆在桌上,删代码只是把已经发生的事登记一下。需要拍板的只剩最后那一下,它前面留着两次可以停下来的机会。

观测这一步看什么?不是看有没有人投诉——等到有人投诉,说明开关已经关得太深了。看的是三个反向的信号:功能入口是不是还有人在点,后台有没有开始报错,有没有人回来问"这个功能去哪了"。观测期要盖住一个完整的使用节奏:至少跨过一次版本更新、一次充电循环,否则那些只在夜里或者周末才发生的使用,根本不会露头。

七、外面的推力,只覆盖一半

这几年外部约束在收紧——有几件过去靠自觉的事,被写成了明文条款,而且可以被检测出来。

商店把持锁时长写成了准入指标。

安卓的官方说明把阈值写得很清楚:过去 28 天里,只要一个应用有超过 5% 的用户会话在 24 小时内累计持有非豁免的部分唤醒锁超过两小时,就踩到了"不良行为阈值"。后果直接落在上架这件事上:商店详情页可能挂出警告,并且可能被排除在推荐这类发现途径之外。这项措施从今年三月一日起开始推行。同一份说明里还有一句——电池效率被提升为核心质量指标,和崩溃、无响应这些稳定性指标并列。

它同时给了豁免与替代路径:系统持有、有明确用户益处、又没法再优化的锁(音频播放、定位访问、用户自己发起的数据传输)可以豁免;用户发起的数据传输走专门的接口;周期性的后台同步交给系统调度器,不要自己攥着锁。

还有一句官方专门澄清的话,能省下不少冤枉功夫:前台服务是一个生命周期接口,它管的是"别把应用杀掉",并不会在屏幕关闭之后阻止处理器睡下去。想要屏幕关了还让处理器运行,需要的是另一套机制。

它管的是"持锁时长"这个行为,不是"这件事该不该存在"。一把两小时的锁,如果那件事本身早该没了,平台也不会告诉你。

后台模式名不副实会被拒审。

苹果的审核指南里那一条写得很短:多任务应用只允许在实现它声明的预期用途时使用后台服务——网络电话、音频播放、定位、任务完成、本地通知这些。

你在清单里声明了要用哪一种后台能力,就得真有对应的事在干——声明了后台定位,却找不到一个需要持续定位的功能,是常见的拒审理由。

自启动、关联启动、互相保活,被逐项点名。

华为应用市场的审核材料把这几条拆成了四项,几乎可以对着抄:不得未经用户同意或无合理场景自启动、关联启动,家族应用不得互相保活;不得通过熄屏、锁屏、联系人、播放音频、壁纸、性能监控、设备管理、计划任务定时这些功能,唤醒应用或长期驻留后台;不得通过创建前台服务,唤醒应用或长期驻留后台;应用进程不得无法停止。

另一组条目把重点放在"频繁"两个字上:华为写明重点整治的,正是"频繁自启动或关联启动第三方应用";同一份条目里还专门点到 SDK:非服务所必需或无合理应用场景,SDK 不应启动或关联启动应用——约束的不只是应用本身。

"频繁"是个能被数出来的词。能被数出来,就意味着能被查。

平台管得到的那一半。

三家的条款指向同一类东西:可检测的行为。持锁多久、启动了谁、声明的后台能力有没有用上、进程能不能停——每一条都有对应的读数,能写成阈值,也能自动判定。

但没有一条能回答"这件事还要不要存在"。这不是平台不愿意管,是它手上没有这个信息,也不该有——这个任务当初是谁提的、提它的人还在不在,全在开发团队内部,从外面看不到。可检测的都是留在设备上的痕迹;归属不是痕迹,是关系。

于是出现一道很清楚的界线:第二、三类活,外面正在替你收——行为可见、能被数出来,于是被写成了门槛,以后不清理,有人管第一类活,外面管不着——它不是行为问题,是归属问题,能不能收回来,只能看内部有没有人认领。

一边是外面能替你收的,一边是只能自己收的——真正难的那一半,在后一边。

平台能数出你持锁两小时,数不出这个任务还有没有主人。

那这一半推力够不够用?不够。门槛能筛掉的是"做得太过分"的那一批——持锁太久、频繁自启动、声明了却不用的后台能力,这些行为大都控制得住,有人提醒、有人盯指标就能压下来。

它管不了的是"这件事本来就不该存在":一个任务只要还在跑,留下的都是正常记录。门槛看到的是"这个应用的持锁时间在限度内",看不到"这个应用有一半常驻任务没人要"。

八、动手验证

先读一遍,再动手。下面这些读数全是公开的系统接口,字段名各个版本不一样,按关键词找那几段,别去记字段名。

定时任务队列里都有谁:读系统的闹钟与任务队列(adb shell dumpsys alarm)。不要问"哪一条次数最多",改问"这一条是替哪个功能干的"——整个方法就在这一句改问法里。

唤醒记录里的持有者名字:读系统统计转储里的唤醒与持锁那几段(adb shell dumpsys batterystats)。这一次看的是名字,不是排行:把名字逐条对回你自己知道的功能,对不上号的那一条,就是没有主人的任务。

电池与电源的基本面adb shell dumpsys battery 看电量、温度、是不是插着充电器——做待机对照实验,第一步先确认这台机器没插电,否则测的是一场空。

读完心里有数,动手三条——前两条只用一台手机就够。

  1. 静置对照。
    把一个应用退到后台、锁屏十分钟,看它的处理器占用、网络、传感器是不是完全静止。这一条能把"不可见时还在干"的行为筛出来一大半——它检验的是"该停没停"。
  2. 给常驻任务列一张表。
    把手上这个应用里定时起来的活写成一列,每条后面补一句"它是替谁干的"。写不出来的那几条,就是第一问的候选,还得接着去问它到底有没有失主。
  3. 测形状
    (有功耗板条件)。同一段静置时间,分别在"这条任务还在跑"和"已经删掉"两个版本下各录一段波形——两种形状的差别通常在尖峰的密度上,不在基线上。

最后是一条排查顺序:先找没有主人的 → 再看申请有没有成对归还 → 再看谁被豁免了 → 最后才怀疑系统本身。"这台机器待机差"这一类问题,答案基本在前两步里。

三条结论

一、三个问题的顺序不能反。一件活只归其中一个问题:本该消失的,只有第一问能把它归零;必须干的,第一问给的答案是"留着",能省的只能从后两个问题里出。顺序反了的代价,是对着一件本就该消失的活使劲优化——它还在醒,只是每次醒得省一点。

二、难的不是技术,是归属。平台能数出你持锁两小时,数不出这个任务还有没有主人——可检测的都能被写成门槛,归属只能靠组织里有人认领。一条任务要想收得回来,得先有一个能回答"它替谁干活"的人:这个人不一定懂那段代码,但他得为它还在不在负责。反过来说,如果一个任务连这样的人都找不出来,它本身就已经回答了"该不该留"。

三、判断一件事该不该留,别先算电,先问它现在替谁干活。答不上来的,进候选;答得上来的留下——它的问题不在留不留,在后两个问题里。

收束

第一问管的是一件事:这件事还要不要存在。

一件活要么根本不该做,要么不该现在做——先把它筛出去,剩下的才值得讨论怎么做。顺序反过来的代价,是花了大力气,省下了一个本来不该存在的开销。或许一次优化会被后续的功能增长重新填满;删掉一件本就该消失的事不会——去掉的不是开销,是需求本身。

下一篇问第二个问题:能不能等会儿做、几件一起做。活其实还是要干的,能挑的只是时刻和批次——而这一问里省得最多的一招,是把活挪到一段掉电看不出来的时段。

《软件省电(中):再问能不能等会儿做》。

(评论区聊聊:你有没有在自己手机上见过那种"早就没人用、还在后台准时上班"的功能?)

相关学习资料