EmbeddedUIExtensionAbility 笔记:跨进程界面嵌入怎么用?
这一篇是我给 EmbeddedUIExtensionAbility 写的学习笔记。 关键词就是:跨进程 UI 嵌入 + 模块隔离 + 和 EmbeddedComponent 配合使用。
1. 它到底是干嘛的?
官方一句话:
EmbeddedUIExtensionAbility 是
EMBEDDED_UI类型的 ExtensionAbility,提供跨进程界面嵌入能力。
翻译成「人话」就是:
- 有一个 UIAbility 在主进程 跑着;
- 它的页面里可以放多个
EmbeddedComponent; - 每个
EmbeddedComponent都可以把 另一个进程里 的 UI(就是 EmbeddedUIExtensionAbility 提供的)嵌进来; - 嵌进来的那块 UI:布局、渲染在单独进程完成,和 UIAbility 的数据不直接互通。
非常适合这种场景:
-
你做一个「办公类应用」:
- 主 UIAbility:负责整体 Shell(菜单栏、侧边栏、容器布局);
- 文档编辑、表格编辑、PPT 编辑: 各自用一个 EmbeddedUIExtensionAbility,在独立进程里跑;
-
这样:
- 各模块互相隔离(一个崩了不一定拖垮主进程);
- 更符合「大应用拆解成多个可独立演进模块」的思路。
2. 使用前先看限制
有两个很重要的前提:
-
只支持有「多进程配置」的设备
- 目前文档里点名支持:
2in1和Tablet; - 也就是说在一些手机设备上,暂时是用不了这套的。
- 目前文档里点名支持:
-
一些强约束:
EmbeddedComponent只能在 UIAbility 中使用;- 被拉起的 EmbeddedUIExtensionAbility 必须和 UIAbility 属于同一个应用(同一个 Bundle) 。
3. 生命周期:跟 UIExtensionAbility 一脉相承
EmbeddedUIExtensionAbility 的生命周期回调继承自 UIExtensionAbility,主要有这些:
-
onCreate()创建 ExtensionAbility 实例时调用,一般做一次性的初始化。 -
onSessionCreate(want, session)界面内容对象创建后 调用:- 会拿到一个
UIExtensionContentSession; - 通常在这里
session.loadContent()把具体 UI 页面加载出来。
- 会拿到一个
-
onSessionDestroy(session)界面内容对象销毁时调用,可以做会话级别的资源清理。 -
onForeground()从后台切到前台时调用。 -
onBackground()从前台切到后台时调用。 -
onDestroy()EmbeddedUIExtensionAbility 实例销毁时调用,做整体资源清理。
可以简单把它看成:
一个「跑在独立进程里的小 UI 模块」,通过 Session 跟宿主产生一对一的嵌入关系。
4. 提供方开发:写一个 EmbeddedUIExtensionAbility
4.1 新建能力文件
在 DevEco Studio 里大致步骤:
- 在某个 Module 的
ets目录下,新建目录,比如:EmbeddedUIExtAbility/; - 在这个目录里新建
EmbeddedUIExtAbility.ets文件; - 在里面继承
EmbeddedUIExtensionAbility,实现相关生命周期。
示例代码(提供方):
import { EmbeddedUIExtensionAbility, UIExtensionContentSession, Want } from '@kit.AbilityKit';
const TAG: string = '[ExampleEmbeddedAbility]';
export default class ExampleEmbeddedAbility extends EmbeddedUIExtensionAbility {
onCreate() {
console.info(TAG, `onCreate`);
}
onForeground() {
console.info(TAG, `onForeground`);
}
onBackground() {
console.info(TAG, `onBackground`);
}
onDestroy() {
console.info(TAG, `onDestroy`);
}
onSessionCreate(want: Want, session: UIExtensionContentSession) {
console.info(TAG, `onSessionCreate, want: ${JSON.stringify(want)}`);
// 把 session 丢进 LocalStorage,给页面用
let param: Record<string, UIExtensionContentSession> = {
'session': session
};
let storage: LocalStorage = new LocalStorage(param);
// 加载真正显示 UI 的页面
session.loadContent('pages/extension', storage);
}
onSessionDestroy(session: UIExtensionContentSession) {
console.info(TAG, `onSessionDestroy`);
}
}
关键点:
onSessionCreate()里通过session.loadContent('pages/extension', storage)加载 UI;- 通过
LocalStorage把session传到页面端,方便在页面逻辑中访问。
4.2 扩展页面:真正渲染的 UI
pages/extension.ets 大概这样写:
import { UIExtensionContentSession } from '@kit.AbilityKit';
@Entry()
@Component
struct Extension {
@State message: string = 'EmbeddedUIExtensionAbility Index';
// 拿到 SharedLocalStorage
localStorage: LocalStorage | undefined = this.getUIContext().getSharedLocalStorage();
private session: UIExtensionContentSession | undefined =
this.localStorage?.get<UIExtensionContentSession>('session');
build() {
Column() {
Text(this.message)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Button('terminateSelfWithResult')
.fontSize(20)
.onClick(() => {
this.session?.terminateSelfWithResult({
resultCode: 1,
want: {
bundleName: 'com.example.embeddeddemo',
abilityName: 'ExampleEmbeddedAbility'
}
});
})
}
.width('100%')
.height('100%')
}
}
这里有几个知识点:
-
通过
getSharedLocalStorage()拿到刚刚在扩展里塞进去的 LocalStorage; -
再用 key
'session'取出UIExtensionContentSession; -
session.terminateSelfWithResult(...):- 表示当前嵌入的这个 UI 模块「带着结果主动结束」;
- 外层的 EmbeddedComponent 可以通过回调拿到
resultCode和want。
4.3 在 module.json5 里注册
最后,得在 module.json5 中把这个 ExtensionAbility 注册出来:
{
"module": {
"extensionAbilities": [
{
"name": "EmbeddedUIExtAbility",
"icon": "$media:icon",
"description": "EmbeddedUIExtAbility",
"type": "embeddedUI", // ★ 类型必须是 embeddedUI
"srcEntry": "./ets/EmbeddedUIExtAbility/EmbeddedUIExtAbility.ets"
}
]
}
}
这里最重要的两个字段:
"type": "embeddedUI":指明这是 EmbeddedUIExtensionAbility;"srcEntry":指向刚才写的.ets文件。
5. 使用方开发:UIAbility 页面里嵌入它
提供方搞定之后,就轮到使用方 —— 也就是 UIAbility 页面,这里主要用 EmbeddedComponent。
示例:pages/Index.ets:
import { Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
@State message: string = 'Message: '
private want: Want = {
bundleName: 'com.example.embeddeddemo',
abilityName: 'EmbeddedUIExtAbility',
parameters: {
// 控制进程模式的参数(后面解释)
'ohos.extension.processMode.hostInstance': 'true'
}
}
build() {
Row() {
Column() {
Text(this.message).fontSize(30)
EmbeddedComponent(this.want, EmbeddedType.EMBEDDED_UI_EXTENSION)
.width('100%')
.height('90%')
.onTerminated((info: TerminationInfo) => {
this.message = 'Termination: code = ' + info.code
+ ', want = ' + JSON.stringify(info.want);
})
.onError((error: BusinessError) => {
this.message = 'Error: code = ' + error.code;
})
}
.width('100%')
}
.height('100%')
}
}
你可以重点记住:
-
EmbeddedComponent(this.want, EmbeddedType.EMBEDDED_UI_EXTENSION):want用来指定「嵌入哪一个 EmbeddedUIExtensionAbility」;- 第二个参数使用
EMBEDDED_UI_EXTENSION类型;
-
onTerminated:对方调用terminateSelfWithResult时,这里会回调; -
onError:嵌入过程出错时的兜底处理。
6. 进程模式:两个关键参数怎么玩?
EmbeddedUIExtensionAbility 支持更灵活的进程控制,通过 want.parameters 里的两个字段来调:
-
ohos.extension.processMode.hostSpecified-
含义:指定 非首次启动时 的进程名;
-
例子:
"ohos.extension.processMode.hostSpecified": "com.ohos.inentexecutedemo:share" -
作用:告诉系统「之后再启动这个 EmbeddedUIExtensionAbility,都跑在这个进程里」。
-
-
ohos.extension.processMode.hostInstance-
含义:控制是否按「独立进程」启动;
-
入参是字符串
"true"/"false"(注意是字符串):"false":跟随 UIExtensionAbility 默认的进程模型;"true":无论 Extension 配置了什么进程模型,都会新建一个独立进程。
-
-
冲突时谁优先?
如果两个字段都配了,
hostSpecified优先级更高,也就是:有
hostSpecified就按它的进程名跑,忽略hostInstance。
如何选用?
-
需要「强隔离、每个嵌入实例一个独立进程」:
- 用
'ohos.extension.processMode.hostInstance': 'true';
- 用
-
希望「多个嵌入共用一个特定进程」:
- 配
'ohos.extension.processMode.hostSpecified': 'xxx:your_process';
- 配
-
如果两个场景都想用:
- 可以按业务类别拆分多个 EmbeddedUIExtensionAbility,分别配置不同的进程策略。
7. 我的实践向理解小结
我会把这套能力理解成:
“把一个复杂 UI 模块塞进独立进程,然后再嵌进主 UIAbility 里使用” 。
-
适合:
- 大型、复杂、容易出问题的模块(例如渲染引擎、三方文档渲染、编辑器内核);
- 对稳定性要求很高,希望「某个子模块挂掉不拖死整个 App」的场景;
-
不太适合:
- 只做简单展示的小组件(进程开销会显得比较重);
- 对性能极度敏感、切换频繁且体量很小的 UI(频繁起停进程会较重)。