文档管理中心
您当前正在浏览新版开发者文档中心,目录分类和层级有所调整。点击左侧当前文档分类名称前的“☰”图标,可切换文档分类。 了解新版目录
问题处理与运维开发与测试问题应用框架开发Web框架Web开发(ArkWeb)Web组件如何解决切换可见状态出现残留帧

Web组件如何解决切换可见状态出现残留帧

问题现象

当复用同一个WebviewController,Web组件切换到不可见状态时,通过loadUrl加载新的网页后再切换到可见状态,会出现上一个页面的残留帧。

问题效果预览:

效果预览

背景知识

  • onInactive:调用此接口通知Web组件进入未激活状态。开发者可以在此回调中实现应用失去焦点时应表现的恰当行为。
  • onActive:调用此接口通知Web组件进入前台激活状态。
  • WebviewController:通过WebviewController可以控制Web组件各种行为(包括页面导航、生命周期状态、JavaScript交互等行为)。

解决方案

该问题主要由于Web组件在不可见到可见状态切换时,存在之前Web页面的残留帧导致的。可参考以下方案解决:

  • 当Web组件的页面视图切走后,Web组件会被系统切换为onInactive状态,所以当Web组件不可见时,在调用loadUrl方法加载新页面前需要将Web组件设为onActive状态。
  • 复用同一个WebviewController,当Web组件从不可见状态切换到可见状态时,会出现之前Web页面的残留帧,所以需要在Web组件切换至不可见后并在加载新页面前,使用loadUrl方法加载空URL(加载空URL前也需将Web组件置为onActive状态),这会使Web组件加载一个空白的页面,将之前页面的残留帧替换为该空白页面。加载空URL的时机,如可在Web组件由可见至不可见时就加载空URL,若存在用户切换操作,也可在用户退出时加载空URL。
  • Web组件在后台时若一直保持onActive状态,可能会对功耗有影响。所以当存在使用loadUrl加载页面后但Web组件长时间未切换为可见状态的场景,需要在页面加载完成后将Web组件状态设为onInactive状态。

Web组件所在页面代码逻辑示例:

收起
自动换行
深色代码主题
复制
  1. import { webview } from '@kit.ArkWeb';
  2. import { emitter } from '@kit.BasicServicesKit';
  3. import { PromptAction } from '@kit.ArkUI';
  4. @Entry
  5. @Component
  6. struct WebPages {
  7. @State selectedIndex: number = 0;
  8. @State currentIndex: number = 0;
  9. private controller: TabsController = new TabsController();
  10. @Builder
  11. tabBuilder(title: string, targetIndex: number) {
  12. Column() {
  13. Text(title).fontColor(this.selectedIndex === targetIndex ? '#0a59f7' : '#6B6B6B');
  14. }.width('100%')
  15. .height(50)
  16. .justifyContent(FlexAlign.Center);
  17. }
  18. build() {
  19. Column() {
  20. Tabs({ barPosition: BarPosition.End, index: this.currentIndex, controller: this.controller }) {
  21. TabContent() {
  22. TabOne();
  23. }.tabBar(this.tabBuilder('Tab', 0));
  24. TabContent() {
  25. TabTwo();
  26. }.tabBar(this.tabBuilder('Tab', 1));
  27. }
  28. .vertical(false)
  29. .barMode(BarMode.Fixed)
  30. .barWidth(360)
  31. .barHeight(60)
  32. .animationDuration(0)
  33. .onChange((index: number) => {
  34. this.currentIndex = index;
  35. this.selectedIndex = index;
  36. })
  37. .width('100%')
  38. .height('100%');
  39. };
  40. }
  41. }
  42. @Component
  43. export struct TabOne {
  44. @State webviewController: webview.WebviewController = new webview.WebviewController();
  45. // 用户标识
  46. @StorageProp('userInfo') userInfo: string = '';
  47. // 监听用户登录及退出操作
  48. @StorageProp('isLogged') @Watch('isLoggedChange') isLogged: boolean = false;
  49. loadNewUrl: () => void = () => {
  50. // 通过loadUrl加载网页前,将Web组件设置为onActive状态
  51. this.webviewController.onActive();
  52. // 此处地址实际使用过程中替换为三个不同的真实地址
  53. if (this.userInfo === '1') {
  54. this.webviewController.loadUrl('xx.xx.xx');
  55. } else if (this.userInfo === '2') {
  56. this.webviewController.loadUrl('xx.xx.xx');
  57. } else {
  58. this.webviewController.loadUrl('xx.xx.xx');
  59. }
  60. };
  61. aboutToAppear(): void {
  62. // 订阅网页加载事件,其它Tab页面会通知加载网页
  63. emitter.on('loadUrl', this.loadNewUrl);
  64. }
  65. isLoggedChange() {
  66. if (!this.isLogged) {
  67. // 当用户退出时,将Web组件设置为onActive状态后加载空URL
  68. this.webviewController.onActive();
  69. this.webviewController.loadUrl('about:blank');
  70. }
  71. }
  72. aboutToDisappear(): void {
  73. emitter.off('loadUrl');
  74. }
  75. getSrc() {
  76. // 此处地址实际使用过程中替换为三个不同的真实地址
  77. let src = 'xx.xx.xx';
  78. if (this.userInfo === '1') {
  79. src = 'xx.xx.xx';
  80. } else if (this.userInfo === '2') {
  81. src = 'xx.xx.xx';
  82. }
  83. return src;
  84. }
  85. build() {
  86. Web({
  87. src: this.getSrc(),
  88. controller: this.webviewController
  89. })
  90. .onPageEnd((event) => {
  91. // Web组件在后台时若一直保持onActive状态,可能会对功耗有影响。此处判断加载的URL,当加载空URL完成时将Web组件设置回onInactive状态
  92. if (event.url == 'about:blank') {
  93. this.webviewController.onInactive();
  94. }
  95. })
  96. .geolocationAccess(false)
  97. .fileAccess(true)
  98. .domStorageAccess(true);
  99. }
  100. }
  101. @Component
  102. export struct TabTwo {
  103. @StorageProp('userInfo') userInfo: string = '';
  104. @StorageProp('isLogged') isLogged: boolean = false;
  105. promptAction: PromptAction = this.getUIContext().getPromptAction();
  106. build() {
  107. Column({ space: 20 }) {
  108. Text(`当前登录状态:${this.isLogged ? '已登录' : '未登录'}`);
  109. Text(`当前用户:${this.userInfo}`);
  110. // 模拟用户登录退出操作
  111. Button('登录用户1 网页1')
  112. .onClick(() => {
  113. AppStorage.setOrCreate('isLogged', true);
  114. AppStorage.setOrCreate('userInfo', '1');
  115. emitter.emit('loadUrl');
  116. this.promptAction.showToast({
  117. message: '用户1 登录成功'
  118. });
  119. });
  120. Button('登录用户2 网页2')
  121. .onClick(() => {
  122. AppStorage.setOrCreate('isLogged', true);
  123. AppStorage.setOrCreate('userInfo', '2');
  124. emitter.emit('loadUrl');
  125. this.promptAction.showToast({
  126. message: '用户2 登录成功'
  127. });
  128. });
  129. Button('退出登录')
  130. .onClick(() => {
  131. AppStorage.setOrCreate('isLogged', false);
  132. AppStorage.setOrCreate('userInfo', '');
  133. this.promptAction.showToast({
  134. message: '退出登录成功'
  135. });
  136. });
  137. };
  138. }
  139. }
说明

访问在线网页时需添加网络权限:ohos.permission.INTERNET,具体申请方式请参考声明权限。