折叠屏、平板、PC 上跑原来的手机 App,状态串了、布局歪了、数据同步了——问题都出在还是单窗口思维。

最开始做应用的时候,默认就是手机单窗口:一个 UIAbility 实例,一个当前页面,一套全局状态。在手机上跑没问题,一切正常。
后来要适配折叠屏和平板,才发现问题来了:分屏之后两个窗口同时在跑,原来写的全局状态直接串了——在左窗口改了数据,右窗口也跟着变了。布局也不对,原来按竖屏尺寸写的固定高度,横屏分屏之后直接变形。
这时候才意识到:多窗口不是"把布局拉宽"那么简单,是整个应用架构都要从单窗口思维转过来。
一、响应式布局和真正的多窗口不是一回事
先把两个概念区分清楚:
| 类型 | 特点 | 本质 |
|---|---|---|
| 响应式布局 | 一个窗口,根据尺寸变布局 | 还是单实例 |
| 应用内分屏 | 一个 App 开两个窗口实例 | 多实例多状态 |
响应式布局只是同一个窗口里,布局从单栏变成双栏。但应用内分屏是真的开了两个 UIAbility 实例,每个实例有自己的生命周期、自己的页面栈、自己的状态。
很多人最开始把多窗口理解成"用 Row 左右分两栏",那就完全错了。Row 双栏还是同一个页面实例,共享同一套状态。但真正的多窗口是两个独立的实例。
二、窗口实例和 UIAbility 的关系
每个窗口对应一个 UIAbility 实例。你开了两个窗口,就是两个 UIAbility 实例,跑在同一个进程里。
| 窗口类型 | UIAbility 实例 | 页面栈 | 状态 |
|---|---|---|---|
| 单窗口 | 1 个 | 1 个 NavPathStack | 全局共享 |
| 应用内分屏 | 2 个 | 2 个 NavPathStack | 各自独立 |
| 多实例 | N 个 | N 个 NavPathStack | 各自独立 |
这就是为什么原来的全局状态会串:所有 UIAbility 实例都引用同一个全局状态对象。左窗口改了数据,右窗口自然也跟着变了。
三、StartOptions 怎么控制窗口创建
打开新窗口的时候,通过 StartOptions 指定窗口模式和参数。
这段代码解决什么问题: 以分屏模式打开新的页面实例。
文件: pages/HomePage.ets
用途: 多窗口启动
接入位置: 点击"在新窗口打开"按钮
import window from '@ohos.window';
import UIAbility from '@ohos.app.ability.UIAbility';
async openSplitWindow() {
const context = getContext(this);
const want = {
bundleName: 'com.example.app',
abilityName: 'EntryAbility',
parameters: { windowMode: window.WindowMode.WINDOW_MODE_SPLIT, page: 'detail' }
};
context.startAbility(want, { windowMode: window.WindowMode.WINDOW_MODE_SPLIT });
}
这里指定了窗口模式是分屏,同时通过 Want 传了要打开的页面参数。新窗口启动之后,会走自己的 onCreate 生命周期,创建自己的页面栈。
四、为什么 NavPathStack 不能全局共享
这是多窗口最容易踩的坑。
原来单窗口的时候,把 NavPathStack 做成全局单例,所有页面都用同一个导航栈。在单窗口下没问题,因为只有一个页面。
但多窗口下,两个 UIAbility 实例都引用同一个全局 NavPathStack,那左窗口 push 了一个页面,右窗口的页面栈也跟着变了。导航完全乱套。
正确的做法是:每个 UIAbility 实例持有自己的 NavPathStack。全局状态里不要放导航栈,要按窗口实例分开管理。

五、全局状态和局部状态怎么隔离
| 状态类型 | 多窗口下的处理 |
|---|---|
| 用户登录态 | 可以全局共享(同一个账号) |
| 页面表单数据 | 必须局部隔离(每个窗口各填各的) |
| 列表选中项 | 必须局部隔离 |
| 缓存的临时数据 | 按窗口实例隔离 |
| 应用配置 | 可以全局共享 |
原则很简单:和"当前哪个窗口"有关的状态,必须局部隔离;和"应用整体"有关的状态,可以全局共享。
六、窗口销毁的时候要释放什么
多窗口下还有个容易忘的点:窗口销毁的时候,对应的监听器、定时器、订阅都要释放。
单窗口的时候应用退出才释放,问题不大。但多窗口下,用户关掉其中一个窗口,另一个窗口还在跑。如果销毁的那个窗口的监听器没释放,它还在后台跑,占着内存,甚至还在更新 UI——但 UI 已经没了。
窗口销毁的时候要检查:
- 窗口变化监听器有没有移除;
- 定时器有没有清掉;
- 事件订阅有没有取消;
- 持有的资源有没有释放。
七、测试的时候不能只测 1:1 分屏
很多人测多窗口就测一下 1:1 分屏,觉得没问题就上线了。但实际场景里分屏比例是可以拖的——1:2、2:1、甚至更小的窗口。
| 测试场景 | 检查点 |
|---|---|
| 1:1 分屏 | 基础布局正常 |
| 1:2 分屏 | 窄窗口下布局不挤 |
| 2:1 分屏 | 宽窗口下布局不松 |
| 悬浮窗 | 小尺寸下内容不溢出 |
| 折叠屏展开/收起 | 状态不丢失 |
尤其是折叠屏展开收起的时候,窗口尺寸会变。这时候要保证页面状态不丢,布局能自适应调整。

这次做多窗口适配最大的体会是:多窗口不是布局问题,是架构问题。原来的单窗口思维——一个全局状态、一个导航栈、一套页面——在多窗口下全都要拆开。哪一层没拆干净,哪一层就出问题:状态串了、导航乱了、资源漏了。想清楚"哪些状态是窗口级的、哪些是应用级的",再动代码。

6228

被折叠的 条评论
为什么被折叠?



