Android 上 uni.getSystemInfoSync().theme 总是 light,因其仅在冷启动或后台唤醒时读取一次系统主题快照,不监听深色模式变化广播,且依赖 Android 11+ 和 HBuilderX 3.6+。
uni.getSystemInfoSync().theme 在 Android 上为什么总是 light?
因为 android 端 theme 字段不是实时上报的,也不是所有机型都支持——它依赖系统 api 30+(android 11)及 hbuilderx 3.6+ 的底层桥接,且仅在**冷启动或应用从后台唤醒时**才读取一次。你调用 uni.getsysteminfosync() 得到的 theme 值,大概率是启动那一刻的快照,后续系统切换深色模式,它不会自动更新。
- 常见错误现象:用户开了“日落到日出自动切深色”,但 uni-app 页面始终显示浅色,
console.log(systemInfo.theme)一直输出"light" - 真实使用场景:需要响应式适配,比如夜间自动变暗、设置页手动开关后同步导航栏和内容区
- 根本原因:uni-app 没有监听 Android 的
UI_MODE_NIGHT_YES/NO广播,原生层未注册变化回调 - 实操建议:别只信
theme字段;改用window.matchMedia('(prefers-color-scheme: dark)')(H5 和部分小程序支持),或在 App.vue 的onShow中重新调用uni.getSystemInfoSync()触发重读(虽不完美,但比不动强)
如何让页面真正「自动响应」系统深色模式变化?
靠 CSS 媒体查询能搞定视觉层,但无法驱动逻辑(比如存用户偏好、改 API 请求头、控制图标颜色)。所以得把「检测 + 切换 + 同步」三件事串起来,不能只靠一个 onLoad。
- 推荐做法:在
App.vue的onShow钩子中执行主题检测,并通过$forceUpdate()或uni.$emit('themeChange', isDark)通知子组件 - 关键兼容点:Android 真机上
matchMedia不可用,iOS 和 H5 可靠;所以必须 fallback 到uni.getSystemInfoSync().theme+ 定期轮询(例如每 3 秒查一次,仅限前台活跃时) - 参数差异:
uni.getSystemInfoSync()返回的theme是字符串"light"/"dark",而matchMedia返回的是布尔值mql.matches,别混用 - 示例片段:
onShow() { const systemInfo = uni.getSystemInfoSync(); const isDark = systemInfo.theme === 'dark' || window.matchMedia?.('(prefers-color-scheme: dark)')?.matches; this.$store.commit('SET_THEME', isDark); }
pages.json 里用 @navBgColor 为啥没生效?
因为 @xxx 变量只在 pages.json 解析阶段起作用,它是一次性注入的静态值,不随运行时主题切换而刷新。你改了 store 里的主题状态,pages.json 不会重载、也不会监听变化。
- 常见错误现象:切换深色模式后,顶部导航栏颜色不变,但页面内容变了——说明样式走的是 CSS 变量,但导航栏走的是 pages.json 静态配置
- 实操建议:导航栏若需动态响应,放弃
@xxx,改用<uni-nav-bar dark></uni-nav-bar>组件属性(它会自动按深色逻辑设色),或用:background-color+v-bind绑定动态变量 - 性能影响:pages.json 主题变量解析发生在启动时,无运行时开销;但手动绑定
background-color会触发 style 计算,不过对导航栏这种固定结构影响极小 - 注意:只有
app-plus、mp-weixin等平台支持themeLocation,鸿蒙还额外要求app-harmony.darkmode: true,漏配就全白搭
用户手动切换主题后,如何避免下次打开还是系统默认?
系统主题只是初始值,用户一旦点过“深色/浅色”开关,你就该把它当最高优先级,覆盖系统判断。否则就会出现:用户明明选了深色,重启 App 又变回浅色。
- 核心逻辑:主题来源分三级——用户手动设置 > 系统当前模式 > 默认浅色;必须把用户选择持久化
- 实操建议:用
uni.setStorageSync('themeMode', 'dark')存,用uni.getStorageSync('themeMode')读;首次加载时优先读这个,不存在再 fallback 到系统 - 容易踩的坑:没清缓存就改逻辑,导致旧值残留;或者在多个地方(如登录后、设置页、App.vue)重复写入,引发竞态
- 补充提醒:不要用
uni.setStorage(异步),它可能在页面渲染完才写入,造成闪屏;setStorageSync更稳妥










