YAOTU INSIGHTS

小程序屏幕常亮setKeepScreenOn失效?生命周期与真机排查全攻略

小程序屏幕常亮setKeepScreenOn失效?生命周期与真机排查全攻略
先说结论微信小程序里要让屏幕保持常亮很多人的第一反应就是调wx.setKeepScreenOn({ keepScreenOn: true })三行代码完事。但真到了扫码、计时、直播这类需要长时间亮屏的页面上你会发现它经常不按剧本走屏幕该熄还是熄甚至设置了跟没设置一个样。这个无效的问题我前前后后折腾过很久网上搜到的答案多半只说放 onShow 里就好但实际原因远不止一个。这篇文章把我排查下来验证有效的思路、代码和边界情况完整记录下来给同样被这个 API 折腾的人一条可复现的排查路径。1. setKeepScreenOn 的生效逻辑先搞清楚无效到底是哪种无效1.1 基础用法与 success 回调的认知误区先看最基础的调用方式wx.setKeepScreenOn({ keepScreenOn: true, success: (res) { console.log(设置常亮成功, res) }, fail: (err) { console.error(设置常亮失败, err) } })很多人的误区是success回调只要执行了就以为屏幕已经保持常亮。实际上success只代表微信小程序框架收下了这条指令并且向系统申请了一次屏幕唤醒锁wakelock不代表系统电源策略一定会一直买账。常亮最终能否持续取决于当前页面是否在前台、是否有原生组件干扰、是否处于低电量模式以及微信版本和基础库版本对 wakelock 的管理方式。也有相当一部分人根本没看fail回调。如果基础库版本过低setKeepScreenOn这个 API 可能压根不存在你调的只是一个 undefined 函数自然一点反应都没有。排查前先做一次能力检测if (wx.canIUse(setKeepScreenOn)) { wx.setKeepScreenOn({ keepScreenOn: true }) } else { console.warn(当前基础库不支持 setKeepScreenOn请升级微信) }这一步能砍掉一个很隐蔽的无效分支。1.2 无效的三种表现对应三种不同根因我遇到的无效并不是同一种表现按现象拆开看每种背后的原因完全不一样。表现最可能的原因首次进入页面就一直不常亮基础库版本过低、开发者工具里测试、系统省电模式刚进入是常亮的切后台再回来就恢复熄屏调用写在 onLoad/mounted 里恢复前台时没有重新设置某些页面常亮正常跳转几个页面后变回熄屏全局 API 状态被其他页面的生命周期覆盖或系统回收这个分类很重要。如果你只按放 onShow这一招去修最多只能解决第二类问题第一类和第三类需要不同的处理方式。下面逐个拆开聊。2. 生命周期是重灾区onLoad、mounted 和 onShow 的坑2.1 为什么 onLoad 里设置常亮不可靠先看小程序页面生命周期的执行时机。onLoad页面首次加载时执行一次之后不会再跑。onShow页面每次显示时都会执行包括第一次进入、从后台切回前台、从其他页面返回当前页。如果常亮逻辑写在onLoad里第一次进入确实生效了但小程序页面实例会被页面栈保留下来你切后台再回来时页面不会重新触发onLoad而是触发onShow。这时候之前申请的屏幕唤醒锁大概率已经被系统回收表现在真机上就是进去是亮的按 Home 键再切回来没过多久屏幕又熄了。我见过很多这个问题的帖子代码基本长这样Page({ onLoad() { wx.setKeepScreenOn({ keepScreenOn: true }) // 切后台回来就失效 } })正确的做法是放到onShow里Page({ onShow() { wx.setKeepScreenOn({ keepScreenOn: true }) } })2.2 vue 系写法的映射陷阱mounted 不等于 onShow这个坑在 uni-app、Taro、mpvue 这类 vue 系小程序框架里尤其普遍。很多人习惯把所有初始化逻辑塞进mounted在微信小程序底层mounted对应的是组件挂载完成等价于页面首次创建而不是页面每次显示。小程序页面栈会把页面组件和 Vue 实例一起保留从后台回到前台时组件不会重新挂载mounted当然不会再次执行。所以在mounted里写常亮跟你写在原生的onLoad里是同一个问题只在第一次生效。uni-app 的 Vue 3 写法里应该用dcloudio/uni-app提供的生命周期组合式函数import { onShow } from dcloudio/uni-app onShow(() { uni.setKeepScreenOn({ keepScreenOn: true }) })Taro 里也是类似道理如果用的是 React 写法useDidShow才是页面每次显示会触发的钩子useEffect只在挂载时跑一次。确认一下自己用的框架钩子是不是真正对应了页面的onShow这一步能避免一大半框架层面的无效。2.3 全局 API 和页面栈切换之间的状态拉扯还有一个容易被忽略的事实setKeepScreenOn是全局 API不是页面级配置。A 页面设置true之后跳到 B 页面B 页面如果不主动维护这个状态常亮到底还活不活着取决于微信小程序当时有没有把 wakelock 回收掉。我实测下来这种跨页面残留非常不可靠有时候 A 设完去 B 还亮有时候 B 页面刚加载出来没几秒就熄了跟微信版本、基础库版本、机型都有关系。所以不要指望设一次整个小程序所有页面都常亮。你需要在每个需要常亮的页面onShow里主动设置true在不需要常亮的页面onShow里主动设置false把状态控制权握在自己手里而不是赌系统的回收时机。3. 工具测试是伪命题真机上还有 iOS 和安卓的差异化表现3.1 为什么在开发者工具里测常亮毫无意义小程序开发者工具跑在桌面端本质上是一个模拟器容器它控制不了你电脑操作系统的息屏策略。你在工具里调用setKeepScreenOn({ keepScreenOn: true })只会得到一次 API 调用成功你的桌面该多久休眠还是多久休眠开发者工具也没有一个虚拟屏幕可以真的被点亮或熄灭。所以如果只在开发者工具里验证得出无效的结论本身就是无效的。常亮这类跟系统电源策略相关的接口请一律用真机预览测试。这是很多人浪费一两个小时排查后才会意识到的点。3.2 一套可复现的真机验证流程要确认setKeepScreenOn到底有没有生效推荐按下面这个流程来验证结果才可信手机端打开调试模式系统自动锁屏时间调到最短iOS 可以设 30 秒安卓通常有 15 秒/30 秒选项。用开发者工具生成预览二维码真机扫码打开对应页面。打开 vConsole观察接口调用日志确认success或fail被触发。保持页面静止开始计时。如果到了系统设定的自动锁屏时间后屏幕依然亮着说明常亮生效了。再测一组切后台再回来按 Home 键切后台等几秒再切回小程序重新计时观察。测试时建议一次只改一个变量避免把系统省电模式和生命周期问题混在一起排查。我习惯用表格记录每台测试机的表现机型、系统版本、微信版本、基础库版本四个字段缺一不可因为很多无效其实是特定版本的行为差异。3.3 iOS 回前台立即设置偶尔失效的延迟处理在部分 iOS 微信版本上从后台回到小程序onShow触发后如果立刻调用setKeepScreenOn常亮偶尔会不生效。我推测跟 WKWebView 的页面活跃状态恢复时序有关但这不重要重要的是实测有效的规避手段稍微延迟一下再设置。Page({ onShow() { this.clearKeepScreenOnTimer() this.keepScreenOnTimer setTimeout(() { wx.setKeepScreenOn({ keepScreenOn: true }) }, 300) }, onHide() { this.clearKeepScreenOnTimer() }, onUnload() { this.clearKeepScreenOnTimer() }, clearKeepScreenOnTimer() { if (this.keepScreenOnTimer) { clearTimeout(this.keepScreenOnTimer) this.keepScreenOnTimer null } } })延迟 200 到 500 毫秒都试过300 毫秒在我这边表现最稳定。注意离开页面时要把定时器清掉否则页面都销毁了定时器还挂着属于不必要的副作用。4. 原生组件、视频播放和系统省电策略几个文档不会明说的边界4.1 视频和直播类原生组件对常亮的干扰setKeepScreenOn管的是 WebView 渲染层的常亮策略但小程序里的video、live-player、map、camera这类组件是原生组件渲染层级在 WebView 之上电源管理行为更接近原生 App。最典型的表现是页面onShow里设了常亮点开一个全屏视频播放到一半屏幕照样能熄。根据我的实践经验视频场景最好不要只依赖页面级的onShow设置。监听视频组件的播放事件在播放开始后重新设置一次常亮在暂停或结束时按业务需要决定是否关闭video idmyVideo src{{videoSrc}} bindplayonVideoPlay bindpauseonVideoPause bindendedonVideoEnded /Page({ onVideoPlay() { wx.setKeepScreenOn({ keepScreenOn: true }) }, onVideoPause() { // 按业务需要决定如果暂停时也要求亮屏就保持 true wx.setKeepScreenOn({ keepScreenOn: false }) }, onVideoEnded() { wx.setKeepScreenOn({ keepScreenOn: false }) } })这样处理的逻辑是视频真正进入播放态系统播放器持有前台媒体会话此刻再申请一次 wakelock才会在原生层生效。4.2 直播页的常亮组合方案直播类页面比普通视频更依赖常亮因为用户可能长时间只看不操作。除了页面onShow设置一次外我建议在live-player进入播放状态后再设置一次Page({ onShow() { wx.setKeepScreenOn({ keepScreenOn: true }) }, onLivePlayerStateChange(e) { const code e.detail.code // 进入播放状态码各家不一样以实际返回为准 if (code 200) { wx.setKeepScreenOn({ keepScreenOn: true }) } else if (code 0) { // 出错/缓冲时播放器可能短暂接管屏幕状态可重新设置一次 setTimeout(() { wx.setKeepScreenOn({ keepScreenOn: true }) }, 500) } } })还有一个容易被忽视的细节长时间高负载播放会让手机发热部分安卓定制系统会出于温控策略强制熄灭屏幕哪怕你持有 wakelock。这种情况代码层面很难完全绕过只能尽量优化页面渲染和播放清晰度减少不必要的性能开销。4.3 低电量与省电模式不是代码问题但你必须知道iOS 的低电量模式、安卓的超级省电/极致省电都会无视第三方应用的 wakelock。你代码写得再对系统层面照样强制休眠。这类无效其实是系统策略的兜底不是小程序的问题。如果业务强依赖屏幕常亮可以考虑用wx.getBatteryInfoSync()检测电量在电量偏低时主动提示用户const batteryInfo wx.getBatteryInfoSync() if (batteryInfo.level 20 batteryInfo.isCharging false) { wx.showToast({ title: 低电量模式可能影响常亮, icon: none }) }提示文案尽量写得像业务建议而不是系统报错避免给用户造成困扰。5. 一套可以直接抄的常亮管理方案5.1 原生小程序页面模板这是我在普通页面里最常用的写法onShow里设置常亮onHide和onUnload里不做对称式关闭只在需要取消常亮的页面onShow里显式设置false。Page({ onShow() { wx.setKeepScreenOn({ keepScreenOn: true }) } })为什么不推荐在onHide里设置false因为页面跳转时新旧页面的onHide和onShow触发顺序在部分场景下会有交错如果你在 A 页的onHide里关掉常亮B 页的onShow还没来得及把它打开就会产生一个熄屏窗口甚至在特定顺序下直接把下一个页面的常亮覆盖掉。让每个页面只对自己的onShow负责反而更稳。5.2 uni-app 的页面生命周期写法如果你用 uni-app最常见的坑是拿mounted代替页面生命周期前面已经详细说过了。正确写法如下script setup import { onShow, onHide, onUnload } from dcloudio/uni-app onShow(() { uni.setKeepScreenOn({ keepScreenOn: true }) }) onHide(() { // 不建议在这里设 false理由同上 }) /script注意onHide、onUnload没有特别必要就别去动常亮状态除非你的业务明确要求页面一旦离开就立刻取消常亮。5.3 用 Behavior 做一处维护、全局生效如果小程序里有十几个页面都需要常亮每个页面复制粘贴onShow调用太蠢了也容易漏。原生小程序可以用Behavior做一个公共能力// keepScreenOnBehavior.js module.exports Behavior({ onShow() { wx.setKeepScreenOn({ keepScreenOn: true }) } })页面里只需要挂载这个 behaviorconst keepScreenOnBehavior require(../../behaviors/keepScreenOnBehavior) Page({ behaviors: [keepScreenOnBehavior] })对于不需要常亮的页面不挂载这个 behavior 即可。这样全局只有一份常亮设置逻辑想调整的时候不用翻十几个页面。需要提醒的是页面使用behaviors对基础库版本有要求如果项目要兼容很老的微信版本建议退一步抽一个公共函数在每个页面的onShow里手动调用维护成本虽然高一点但兼容性更稳。6. 排查复盘一张表定位问题把前面所有内容收拢成一张快速定位表遇到无效直接对照现象优先排查处理手段工具里测试无效本来就不生效别浪费时间换真机预览真机首次进入就无效基础库版本、系统省电模式用wx.canIUse检测提示用户关省电模式首次有效切后台回来失效调用写在 onLoad 或 mounted改到 onShow回前台重新设置跨页面跳转后失效全局 API 状态被覆盖每个需要常亮的页面 onShow 手动设置全屏视频播放时熄屏原生组件接管电源策略监听 video 播放事件重新设置iOS 上回前台偶发无效页面活跃时序延迟 200-500ms 再调用低电量提示后熄屏系统省电策略兜底页面内引导关闭省电模式我现在的常规排查顺序是这样的先确认基础库支持再改到onShow然后真机复现排除省电模式最后检查是不是原生组件场景。这套顺序走下来九成以上的setKeepScreenOn 无效都能定位到根因。真正让我在无效上卡得最久的不是这个 API 有多难而是我一度盲目相信success回调等于屏幕真的常亮了。现在每次接到类似问题我都会先问一句你是在开发者工具里测的还是真机上测的这个问题的答案能直接砍掉一半的无效排查工作。