HarmonyOS 保持屏幕常亮与后台持锁:setWindowKeepScreenOn 与 runningLock 怎么选

前言

在华为开发者论坛,一个高频问题是「鸿蒙系统如何设置禁止休眠(熄屏)」。很多开发者第一反应是去找一个全局开关,希望自己的应用一启动,整台手机就永不熄屏。这种预期需要首先被纠正:第三方应用并没有能力真正接管系统的熄屏策略。

系统何时熄屏,由电源管理(Power Manager)统一决策,受用户设置、电池策略、温控等多重因素约束。应用能做且只做两件事:第一,在自身处于前台时,请求「当前窗口保持常亮」;第二,在退到后台仍需持续工作时,通过后台长时任务 + 持锁机制让进程不被回收,但屏幕该熄屏仍然会熄屏。本文把这两条路径讲清,并给出可直接落地的选型与代码。

问题描述

原帖诉求是「禁止系统休眠」。把它翻译成工程语言,实际落地时通常只有三种具体场景:

  1. 用户正在看阅读页、导航页、视频页时,不要中途灭屏——这是前台常亮需求。
  2. 应用退到后台后,例如导航语音播报、运动轨迹记录,仍需持续运行一段时间——这是后台保活需求,但屏幕此时本就该灭。
  3. 自动化测试或演示时希望设备不锁屏——这属于调试手段,不应写进应用逻辑。

把「禁止系统休眠」这一个大而模糊的需求,拆成「前台常亮」与「后台持锁」两个正交的能力,是正确实现的第一步。混淆二者,要么导致常亮忘关持续吃电,要么后台任务违规被系统冻结。

细节解析

核心机制有两套:@ohos.windowsetWindowKeepScreenOn 解决前台常亮;@ohos.runningLock 配合 backgroundTaskManager 解决后台保活。二者作用域完全不重叠,对比如下:

方案作用范围是否需权限适用场景被系统冻结风险
setWindowKeepScreenOn(true)仅本应用处于前台的窗口否,免权限免申请阅读、导航、视频等前台常亮无;但持续点亮屏幕会显著耗电
runningLock + 后台长时任务应用退到后台仍持锁运行需声明 ohos.permission.KEEP_BACKGROUND_RUNNING导航后台播报、数据同步、轨迹记录有;超时或滥用约 1 分钟后被冻结
@ohos.screenLock(MDM)设备级锁屏控制需设备管理(MDM)能力企业设备管控类应用无,但普通应用无此权限,不可用

关于 runningLock 的锁类型,需注意枚举命名以本机 SDK 的 RunningLockType 枚举为准,常见类型包括 BACKGROUND(阻止系统挂起 CPU/进程)、PROXIMITY_SCREEN_OFF(接近传感器熄屏)、接近点亮等,具体常量名请以你工程当前 SDK 的 API 文档为准,不要硬编码猜测的名字。

后台长时任务有硬性约束:必须在 module.json5 声明 backgroundModes(如 dataTransferlocationaudioPlayback),并调用 backgroundTaskManager.startBackgroundRunning()。系统对后台任务有超时上限,超时或长时间无有效进展会被判定为 THREAD_BLOCK 进而冻结应用,因此任务完成必须及时 stopBackgroundRunning()

示例代码

路径 A:页面级前台常亮封装,正确使用生命周期开关。

import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class ScreenKeepOnHelper {
  private win: window.Window | null = null;

  async enable(): Promise<void> {
    try {
      const context: common.UIAbilityContext = getContext() as common.UIAbilityContext;
      this.win = await window.getLastWindow(context);
      await this.win.setWindowKeepScreenOn(true);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`enable keep screen on failed, code: ${err.code}, message: ${err.message}`);
    }
  }

  async disable(): Promise<void> {
    if (this.win === null) {
      return;
    }
    try {
      await this.win.setWindowKeepScreenOn(false);
      this.win = null;
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`disable keep screen on failed, code: ${err.code}, message: ${err.message}`);
    }
  }
}

在页面中调用,务必在 onPageHideonBackPress 关闭,避免持续耗电:

import { ScreenKeepOnHelper } from '../utils/ScreenKeepOnHelper';

@Entry
@Component
struct ReadingPage {
  private helper: ScreenKeepOnHelper = new ScreenKeepOnHelper();

  onPageShow(): void {
    this.helper.enable();
  }

  onPageHide(): void {
    this.helper.disable();
  }

  onBackPress(): boolean {
    this.helper.disable();
    return false;
  }

  build() {
    Column() {
      Text('阅读内容区域')
        .fontSize(20)
    }
    .width('100%')
    .height('100%')
  }
}

路径 B:后台长时任务 + 持锁完整示例。

import { runningLock } from '@kit.BackgroundTasksKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class KeepAliveManager {
  private lock: runningLock.RunningLock | null = null;

  async startLongTask(): Promise<void> {
    const context: common.UIAbilityContext = getContext() as common.UIAbilityContext;
    try {
      await backgroundTaskManager.startBackgroundRunning(
        context,
        backgroundTaskManager.BackgroundMode.DATA_TRANSFER,
        { abilityName: 'EntryAbility', bundleName: context.abilityInfo.bundleName }
      );
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`startBackgroundRunning failed, code: ${err.code}, message: ${err.message}`);
      return;
    }
    try {
      this.lock = await runningLock.create('my_running_lock', runningLock.RunningLockType.BACKGROUND);
      this.lock.lock(runningLock.RunningLockType.BACKGROUND, 60000);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`create running lock failed, code: ${err.code}, message: ${err.message}`);
    }
  }

  async stopLongTask(): Promise<void> {
    const context: common.UIAbilityContext = getContext() as common.UIAbilityContext;
    if (this.lock !== null) {
      this.lock.unlock();
      this.lock = null;
    }
    try {
      await backgroundTaskManager.stopBackgroundRunning(context);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`stopBackgroundRunning failed, code: ${err.code}, message: ${err.message}`);
    }
  }
}

module.json5 权限与后台模式配置片段:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "requestPermissions": [
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
        "reason": "$string:keep_bg_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ts",
        "backgroundModes": [
          "dataTransfer",
          "location"
        ]
      }
    ]
  }
}

调试场景下防熄屏,无需写应用逻辑:执行 hdc shell power-shell wakeup 临时唤醒,或在「开发者选项」开启「充电时不锁定屏幕」即可,更适合自动化测试。

总结

「禁止系统休眠」在第三方应用层面不成立。正确做法是按场景选型:纯前台常亮用 setWindowKeepScreenOn,并在 onPageHide/onBackPress 及时关闭;后台持续运行用 runningLock + backgroundTaskManager 长时任务,声明 backgroundModesKEEP_BACKGROUND_RUNNING 权限,并在任务结束立即 stopBackgroundRunning 规避冻结;@ohos.screenLock 属 MDM 能力,普通应用不可用。把前台常亮与后台持锁两条正交路径分清,才能既满足体验又不踩电源策略与后台管控的红线。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐