HarmonyOS鸿蒙Next中协同连接定时b设备给a设备每秒发送数据,b设备后台一分钟左右就被冻结。

HarmonyOS鸿蒙Next中协同连接定时b设备给a设备每秒发送数据,b设备后台一分钟左右就被冻结。 abilityConnectionManager实现设备通信分布式任务, 开启了长时任务multiDeviceConnection,权限也配置了, 逻辑是a连接b,连接成功后,a和b都启动后台长时任务,然后b定时每秒采集设备的某个信息给a发送json数据,但是后台一会儿几十秒就被冻结了,下图的通知消息在应用被冻结也会被清除。

应该怎么做啊。

cke_797.jpeg


更多关于HarmonyOS鸿蒙Next中协同连接定时b设备给a设备每秒发送数据,b设备后台一分钟左右就被冻结。的实战教程也可以访问 https://www.itying.com/category-93-b0.html

19 回复

尊敬的开发者,您好,

感谢您的反馈,您上述的问题经内部确认,是因为后台任务只能运行不超过1min,超过1min后会被系统冻结的限制导致的,该问题将在后续版本中修复,版本发布时间以官网为准。

更多关于HarmonyOS鸿蒙Next中协同连接定时b设备给a设备每秒发送数据,b设备后台一分钟左右就被冻结。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


看来我的反馈还是有效

最近分布式问题被清除后台问题有很多,建议提交工单反馈一下。

我这边刚好也在做这个,是存在这个问题

cke_142.png

跟你一模一样。而且你插上充电器,能管得久一些

解决了吗朋友

木有解决; 感觉短时任务都比长时任务时间长
不知道是不是进入后台后,系统没有识别出来当前操作是多设备互联(multiDeviceConnection),然后隔了一分钟杀掉了

尊敬的开发者,您好!感谢您的反馈,问题正在加速处理中,还请关注后续版本,感谢您的理解与支持。

尊敬的开发者,您好!感谢您的反馈,问题正在加速处理中,还请关注后续版本,感谢您的理解与支持。

你这个现象,大概率不是代码里 setIntervalsendMessage 本身坏了,而是 b 端后台长时任务被系统撤销了,进程随后被冻结/挂起,所以你看到:

  • 后台几十秒到 1 分钟左右就不再发送
  • 通知栏里“正在运行分布式任务,删除通知后任务将停止”的通知也一起消失
  • 之后 b -> a 的定时发送自然全停

从官方机制看,这个判断比较明确:

  • 后台长时任务只适用于“真实且持续存在的对应业务”,系统会持续做一致性校验;如果业务与申请类型不匹配、业务已结束、或后台负载持续异常,应用会被挂起或终止。HarmonyOS 长时任务文档
  • MULTI_DEVICE_CONNECTION 的适用场景是“分布式业务连接、投播”,不是“只要有个定时器在发数据就一定能永久保活”。HarmonyOS 长时任务文档
  • 协同连接本身确实要求申请长时任务,否则退后台 5 秒后会被 DMS 结束协同状态;但你现在已经不是这个 5 秒问题,而是更后面的系统持续校验没通过。 跨设备连接 UIAbility 开发指南

先说结论

你现在这套“a 连接 b,双端都起 multiDeviceConnection,然后 b 在后台每秒采集一次并给 a 发 JSON” 的设计,不一定能长期稳定跑。

原因通常有 4 类:

  1. b 端实际业务不被系统判定为“持续的多设备连接业务”
  2. 业务行为更像“后台持续数据采集/上报”,但只申请了 multiDeviceConnection,任务类型和真实业务不完全匹配
  3. b 端后台负载偏高,持续采集 + 编码 JSON + 高频通信,被系统判成异常后台负载
  4. 你的长时任务其实已被取消/暂停,但应用里没有监听取消原因,所以表面上只看到“冻结了”

你这个场景最容易踩的坑

1. 把 multiDeviceConnection 当成“万能后台保活”

不是这样。

multiDeviceConnection 只能说明你有“协同连接/多设备互联”这个场景,但不能替代其他真实业务类型
如果 b 端在后台主要做的是:

  • 周期性采集数据
  • 每秒封装 JSON
  • 持续网络/分布式发送

那系统可能会认为这更接近:

  • 数据传输
  • 传感器/采集类后台行为
  • 或“后台计算负载”

而不是单纯的“多设备连接保持中”。

2. “连接还在”不代表“长时任务校验通过”

哪怕 sessionId 还没主动 disconnect只要系统认为后台任务真实性不足,照样可以撤销长时任务,然后冻结进程。
通知消失,本身就是很强的信号:长时任务没了

3. 每秒发送一次,对后台很可能太激进

每秒一次不一定绝对违规,但在后台长时间持续:

  • 采集
  • 序列化
  • IPC/分布式发送
  • 日志打印

很容易把负载拉高,尤其如果你还做了:

  • 高频 console/hilog
  • 每次都 JSON.stringify
  • 每次都构造大对象
  • 每次都走 UI 层状态更新

系统文档明确提到:后台负载持续高于该类型典型负载时,应用会被挂起或终止。
这和你“几十秒到一分钟被冻结”非常吻合。

应该怎么做

第一优先级:先把“为什么被撤销”打出来

不要先继续猜权限或配置,先把长时任务的状态监听补齐。

至少加这几个监听:

  • backgroundTaskManager.on('continuousTaskCancel', ...)
  • backgroundTaskManager.on('continuousTaskSuspend', ...)
  • backgroundTaskManager.on('continuousTaskActive', ...)

这样你能知道:

  • 是被 cancel
  • 还是先 suspend
  • 原因码是什么
  • 发生在前后台切换后多久

这一步非常关键。你现在缺的不是“再试一个权限”,而是证据链

思路是这样:

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

export function registerBgTaskListener() {
  try {
    backgroundTaskManager.on('continuousTaskCancel', (info) => {
      console.error(`continuousTaskCancel id=${info.id}, reason=${info.reason}`);
    });

    backgroundTaskManager.on('continuousTaskSuspend', (info) => {
      console.error(`continuousTaskSuspend id=${info.id}, reason=${info.reason}`);
    });

    backgroundTaskManager.on('continuousTaskActive', (info) => {
      console.info(`continuousTaskActive id=${info.id}`);
    });
  } catch (e) {
    const err = e as BusinessError;
    console.error(`register listener failed: ${err.code}, ${err.message}`);
  }
}

然后同时监听协同连接:

  • abilityConnectionManager.on('connect', ...)
  • abilityConnectionManager.on('disconnect', ...)

把时间戳都记下来,确认到底是:

  • 先丢长时任务
  • 还是先断协同
  • 还是进程先被冻结

第二优先级:别让 b 端单纯“后台每秒轮询 + 发送”

这是我最建议你改的点。

推荐改法

把“每秒采集一次”改成“事件驱动 + 必要时节流发送”:

  • 设备信息变化时才发送
  • 没变化就不发
  • 真要周期发送,先降到 3s5s10s 验证
  • 做增量发送,不要每次发完整大 JSON

例如:

  • 状态没变化:不发
  • 变化频繁:最多 2 秒合并发一次
  • 只发变化字段

这样既更像真实协同业务,也更容易通过后台负载校验。

第三优先级:确认长时任务申请位置和时机

官方限制里有一个很重要的点:

  • Stage 模型下,只有 UIAbility 能申请长时任务

所以你要确认:

  • 是不是在 UIAbility 上下文里申请的
  • 不是在普通工具类、worker、其他非合法上下文里间接搞的
  • b 端被拉起后,在真正开始后台业务前就申请成功
  • 不是连上后过一会儿才补申请

另外,如果你是 API 21+,建议检查是否需要用多类型申请,而不是只起一个 multiDeviceConnection

第四优先级:考虑“业务类型是否选错了”

如果你的真实场景是:

  • ab 保持协同连接
  • b 连续采集数据
  • 持续向 a 发送数据流

那它未必只是 MULTI_DEVICE_CONNECTION

更合理的判断是:

  • 如果重点是“协同链路保持” -> multiDeviceConnection
  • 如果重点是“持续数据发送/传输” -> 可能还要考虑 dataTransfer
  • 如果有蓝牙连接设备采集 -> 可能涉及 bluetoothInteraction
  • 如果本质是高频后台计算/处理 -> 普通三方应用通常没有真正无限后台计算资格

如果你当前系统版本支持同一个 UIAbility 同时申请多个长时任务类型,可以评估是否要组合申请。官方文档说明 API 21+ 一个 UIAbility 最多可同时申请 10 个长时任务类型Background Task Management API

但注意:

  • 不是多申请几个就一定过
  • 前提仍然是“真实业务和申请类型一致”

第五优先级:别依赖通知“存在”来判断保活成功

你截图里的通知文案已经说明了:

删除通知后任务将停止

这说明通知和长时任务绑定。
如果应用被冻结时通知也被系统清掉,通常说明:

  • 长时任务被取消
  • 或对应进程已失去保活资格

所以要靠监听回调和日志,不要只看通知是否还在。

更稳妥的架构建议

如果你这个需求是“b 设备长期产生数据给 a”,我建议按下面思路重构:

方案 A:协同连接只负责会话,不负责高频常驻采集

  • a 发起连接,建立 session
  • b 只在“有新数据/有明显变化”时推送
  • 后台不做 1 秒硬轮询
  • 切回前台时再恢复高频模式

这是最容易合规的。

方案 B:后台只做低频保活,前台做高频采集

  • 后台时改成 5s10s
  • 前台时恢复 1s
  • UI 上明确告诉用户:后台为省电进入低频同步

这是很多系统允许、也更现实的方案。

方案 C:如果本质是“持续采集设备状态”

那就不要把核心方案完全压在 abilityConnectionManager + multiDeviceConnection 上。
你要重新审视:

  • 采集来源是什么
  • 是否有该硬件/能力自己的后台规范
  • 是否该由系统能力事件回调驱动,而不是你自己轮询
  • 是否需要本地缓存,等前台/唤醒后再批量同步

我对你当前问题的直接判断

核心问题不是“怎么让 b 永远不被冻结”,而是:

HarmonyOS 并不保证三方应用可以靠 multiDeviceConnection 长时间稳定运行一个“每秒采集+发送”的后台循环。

你现在能做的正确方向是:

  1. continuousTaskCancel/suspend/active 日志
  2. connect/disconnect 日志
  3. 把发送频率从 1s 降到 3s/5s 做 AB 测试
  4. 改为“变化触发发送”,不要固定轮询
  5. 检查是否应该组合申请任务类型,而不是只用 multiDeviceConnection
  6. 确认长时任务一定由 UIAbility 合法申请
  7. 降低后台负载:少日志、少大对象 JSON、少无效发送

最后给你一个很实用的判断标准

如果你把下面三件事做完后,后台还是几十秒被撤:

  • 已监听到取消/暂停原因
  • 已把发送频率明显降低
  • 已改成“数据变化才发送”

那基本可以判断:

这个业务模型本身不适合在三方应用后台长期 1 秒级持续运行。

这时就不要继续死磕“怎么保活”,而要改产品方案:
改成低频同步、事件驱动、前台增强、后台降级。

问题的核心是:

  1. 设备 A 连接设备 B(分布式通信);
  2. 连接成功后,A 和 B 都启动后台长时任务
  3. B 每秒采集设备信息并发送 JSON 数据给 A
  4. 几十秒后后台被冻结,通知消息也被清除

大概率是HarmonyOS 分布式任务保活机制未正确配置的问题,建议从以下几个方面分析:

  1. 正确的后台任务类型 - 必须使用 DATA_TRANSFER 或 其他必要的类型
  2. 持续的通知更新 - 通知被清除会导致任务被系统回收(缺少心跳保活机制:系统会检测到任务无实际活动而冻结)
  3. 分布式权限配置 - 需要 ohos.permission.DISTRIBUTED_DATASYNC
  4. 设备间的连接状态保持 - 连接断开会导致任务终止
  5. 资源使用限制 - 频繁的数据传输可能被系统判定为滥用

关于正确的长时任务配置,可参考以下代码(仅供参考):

import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { notificationManager } from '@kit.NotificationKit';
import { wantAgent } from '@kit.AbilityKit';
import { deviceManager } from '@kit.DistributedDeviceManagerKit';
import { common } from '@kit.AbilityKit';

class DistributedTaskManager {
  private notificationId: number = 0;
  private taskId: number = 0;
  private isRunning: boolean = false;
  private keepAliveTimer: number = 0;

  /**
   * 启动分布式长时任务(设备端 B - 采集端)
   */
  async startDistributedTask(context: common.UIAbilityContext): Promise<boolean> {
    try {
      // 1. 创建 WantAgent(点击通知时拉起应用)
      const wantAgentInfo: wantAgent.WantAgentInfo = {
        wants: [{
          bundleName: context.abilityInfo.bundleName,
          abilityName: context.abilityInfo.name
        }],
        actionType: wantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      };
      
      const agent = await wantAgent.getWantAgent(wantAgentInfo);

      // 2. 申请长时任务(使用数据传输类型)
      const result = await backgroundTaskManager.startBackgroundRunning(
        context,
        [backgroundTaskManager.BackgroundTaskType.DATA_TRANSFER],
        agent
      );

      if (!result?.notificationId) {
        console.error('申请长时任务失败');
        return false;
      }

      this.notificationId = result.notificationId;
      this.isRunning = true;

      // 3. 发布持久化通知(关键!不能被清除)
      await this.publishPersistentNotification(context);

      // 4. 启动保活定时器(每 30 秒更新一次通知,防止被系统回收)
      this.startKeepAliveTimer(context);

      // 5. 启动数据采集任务
      this.startDataCollection(context);

      console.info('分布式长时任务启动成功');
      return true;
    } catch (error) {
      console.error(`启动分布式任务失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 发布持久化通知(防止被清除)
   */
  private async publishPersistentNotification(context: common.UIAbilityContext): Promise<void> {
    const notificationRequest: notificationManager.NotificationRequest = {
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_NORMAL,
        normal: {
          title: '分布式数据采集',
          text: '正在采集设备数据...'
        }
      },
      id: this.notificationId,
      notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION,
      // 关键配置:设置为不可清除
      isOngoing: true,           // 正在进行中,不可滑动清除
      isUnRemovable: true,       // 不可移除
      alertOnce: false           // 不重复提醒
    };

    await notificationManager.publish(notificationRequest);
  }

  /**
   * 启动保活定时器(每 30 秒更新通知)
   */
  private startKeepAliveTimer(context: common.UIAbilityContext): void {
    // 清除旧定时器
    if (this.keepAliveTimer) {
      clearTimeout(this.keepAliveTimer);
    }

    const keepAlive = () => {
      if (!this.isRunning) return;

      // 更新通知时间戳,告诉系统任务仍在运行
      const currentTime = new Date().toLocaleTimeString();
      const notificationRequest: notificationManager.NotificationRequest = {
        content: {
          notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_NORMAL,
          normal: {
            title: '分布式数据采集',
            text: `正在采集设备数据... ${currentTime}`
          }
        },
        id: this.notificationId,
        notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION,
        isOngoing: true,
        isUnRemovable: true
      };

      notificationManager.publish(notificationRequest).catch(err => {
        console.error(`更新通知失败: ${JSON.stringify(err)}`);
      });

      // 每 30 秒执行一次
      this.keepAliveTimer = setTimeout(keepAlive, 30000) as unknown as number;
    };

    keepAlive();
  }

  /**
   * 启动数据采集(每秒采集一次)
   */
  private startDataCollection(context: common.UIAbilityContext): void {
    const collectData = async () => {
      if (!this.isRunning) return;

      try {
        // 1. 采集设备数据
        const deviceData = await this.collectDeviceData();

        // 2. 发送到设备 A
        await this.sendDataToDeviceA(deviceData);

        // 3. 每秒执行一次
        setTimeout(collectData, 1000);
      } catch (error) {
        console.error(`数据采集失败: ${JSON.stringify(error)}`);
        // 出错后延迟 5 秒重试
        setTimeout(collectData, 5000);
      }
    };

    collectData();
  }

  /**
   * 采集设备数据
   */
  private async collectDeviceData(): Promise<string> {
    // 模拟采集设备信息
    const data = {
      timestamp: Date.now(),
      cpuUsage: Math.random() * 100,
      memoryUsage: Math.random() * 100,
      batteryLevel: 85,
      temperature: 36.5
    };

    return JSON.stringify(data);
  }

  /**
   * 发送数据到设备 A(使用分布式通信)
   */
  private async sendDataToDeviceA(data: string): Promise<void> {
    try {
      // 使用 abilityConnectionManager 发送数据
      // 这里需要根据实际的连接方式实现
      console.info(`发送数据到设备 A: ${data}`);
      
      // 示例代码(需要根据实际情况修改)
      // await abilityConnectionManager.sendMessage({
      //   deviceId: targetDeviceId,
      //   data: data
      // });
    } catch (error) {
      console.error(`发送数据失败: ${JSON.stringify(error)}`);
      throw error;
    }
  }

  /**
   * 停止分布式任务
   */
  async stopDistributedTask(context: common.UIAbilityContext): Promise<void> {
    try {
      this.isRunning = false;

      // 清除定时器
      if (this.keepAliveTimer) {
        clearTimeout(this.keepAliveTimer);
        this.keepAliveTimer = 0;
      }

      // 取消通知
      if (this.notificationId > 0) {
        await notificationManager.cancel(this.notificationId);
        this.notificationId = 0;
      }

      // 停止长时任务
      await backgroundTaskManager.stopBackgroundRunning(context);

      console.info('分布式长时任务已停止');
    } catch (error) {
      console.error(`停止任务失败: ${JSON.stringify(error)}`);
    }
  }
}

export const distributedTaskManager = new DistributedTaskManager();

也可考虑其他方案,比如添加设备连接状态监控(防止断连),判断数据传输频率是否过高…

不要在页面里申请长时任务,在 UIAbility 里和应用生命周期强绑定试试。

你补充的“插电更久、dataTransfer 只多撑一会儿”,基本说明不是单纯配置漏项,而是触发了长时任务的一致性/能耗管控。长时任务保的是符合类型的用户可感知业务,不等于允许后台每秒高频采集和发送常驻运行。建议注册 backgroundTaskManager.on(‘continuousTaskCancel’) 打印 reason;确有大块数据传输再同时声明 multiDeviceConnection + dataTransfer;普通 JSON 心跳/采样最好改成事件驱动或批量低频发送,后台 5~30 秒合并一次会更稳。

你这有数据传输,再加个dataTransfer

"backgroundModes": ["multiDeviceConnection", "dataTransfer"]

如不解决,代码层加心跳,互传消息。

我之前试了,dataTransfer要比multi…多保持一分钟。而且插上充电器能更久。

加心跳试了吗,可以考虑加实况窗

心跳也试过了,实况窗申请不到

在该场景中,B设备后台被冻结是HarmonyOS Next系统对后台服务资源的默认管理策略。系统会检测持续高频数据传输(如每秒发送),判定为高能耗行为,从而在一分钟左右自动冻结后台应用以节省资源和电量。此行为无法通过常规手段绕过。

在 HarmonyOS Next 中multiDeviceConnection 长时任务的设计目标是让系统在设备间保持数据传输通道活跃,但并不保证应用进程永不被挂起。当 B 设备进入后台,若仅依赖此长时任务,系统仍可能在资源紧张时冻结应用以省电,此时通知栏标记会被清除。

原因:

  • 长时任务仅向系统声明意图,若实际传输频率过高(每秒一次)且无实际数据变化或心跳,系统可能判定为非必要高频任务,优先挂起。
  • 后台应用被冻结后,长时任务对应的通知也会随进程停止而消失。

解决方向:

  • 确保长时任务申请成功,使用 startBackgroundRunning 时设置合理的 wantAgent,通知内容需持续存在,不可被用户清除。
  • 降低传输频率或采用“有变化即传”机制,避免固定每秒空轮询。
  • 若必须高频,可结合“音频播放”等更高级别的长时任务类型,或使用 WorkSchedulerDELIVERY 策略保证数据到达,但实效性会降低。
  • 在 B 设备侧添加前台 Service(带有不可取消的常驻通知),强制提升进程优先级,防止冻结。
回到顶部