前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

secure_application 在 OpenHarmony 上的核心能力——截屏防护和应用切换器内容隐藏——都依赖于 Window 对象。你得先拿到窗口引用,才能调用 setWindowPrivacyMode。但 OpenHarmony 的窗口获取是异步的,这带来了一些需要处理的边界情况。

今天我们把 Window 管理的完整逻辑讲清楚。

一、@kit.ArkUI 中的 window 模块

1.1 模块导入

import { window } from '@kit.ArkUI';

@kit.ArkUI 是 OpenHarmony 的 UI 框架 Kit,其中的 window 模块提供了窗口管理能力。

1.2 window 模块的核心 API

API 类型 说明
window.getLastWindow(context) 异步 获取当前应用的最后一个窗口
Window.setWindowPrivacyMode(isPrivacy) 异步 设置窗口隐私模式
Window.on('windowEvent', callback) 同步 注册窗口事件监听
Window.off('windowEvent') 同步 注销窗口事件监听

1.3 Window 对象的能力

interface Window {
  setWindowPrivacyMode(isPrivacy: boolean): Promise<void>;
  on(type: 'windowEvent', callback: (event: WindowEventType) => void): void;
  off(type: 'windowEvent', callback?: Function): void;
  // ... 其他方法
}

对于 secure_application,我们只用到了上面三个方法。

二、getLastWindow 异步获取主窗口

2.1 基本用法

window.getLastWindow(this.context).then((win: window.Window) => {
  this.mainWindow = win;
  Log.i(TAG, "Got main window successfully");
}).catch((err: BusinessError) => {
  Log.e(TAG, "Failed to get main window: " + JSON.stringify(err));
});

2.2 为什么是异步的

Android 获取窗口是同步的:

// Android:同步获取
val window = activity.window  // 立即返回

OpenHarmony 获取窗口是异步的:

// OpenHarmony:异步获取
window.getLastWindow(context).then((win) => { ... })
平台 获取方式 原因
Android 同步 Window 是 Activity 的直接属性
OpenHarmony 异步 Window 由窗口管理服务管理,需要 IPC 查询

2.3 context 参数

window.getLastWindow(this.context)

这里的 contextcommon.Context 类型,通过 FlutterPluginBinding.getApplicationContext() 获取。它告诉窗口管理服务"我要获取哪个应用的窗口"。

2.4 "最后一个窗口"的含义

getLastWindow 返回的是当前应用最后创建的窗口,通常就是主窗口。在 Flutter 应用中,一般只有一个窗口,所以 getLastWindow 返回的就是 Flutter 渲染内容的那个窗口。

📌 注意:如果应用创建了多个窗口(比如悬浮窗),getLastWindow 可能返回的不是主窗口。但 Flutter 应用通常不会创建额外窗口,所以这个问题不大。

三、完整的窗口获取实现

3.1 getMainWindow 方法

private getMainWindow(): void {
  if (this.context == null) {
    return;
  }
  try {
    window.getLastWindow(this.context).then((win: window.Window) => {
      this.mainWindow = win;
      Log.i(TAG, "Got main window successfully");
      this.registerWindowEventCallback(win);
    }).catch((err: BusinessError) => {
      Log.e(TAG, "Failed to get main window: " + JSON.stringify(err));
    });
  } catch (err) {
    Log.e(TAG, "Exception getting main window: " + JSON.stringify(err));
  }
}

3.2 逐行分析

操作 说明
空值检查 if (this.context == null) 没有上下文就无法获取窗口
try-catch 外层 try-catch 捕获 getLastWindow 调用本身的异常
then 成功回调 保存窗口引用,注册事件监听
catch 失败回调 记录错误日志

3.3 双重错误处理

try {
  window.getLastWindow(this.context).then((win) => {
    // 成功
  }).catch((err: BusinessError) => {
    // Promise 拒绝(异步错误)
    Log.e(TAG, "Failed to get main window: " + JSON.stringify(err));
  });
} catch (err) {
  // 同步异常(比如 context 无效)
  Log.e(TAG, "Exception getting main window: " + JSON.stringify(err));
}

为什么需要两层错误处理?

错误类型 捕获方式 示例
同步异常 try-catch context 类型错误、API 不存在
异步错误 Promise.catch 窗口不存在、权限不足

💡 实际经验:在开发过程中,两种错误都遇到过。同步异常通常是 SDK 版本不匹配导致的,异步错误通常是窗口还没创建好就去获取。

四、窗口获取失败的容错处理

4.1 失败场景

场景 原因 频率
插件初始化太早 窗口还没创建完成 偶尔
context 无效 应用正在退出 罕见
系统资源不足 窗口管理服务异常 极罕见

4.2 延迟重试策略

setPrivacyMode 方法中实现了重试逻辑:

private setPrivacyMode(isPrivacy: boolean): void {
  if (this.mainWindow == null) {
    // 窗口还没获取到,重新尝试获取
    if (this.context != null) {
      try {
        window.getLastWindow(this.context).then((win: window.Window) => {
          this.mainWindow = win;
          this.applyPrivacyMode(win, isPrivacy);
        }).catch((err: BusinessError) => {
          Log.e(TAG, "Failed to get window for privacy mode: " + JSON.stringify(err));
        });
      } catch (err) {
        Log.e(TAG, "Exception setting privacy mode: " + JSON.stringify(err));
      }
    }
    return;
  }
  this.applyPrivacyMode(this.mainWindow, isPrivacy);
}

4.3 重试逻辑的设计

Dart 调用 secure()
    │
    ├── mainWindow != null → 直接设置隐私模式 ✅
    │
    └── mainWindow == null → 重新获取窗口
            │
            ├── 获取成功 → 设置隐私模式 ✅
            └── 获取失败 → 记录日志,静默失败

这种"懒获取"策略的好处是:即使 onAttachedToEngine 时窗口还没准备好,后续调用 secure() 时还有机会获取到。

4.4 静默失败 vs 抛出异常

策略 优点 缺点 适用场景
静默失败 不影响 App 正常运行 可能不知道保护没生效 secure_application 选择的方式
抛出异常 开发者能立即发现问题 可能导致 App 崩溃 安全关键场景

secure_application 选择了静默失败,因为隐私保护是一个"增强功能",即使失败了也不应该影响 App 的正常使用。

五、Window 对象的生命周期

5.1 有效期

onAttachedToEngine → getMainWindow() → mainWindow 有效
    │
    │ ... 插件正常工作 ...
    │
onDetachedFromEngine → mainWindow = null → mainWindow 无效

5.2 窗口引用失效的场景

场景 mainWindow 状态 处理
正常运行 有效 正常使用
热重载 可能失效 onDetachedFromEngine 置空,重新获取
应用退出 失效 onDetachedFromEngine 置空
配置变更 可能失效 需要重新获取

5.3 安全使用窗口引用

// 每次使用前检查
if (this.mainWindow != null) {
  this.mainWindow.setWindowPrivacyMode(true);
}

// 或者用可选链
this.mainWindow?.setWindowPrivacyMode(true);

六、与 Android Activity.window 的对比

6.1 获取方式对比

// Android:同步,通过 Activity 获取
val window = activity.window
window.addFlags(LayoutParams.FLAG_SECURE)
// OpenHarmony:异步,通过 Context 获取
window.getLastWindow(this.context).then((win) => {
  win.setWindowPrivacyMode(true);
});

6.2 操作方式对比

操作 Android OpenHarmony
获取窗口 activity.window(同步) getLastWindow(context)(异步)
开启保护 addFlags(FLAG_SECURE) setWindowPrivacyMode(true)
关闭保护 clearFlags(FLAG_SECURE) setWindowPrivacyMode(false)
返回值 void Promise

6.3 异步带来的复杂性

Android 的同步 API 用起来很简单:

// Android:一行搞定
activity.window.addFlags(LayoutParams.FLAG_SECURE)

OpenHarmony 的异步 API 需要更多的错误处理:

// OpenHarmony:需要处理异步和错误
try {
  await this.mainWindow?.setWindowPrivacyMode(true);
} catch (err) {
  Log.e(TAG, "Failed: " + JSON.stringify(err));
}

📌 虽然异步 API 用起来复杂一些,但它的设计更合理——窗口操作涉及系统服务的 IPC 调用,本质上就是异步的。Android 把它包装成同步 API 反而隐藏了潜在的性能问题。

七、窗口管理的最佳实践

7.1 获取时机

时机 推荐度 原因
onAttachedToEngine ✅ 推荐 尽早获取,后续调用不需要等待
首次 secure() 调用时 ⚠️ 可选 懒加载,但首次调用会有延迟
每次 secure() 调用时 ❌ 不推荐 频繁获取浪费资源

7.2 缓存策略

// 推荐:获取一次,缓存使用
private mainWindow: window.Window | null = null;

private getMainWindow(): void {
  window.getLastWindow(this.context).then((win) => {
    this.mainWindow = win;  // 缓存
  });
}

private setPrivacyMode(isPrivacy: boolean): void {
  if (this.mainWindow != null) {
    this.applyPrivacyMode(this.mainWindow, isPrivacy);  // 使用缓存
  }
}

7.3 释放时机

onDetachedFromEngine(binding: FlutterPluginBinding): void {
  // 先注销事件,再释放引用
  this.unregisterWindowEventCallback();
  this.mainWindow = null;  // 释放窗口引用
}

先注销事件回调,再释放引用。顺序不能反——如果先释放引用,注销事件时就找不到窗口对象了。

总结

本文详细讲解了 secure_application 的 Window 管理:

  1. getLastWindow:异步获取主窗口,需要 ApplicationContext
  2. 双重错误处理:try-catch 捕获同步异常,Promise.catch 捕获异步错误
  3. 懒获取策略:初始化时获取失败,后续调用时重试
  4. 静默失败:窗口获取失败不影响 App 正常运行
  5. 缓存使用:获取一次缓存起来,避免频繁获取

下一篇我们讲 setWindowPrivacyMode——这是 OpenHarmony 隐私保护的核心 API。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

请添加图片描述

OpenHarmony 窗口管理架构

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐