PAYATHON 2026

鸿蒙支付SDK调用getLastWindow获取SubWindow异常

支付老李

结论

window.getLastWindow() 返回当前应用最近使用的窗口,不保证是主窗口。应用创建并显示 SubWindow 后,该接口可能返回子窗口。

因此,@alipay/authbase 调用 getLastWindow 时,存在获取到挂件子窗口的风险。设备未安装支付宝 App 时,支付通常会进入应用内页面等降级链路。如果该链路依赖所获取的窗口展示页面,而实际拿到的是 SubWindow,可能出现页面尺寸、层级、路由或显示状态异常。

不过,API 扫描结果只能说明依赖包中存在这项调用,还不能据此认定支付异常一定是它引起的。需要继续确认:

  • getLastWindow 是否位于未安装支付宝 App 时实际执行的代码路径中;
  • SDK 获取窗口后执行了哪些操作;
  • 异常发生时,该接口是否确实返回了挂件子窗口。

原因分析

getLastWindow 获取的是最近使用的窗口,并非当前 UIAbility 对应的主窗口。挂件子窗口创建并显示后,可能成为“最后窗口”,后续调用便可能返回它。

如果 SDK 使用该窗口执行以下操作,问题可能更明显:

  • 获取 UIContext;
  • 加载支付页面;
  • 创建或挂载弹窗、Web 页面;
  • 获取窗口尺寸;
  • 设置窗口背景、布局或系统栏属性;
  • 判断窗口状态;
  • 将支付 UI 挂载到该窗口对应的页面上下文。

安装支付宝 App 时,支付流程可能直接跳转到支付宝客户端,较少依赖应用内窗口。未安装支付宝 App 时,如果 SDK 改为在当前应用内承载支付页面,窗口选择错误就更容易暴露出来。

建议的排查步骤

1. 确认异常发生时返回的窗口

在调起支付前,分别记录主窗口、最后窗口和子窗口的信息。重点比较窗口名称、类型及尺寸,不要只检查窗口对象是否存在。

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

async function inspectLastWindow(context: Context): Promise<void> {
  try {
    const lastWindow = await window.getLastWindow(context);
    const properties = lastWindow.getWindowProperties();

    console.info(`lastWindow name: ${properties.windowName}`);
    console.info(`lastWindow type: ${properties.windowType}`);
    console.info(
      `lastWindow rect: ${JSON.stringify(properties.windowRect)}`
    );
  } catch (error) {
    console.error(`getLastWindow failed: ${JSON.stringify(error)}`);
  }
}

具体可以读取哪些属性,应以当前 HarmonyOS SDK 中的 WindowProperties 定义为准。如果当前 API 版本没有某个字段,请删除对应日志,不要使用未提供的属性。

建议对比两组场景:

  1. 不创建挂件 SubWindow,直接调起支付;
  2. 创建并显示挂件 SubWindow,然后调起支付。

如果只有第二种场景出现异常,且日志确认 getLastWindow 返回了子窗口,基本可以确定窗口选择与该问题直接相关。

2. 验证 SDK 是否执行了该调用

静态扫描只能证明依赖包中包含 getLastWindow,无法证明发生异常的支付流程确实执行了它。可通过调试日志、断点或临时插桩确认:

  • 调用来自 @alipay/authbase 的哪个模块;
  • 未安装支付宝 App 时是否会进入该分支;
  • 调用发生在支付页面创建之前还是之后;
  • 获取窗口后又调用了哪些窗口或 UI 接口。

依赖包的构建产物还可能经过合并或压缩,因此,最终调用路径不一定与源码目录完全一致。

3. 临时隐藏或销毁子窗口进行验证

可以在调起支付前暂时隐藏挂件子窗口,支付流程结束后再恢复显示。如果隐藏后支付页面恢复正常,说明问题很可能与 SubWindow 有关。

async function startPaymentWithWidgetHidden(): Promise<void> {
  try {
    await widgetSubWindow.hideWindow();

    await startAlipayPayment();
  } finally {
    try {
      await widgetSubWindow.showWindow();
    } catch (error) {
      console.error(`restore widget window failed: ${JSON.stringify(error)}`);
    }
  }
}

widgetSubWindow 和 startAlipayPayment() 只是示意名称,需要替换为业务中的实际对象和支付调用。恢复子窗口的时机也要根据支付接口的完成方式处理。如果支付调用返回的只是“已成功拉起”,而非等待整个支付页面关闭,就不能在该 Promise 完成后立即恢复窗口。

推荐处理方式

方案一:SDK 显式使用主窗口

如果可以修改 SDK,建议不要在需要主窗口的场景中使用 window.getLastWindow()。可以由 UIAbility 的 WindowStage 显式获取主窗口,再将主窗口或对应的 UIContext 传给支付模块。

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

export default class EntryAbility extends UIAbility {
  private mainWindow?: window.Window;

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.getMainWindow()
      .then((mainWindow: window.Window) => {
        this.mainWindow = mainWindow;
        return mainWindow.loadContent('pages/Index');
      })
      .catch((error) => {
        console.error(`get main window failed: ${JSON.stringify(error)}`);
      });
  }

  getMainWindow(): window.Window | undefined {
    return this.mainWindow;
  }
}

更稳妥的设计是让宿主应用在初始化支付 SDK 时传入主窗口、UIContext 或页面承载能力,避免 SDK 根据“最近使用窗口”自行判断。

支付宝 SDK 团队需要结合以下版本,确认当前是否已有相应的初始化参数:

{
  "@alipay/afservicesdk": "1.0.241118203225",
  "@cashier_alipay/cashiersdk": "15.8.23",
  "@alipay/authbase": "1.3.9-250912"
}

在没有官方接口说明的情况下,不应自行假设 SDK 支持某种窗口注入参数。

方案二:支付期间避免让子窗口成为最后使用窗口

如果暂时无法修改 SDK,可以先从业务侧规避:

  • 调起支付前隐藏挂件子窗口;
  • 必要时关闭子窗口,支付结束后再重新创建;
  • 调起支付前避免更新、聚焦或重新显示子窗口;
  • 从主窗口页面执行支付入口;
  • 确保传给支付 SDK 的 Context 来自主窗口所属的 UIAbility。

这类方案适合临时处理,但会依赖窗口操作的时序,稳定性通常不如让 SDK 显式使用主窗口。

方案三:升级或由 SDK 侧提供修复版本

建议向 SDK 团队提供最小复现工程,并附上以下信息:

  • 创建 SubWindow 的代码和调用时序;
  • 支付调用代码;
  • 安装与未安装支付宝 App 时的对比结果;
  • getLastWindow 返回窗口的日志;
  • 主窗口与子窗口的名称、类型和尺寸;
  • 页面异常的截图或录屏;
  • 设备型号、HarmonyOS 版本和 API 版本;
  • 三个支付宝依赖的完整版本及实际解析出的依赖树。

请 SDK 团队重点确认:

  1. @alipay/authbase 中的 getLastWindow 是否用于收银台降级页面;
  2. 当前实现是否默认把返回的窗口当作主窗口;
  3. 是否可以改用 WindowStage.getMainWindow();
  4. 是否可以由宿主传入主窗口或 UIContext;
  5. 是否已有兼容 SubWindow 场景的版本。

注意事项

  • 不建议通过调用一次主窗口 API 来“刷新”窗口顺序。这种方式依赖系统内部行为,无法保证稳定。
  • 不要长期缓存已经销毁的窗口对象。UIAbility 重建或窗口销毁后,应及时清理缓存并重新获取主窗口。
  • 如果应用支持多实例、多窗口或折叠屏场景,不能通过全局单例无条件保存唯一的主窗口,应按对应的 UIAbility 实例进行管理。
  • 不要因为“安装支付宝 App 时正常”就排除 SDK 问题。客户端支付与应用内降级支付可能采用不同的执行路径。
  • 如果隐藏子窗口后仍然异常,应继续检查支付页面使用的 Context、页面路由、窗口尺寸、网络请求和 Web 组件初始化,不要把所有异常都归因于 getLastWindow。

备注:内容仅供参考。