前言

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

隐私保护插件有一个特殊要求:即使出错也不能崩溃。用户打开银行App查看余额,如果因为隐私保护模块的一个异常导致整个App闪退,那比不做保护还糟糕。所以 secure_application 在 OpenHarmony 端采用了大量的防御性编程——每个可能出错的地方都包了 try-catch。

这篇把所有的错误场景和处理策略都梳理一遍。

一、BusinessError 错误类型

1.1 什么是 BusinessError

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

BusinessError 是 OpenHarmony 系统 API 返回的标准错误类型,包含错误码和错误信息:

interface BusinessError {
  code: number;     // 错误码
  message: string;  // 错误描述
}

1.2 Window API 相关错误码

错误码 含义 触发场景
1300001 重复操作 重复调用某些窗口方法
1300002 窗口状态异常 窗口已销毁或不可用
1300003 系统服务异常 窗口管理服务不可用
1300004 权限不足 缺少 PRIVACY_WINDOW 权限
1300005 参数无效 传入了无效的参数

1.3 错误日志格式

Log.e(TAG, "Failed to set window privacy mode: " + JSON.stringify(err));
// 输出示例:
// SecureApplicationPlugin: Failed to set window privacy mode: {"code":1300002,"message":"Window state abnormal"}

📌 用 JSON.stringify 序列化错误对象,可以完整输出错误码和错误信息,方便排查。

二、Window 获取失败的处理

2.1 失败场景

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

2.2 处理策略:延迟重试

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);
}

2.3 降级策略

getMainWindow() 失败
    │
    ├── 记录错误日志
    ├── mainWindow 保持 null
    │
    └── 后续调用 secure() 时
            │
            ├── mainWindow == null → 重新尝试获取
            │       │
            │       ├── 成功 → 设置隐私模式 ✅
            │       └── 失败 → 再次记录日志,静默失败
            │
            └── mainWindow != null → 正常设置

💡 静默失败的合理性:即使原生端的隐私模式设置失败,Dart 层的 SecureGate 模糊遮罩仍然有效。用户切回 App 时看到的是模糊画面,只是截屏防护没生效。

三、setWindowPrivacyMode 调用失败

3.1 失败处理

private applyPrivacyMode(win: window.Window, isPrivacy: boolean): void {
  try {
    win.setWindowPrivacyMode(isPrivacy).then(() => {
      Log.i(TAG, "Window privacy mode set to: " + isPrivacy);
    }).catch((err: BusinessError) => {
      Log.e(TAG, "Failed to set window privacy mode: " + JSON.stringify(err));
    });
  } catch (err) {
    Log.e(TAG, "Exception applying privacy mode: " + JSON.stringify(err));
  }
}

3.2 可能的失败原因

原因 错误码 处理
窗口已销毁 1300002 重新获取窗口
权限不足 1300004 提示用户声明权限
系统服务异常 1300003 延迟重试

3.3 失败后的影响

影响范围 是否受影响 说明
截屏防护 ✅ 失效 用户可以正常截屏
应用切换器 ✅ 失效 切换器中能看到内容
模糊遮罩 ❌ 不受影响 Flutter 层的遮罩正常工作
认证流程 ❌ 不受影响 onNeedUnlock 正常触发
锁定/解锁 ❌ 不受影响 状态管理正常工作

四、生命周期回调注册失败

4.1 失败处理

private registerLifecycleCallback(): void {
  if (this.context == null) {
    return;
  }
  try {
    const applicationContext = this.context.getApplicationContext();
    // ... 注册回调 ...
    Log.i(TAG, "Lifecycle callback registered");
  } catch (err) {
    Log.e(TAG, "Failed to register lifecycle callback: " + JSON.stringify(err));
  }
}

4.2 失败后的影响

影响 说明
应用进入后台时不会触发锁定 但窗口事件监听可能仍然有效
窗口事件作为备份 WINDOW_INACTIVE 仍然能检测到切换
Flutter 层的 WidgetsBindingObserver 仍然能检测到 AppLifecycleState 变化

📌 三重保障:即使生命周期回调注册失败,还有窗口事件监听和 Flutter 层的 WidgetsBindingObserver 作为备份。三层中只要有一层工作,锁定就能触发。

五、try-catch 包裹策略

5.1 所有 try-catch 的位置

// 1. 获取主窗口
private getMainWindow(): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

// 2. 设置隐私模式
private setPrivacyMode(isPrivacy: boolean): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

// 3. 应用隐私模式
private applyPrivacyMode(win, isPrivacy): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

// 4. 注册窗口事件
private registerWindowEventCallback(win): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

// 5. 注销窗口事件
private unregisterWindowEventCallback(): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

// 6. 注册生命周期回调
private registerLifecycleCallback(): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

// 7. 注销生命周期回调
private unregisterLifecycleCallback(): void {
  try { ... } catch (err) { Log.e(TAG, ...); }
}

5.2 统计

统计项 数量
try-catch 块 7个
Promise.catch 4个
空值检查 8个
总防御点 19个

234行代码中有19个防御点,平均每12行就有一个。这个密度对于安全类插件来说是合理的。

5.3 防御性编程原则

  1. 每个系统 API 调用都包 try-catch
  2. 每个 Promise 都有 .catch
  3. 每个可能为 null 的引用都做空值检查
  4. 错误不上抛,只记录日志
  5. 错误不影响后续操作

六、onMethodCall 中的错误处理

6.1 opacity 参数解析

case "opacity":
  try {
    const newOpacity: number = call.argument("opacity");
    if (newOpacity != null && newOpacity != undefined) {
      this.opacity = newOpacity;
    }
  } catch (e) {
    Log.e(TAG, "Failed to parse opacity argument");
  }
  result.success(true);  // 即使解析失败也返回成功
  break;

6.2 secure 方法的可选参数

case "secure":
  this.secured = true;
  try {
    const secureOpacity: number = call.argument("opacity");
    if (secureOpacity != null && secureOpacity != undefined) {
      this.opacity = secureOpacity;
    }
  } catch (e) {
    // 静默忽略,使用默认 opacity
  }
  this.setPrivacyMode(true);
  result.success(true);
  break;

6.3 为什么参数解析失败不影响主逻辑

secure() 调用
    │
    ├── this.secured = true          ← 必须成功
    ├── 解析 opacity                  ← 可选,失败不影响
    ├── this.setPrivacyMode(true)    ← 必须执行
    └── result.success(true)         ← 必须返回

opacity 是可选参数,解析失败时使用默认值(0.2),不影响核心的隐私模式设置。

七、错误恢复策略

7.1 自动恢复

错误 恢复方式 时机
窗口获取失败 下次 secure() 时重试 自动
隐私模式设置失败 下次 secure() 时重试 自动
窗口事件注册失败 依赖生命周期回调 自动降级

7.2 手动恢复

// Dart 层:如果怀疑保护没生效,可以重新调用
controller.open();   // 先关闭
controller.secure(); // 再开启

7.3 错误监控建议

// 可以添加错误计数器
private errorCount: number = 0;

private handleError(operation: string, err: any): void {
  this.errorCount++;
  Log.e(TAG, `[${this.errorCount}] ${operation}: ${JSON.stringify(err)}`);

  // 如果错误过多,通知 Dart 层
  if (this.errorCount > 5 && this.channel != null) {
    this.channel.invokeMethod("error", {
      "message": "Privacy mode may not be working",
      "count": this.errorCount
    });
  }
}

💡 当前实现没有错误计数和上报机制,但在生产环境中建议添加。这样 Dart 层可以在隐私模式不可用时给用户一个提示。

总结

本文全面分析了 secure_application 的错误处理策略:

  1. BusinessError:OpenHarmony 标准错误类型,包含错误码和描述
  2. 19个防御点:7个 try-catch + 4个 Promise.catch + 8个空值检查
  3. 静默失败:错误不上抛,不影响 App 正常运行
  4. 三重保障:窗口事件 + 生命周期回调 + Flutter WidgetsBindingObserver
  5. 自动恢复:窗口获取失败时下次调用自动重试

下一篇我们讲调试与问题排查——如何用日志和工具定位问题。

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


相关资源:

请添加图片描述

Logo

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

更多推荐