OpenHarmony应用开发-线程间通讯与任务管理
版本:v3.2 Beta5
线程模型
OpenHarmony应用中每个进程都会有一个主线程,主线程有如下职责:
- 负责管理其他线程
- 同应用多个UIAbility组件共用一个主线程
- 输入事件分发
- UI绘制
- 应用代码回调(事件处理,生命周期)
- 接收Worker发送的消息
除主线程外,还有一类与主线程并行的独立线程Worker,主要用于执行耗时操作,但不可以直接操作UI。Worker线程在主线程中创建,与主线程相互独立。最多可以创建7个Worker:
基于OpenHarmony的线程模型,不同的业务功能运行在不同的线程上,业务功能的交互就需要线程间通信。线程间通信目前主要有Emitter和Worker两种方式,其中Emitter主要适用于线程间的事件同步, Worker主要用于新开一个线程执行耗时任务。
说明:
- Stage模型只提供了主线程和Worker线程,Emitter主要用于主线程内或者主线程和Worker线程的事件同步。
使用Emitter进行线程间通信
Emitter主要提供线程间发送和处理事件的能力,包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。
Emitter的开发步骤如下:
- 订阅事件
import emitter from "@ohos.events.emitter";
// 定义一个eventId为1的事件
let event = {
eventId: 1
};
// 收到eventId为1的事件后执行该回调
let callback = (eventData) => {
console.info('event callback');
};
// 订阅eventId为1的事件
emitter.on(event, callback);
- 发送事件
import emitter from "@ohos.events.emitter";
// 定义一个eventId为1的事件,事件优先级为Low
let event = {
eventId: 1,
priority: emitter.EventPriority.LOW
};
let eventData = {
data: {
"content": "c",
"id": 1,
"isEmpty": false,
}
};
// 发送eventId为1的事件,事件内容为eventData
emitter.emit(event, eventData);
使用Worker进行线程间通信
Worker是与主线程并行的独立线程。创建Worker的线程被称为宿主线程,Worker工作的线程被称为Worker线程。创建Worker时传入的脚本文件在Worker线程中执行,通常在Worker线程中处理耗时的操作,需要注意的是,Worker中不能直接更新Page。
Worker的开发步骤如下:
- 在工程的模块级build-profile.json5文件的buildOption属性中添加配置信息。
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/workers/worker.ts"
]
}
}
- 根据build-profile.json5中的配置创建对应的worker.ts文件。
import worker from '@ohos.worker';
let parent = worker.workerPort;
// 处理来自主线程的消息
parent.onmessage = function(message) {
console.info("onmessage: " + message)
// 发送消息到主线程
parent.postMessage("message from worker thread.")
}
- 主线程中使用如下方式初始化和使用worker。
- Stage模型:
import worker from '@ohos.worker';
let wk = new worker.ThreadWorker("entry/ets/workers/worker.ts");
// 发送消息到worker线程
wk.postMessage("message from main thread.")
// 处理来自worker线程的消息
wk.onmessage = function(message) {
console.info("message from worker: " + message)
// 根据业务按需停止worker线程
wk.terminate()
}
- FA模型:
import worker from '@ohos.worker';
let wk = new worker.ThreadWorker("../workers/worker.ts");
// 发送消息到worker线程
wk.postMessage("message from main thread.")
// 处理来自worker线程的消息
wk.onmessage = function(message) {
console.info("message from worker: " + message)
// 根据业务按需停止worker线程
wk.terminate()
}
说明:
- build-profile.json5中配置的worker.ts的相对路径都为
./src/main/ets/workers/worker.ts
时,在Stage模型下创建worker需要传入路径entry/ets/workers/worker.ts
;在FA模型下创建worker需要传入路径../workers/worker.ts
。- 主线程与Worker线程间支持的数据类型参考序列化支持类型。
任务管理场景介绍
任务管理相关的基本概念如下:
- AbilityRecord:系统服务侧管理一个UIAbility实例的最小单元,对应一个应用侧的UIAbility组件实例。
- MissionRecord:任务管理的最小单元。一个MissionRecord中仅有一个AbilityRecord,即一个UIAbility组件实例对应一个单独的任务。
- MissionList:一个从桌面开始启动的任务列表,记录了任务之间的启动关系,上一个任务由下一个任务启动,最底部的任务由桌面启动,这里称之为任务链。
- MissionListManager:系统任务管理模块,内部维护了当前所有的任务链,与最近任务列表保持一致。图1任务管理示意图
任务的管理由系统应用(如桌面应用)负责,三方应用无法管理任务。用户通过最近任务列表进行任务的相关交互。当创建任务后,用户可以对最近任务列表进行如下操作:
- 删除一个指定的任务。
- 加锁或解锁一个指定的任务(加锁后的任务在清理所有任务时不会被清理)。
- 清理最近任务列表中的所有任务。
- 将一个指定的任务切换到前台。
一个UIAbility实例对应一个单独的任务,因此应用调用startAbility()方法启动一个UIAbility时,就是创建了一个任务。
桌面应用调用missionManager的接口管理任务,需要申请ohos.permission.MANAGE_MISSIONS
权限,配置方式请参阅访问控制授权申请指导。
利用missionManager进行任务管理(监听任务变化、获取任务信息、获取任务快照、清理任务、任务加锁/解锁等),示例代码如下:
import missionManager from '@ohos.app.ability.missionManager'
let listener = {
// 任务创建
onMissionCreated: function (mission) {
console.info("--------onMissionCreated-------")
},
// 任务销毁
onMissionDestroyed: function (mission) {
console.info("--------onMissionDestroyed-------")
},
// 任务快照变化
onMissionSnapshotChanged: function (mission) {
console.info("--------onMissionSnapshotChanged-------")
},
// 任务被移动到前台
onMissionMovedToFront: function (mission) {
console.info("--------onMissionMovedToFront-------")
},
// 任务图标变化
onMissionIconUpdated: function (mission, icon) {
console.info("--------onMissionIconUpdated-------")
},
// 任务名称变化
onMissionLabelUpdated: function (mission) {
console.info("--------onMissionLabelUpdated-------")
},
// 任务实例被关闭
onMissionClosed: function (mission) {
console.info("--------onMissionClosed-------")
}
};
// 1.注册任务变化通知
let listenerId = missionManager.on('mission', listener);
// 2.获取系统最近20个任务
missionManager.getMissionInfos("", 20, (error, missions) => {
console.info("getMissionInfos is called, error.code = " + error.code);
console.info("size = " + missions.length);
console.info("missions = " + JSON.stringify(missions));
});
// 3.获取单个任务的详细信息()
let missionId = 11; // 11只是示例,实际是从系统中获取的任务id,下面类似
let mission = missionManager.getMissionInfo("", missionId).catch(function (err) {
console.info(err);
});
// 4.获取任务快照
missionManager.getMissionSnapShot("", missionId, (error, snapshot) => {
console.info("getMissionSnapShot is called, error.code = " + error.code);
console.info("bundleName = " + snapshot.ability.bundleName);
})
// 5.获取低分辨任务快照
missionManager.getLowResolutionMissionSnapShot("", missionId, (error, snapshot) => {
console.info("getLowResolutionMissionSnapShot is called, error.code = " + error.code);
console.info("bundleName = " + snapshot.ability.bundleName);
})
// 6.加锁/解锁任务
missionManager.lockMission(missionId).then(() => {
console.info("lockMission is called ");
});
missionManager.unlockMission(missionId).then(() => {
console.info("unlockMission is called ");
});
// 7.把任务切到前台
missionManager.moveMissionToFront(missionId).then(() => {
console.info("moveMissionToFront is called ");
});
// 8.删除单个任务
missionManager.clearMission(missionId).then(() => {
console.info("clearMission is called ");
});
// 9.删除全部任务
missionManager.clearAllMissions().catch(function (err) {
console.info(err);
});
// 10.解注册任务变化通知
missionManager.off('mission', listenerId, (error) => {
console.info("unregisterMissionListener");
})
任务管理与启动模式
如前文所述,一个UIAbility实例对应一个任务。UIAbility实例个数与UIAbility配置的启动模式有关。在FA模型下,通过config.json配置文件中的“launchType”属性配置;在Stage模型下,通过module.json5配置文件中的“launchType”属性配置。
下面介绍了任务管理如何实现以下三种启动模式UIAbility组件的管理:
- singleton:单实例模式,应用在运行时只存在一个该UIAbility实例。
图1 任务与singleton模式
- standard:多实例模式,每次调用startAbility()方法,都会在应用进程中创建一个该Ability的实例。
图2 任务与standard模式
- specified:指定实例模式,由AbilityStage的(onAcceptWant)决定是否创建新的实例。
图3 任务与specified模式
每个UIAbility实例都对应了一个最近任务列表中看到的Mission(任务)。
每个UIAbility实例对应的Mission都保留有该UIAbility实例的快照(Snapshot),UIAbility实例销毁后,Mission信息(包括Ability信息和任务快照)依然会保留,直到用户删除该任务。
说明: specified模式只在Stage模型上支持,FA模型不支持。
页面栈及任务链
页面栈
单个UIAbility组件可以实现多个页面,并在多个页面之间跳转,这种UIAbility组件内部的页面跳转关系称为“页面栈”,由ArkUI框架统一管理,如下图中的UIAbility1的Page1->Page2->Page3和UIAbility2的PageA->PageB->PageC。
图1 页面栈示意图
- 页面栈的形成(下面2/3/5/6步骤为页面跳转,由ArkUI管理)
- 点击桌面图标(startAbility)启动UIAbility1,UIAbility1的初始页面为Page1。
- 点击Page1页面按钮(Navigator)跳转到Page2页面。
- 点击Page2页面按钮(Navigator)跳转到Page3页面。
- 点击Page3页面按钮(startAbility)跳转到UIAbility2,UIAbility2的初始页面为PageA。
- 点击PageA页面按钮(Navigator)跳转到PageB页面。
- 点击PageB页面按钮(Navigator)跳转到PageC页面。
- 页面栈的返回(下面1/2/4/5步骤为页面跳转,由ArkUI管理)
- 在UIAbility2的PageC页面点击返回键回到UIAbility2的PageB页面。
- 在UIAbility2的PageB页面点击返回键回到UIAbility2的PageA页面。
- 在UIAbility2的PageA页面点击返回键跳转到UIAbility1的Page3页面。
- 在UIAbility1的Page3页面点击返回键回到UIAbility1的Page2页面。
- 在UIAbility1的Page2页面点击返回键回到UIAbility1的Page1页面。
- 在UIAbility1的Page1页面点击返回键回到桌面。
任务链
上文介绍了页面栈的返回,如果Ability2页面栈一层层通过返回键返回到最底层,再次点击返回键时,会返回到Ability1。因为在MissionList中记录了任务(Mission)之间的启动关系,即如果Ability1通过startAbility启动Ability2,则会形成一个MissionList任务链:Ability1->Ability2,当Ability2页面栈返回到首页时,再次点击返回键,会返回到Ability1的页面。
MissionList任务链记录了任务之间的拉起关系,但是这个任务链可能会断开,有以下几种情况会导致任务链的断开:
- 进入任务列表,把任务链中间某个任务移动到前台。
- 进入任务列表,把任务链中间某个任务清理掉。
- 单实例UIAbility的任务,被不同的任务反复拉起(AbilityB为单例)。