HarmonyOS 应用开发进阶案例(四): 基于应用拉起相关能力实现Web跳转

28 阅读3分钟

本示例基于应用拉起相关能力,实现了Web页面与ArkTS页面的相互拉起、从Web页面拉起系统应用等场景。

1. 案例效果截图

2. 案例运用到的知识点

2.1. 核心知识点

  • Web组件:onLoadIntercept拦截器拦截页面加载。
  • Navigation:组件导航。
  • UIAbility:为对应系统应用配置Want参数,使用startAbility进行拉起。
  • 环境变量获取:调用Ability接口直接获取系统环境变量

2.2. 其他知识点

  • ArkTS 语言基础
  • V2版状态管理:@ComponentV2/@Provider/@Consumer
  • 自定义组件和组件生命周期
  • 内置组件:Column/Button
  • 日志管理类的编写
  • 常量与资源分类的访问
  • MVVM模式

3. 代码结构

├──entry/src/main/ets                                   // 代码区
│  ├──common
│  |  ├──Logger.ets                                     // 日志工具类
│  |  └──Constants.ets                                  // 常量
│  ├──entryability
│  |  └──EntryAbility.ets
│  ├──entrybackupability
│  |  └──EntryBackupAbility.ets
│  └──pages
│     ├──Index.ets                                      // 入口界面
│     └──OriginPage.ets                                 // 原生页面
└──entry/src/main/resources                             // 应用资源目录

4. 公共文件与资源

本案例涉及到的常量类和工具类代码如下:

4.1. 通用常量类

// entry/src/main/ets/common/utils/CommonConstants.ets
export class Constants {
  static readonly ORIGIN_PAGE: string = 'OriginPage'
  static readonly FULL_SCREEN: string = '100%'
  static readonly WEB_PAGE: string = 'WebPage'
  static readonly ORIGIN_PAGE_CHINESE: string = '原生页面'
  static readonly WEB_PAGE_CHINESE: string = 'Web页面'
  static readonly ORIGIN_WEB_PAGE_LINK: string = 'resource://rawfile/index.html'
  static readonly WEB_PAGE_ADDRESS: string = 'resource://rawfile/index1.html'
  static readonly SYSTEM_LANGUAGE_KEY: string = 'systemLanguage'
  static readonly CHINESE_LANGUAGE: string = 'zh-Hans-CN'
  static readonly ENGLISH_LANGUAGE: string = 'en-Latn-CN'
}

4.2. 日志类

// main/ets/common/utils/Logger.ets
import { hilog } from '@kit.PerformanceAnalysisKit'

export class Logger {
  private static domain: number = 0x0000
  private static prefix: string = 'WebPullOtherApplication'
  private static format: string = '%{public}s, %{public}s'

  static debug(...args: string[]): void {
    hilog.debug(Logger.domain, Logger.prefix, Logger.format, args)
  }

  static info(...args: string[]): void {
    hilog.info(Logger.domain, Logger.prefix, Logger.format, args)
  }

  static warn(...args: string[]): void {
    hilog.warn(Logger.domain, Logger.prefix, Logger.format, args)
  }

  static error(...args: string[]): void {
    hilog.error(Logger.domain, Logger.prefix, Logger.format, args)
  }
}

本案例涉及到的资源文件如下:

  1. string.json
// main/resources/base/element/string.json
{
  "string": [
    {
      "name": "module_desc",
      "value": "模块描述"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "Web应用拉起"
    },
    {
      "name": "back_to_web_page",
      "value": "跳转回Web页面"
    },
    {
      "name": "original_page",
      "value": "原生页面"
    }
  ]
}

2. float.json

// main/resources/base/element/float.json
{
  "float": [
    {
      "name": "page_text_font_size",
      "value": "50fp"
    },
    {
      "name": "space_24",
      "value": "24vp"
    },
    {
      "name": "space_bottom",
      "value": "16vp"
    },
    {
      "name": "button_height",
      "value": "40vp"
    }
  ]
}

其他资源请到源码中获取。

5. 应用侧首页面

// entry/src/main/ets/pages/Index.ets
import { webview } from '@kit.ArkWeb'
import { common, Want } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { OriginPage } from './OriginPage'
import { Constants } from '../common/Constants'
import { Logger } from '../common/Logger'
import { env } from '../common/Env'

// 正则表达式用于检测资源URL前缀
const regex = /^resource:///

@Entry
@ComponentV2
struct Index {
  // 导航路径堆栈,管理页面导航
  @Provider('navPathStack') navPathStack: NavPathStack = new NavPathStack()

  // WebView 控制器
  private controller: WebviewController = new webview.WebviewController()

  // UIAbility 上下文对象
  private context = getContext(this) as common.UIAbilityContext

  // 用于存储不同 URL 对应的处理函数
  private functionsMap: Map<string, () => void> = new Map()

  // 存储当前应用的包名
  private bundleName: string = ''
  
  aboutToAppear(): void {
    this.bundleName = this.context.abilityInfo.bundleName  // 获取当前应用包名
    this.initFunctionsMap()  // 初始化 URL 处理函数映射
  }

  // 处理返回按键事件
  onBackPress(): boolean | void {
    if (this.controller.accessBackward()) { // 如果 WebView 可以后退
      this.controller.backward() // 让 WebView 执行后退操作
      return true
    }
  }

  // 页面映射方法,用于导航到不同页面
  @Builder
  PageMap(name: string) {
    if (name === Constants.ORIGIN_PAGE) {
      OriginPage() // 跳转到 OriginPage 页面
    }
  }

  // 初始化 URL 处理函数映射
  initFunctionsMap() {
    // 处理跳转到 OriginPage 的请求
    this.functionsMap.set('arkts://pages/toOriginPage',
      () => this.navPathStack.pushPath({ name: Constants.ORIGIN_PAGE }))

    // 处理跳转到系统 WiFi 设置页的请求
    this.functionsMap.set('network://pages/toSystemApp', () => {
      const want: Want = {
        bundleName: 'com.huawei.hmos.settings',
        abilityName: 'com.huawei.hmos.settings.MainAbility',
        uri: 'wifi_entry',
      }

      // 启动系统 WiFi 设置界面
      this.context.startAbility(want).then(() => {
        Logger.info(`Successfully started Ability.`)
      }).catch((err: BusinessError) => {
        Logger.error(`Failed to start Ability. Code: ${err.code},`)
        Logger.error(`Message: ${err.message}`)
      })
    })
  }

  // 组件构建 UI
  build() {
    Navigation(this.navPathStack) {
      Column() {
        Web({
          src: $rawfile(env.language == Constants.ENGLISH_LANGUAGE
            ? 'index_en.html' : 'index_cn.html'), // 根据语言加载不同 HTML 文件
          controller: this.controller
        })
          .zoomAccess(false) // 禁用缩放
          .onLoadIntercept((event) => { // 拦截页面加载事件
            const url: string = event.data.getRequestUrl() // 获取请求的 URL
            
            // 查找对应的处理函数
            const callFunc = this.functionsMap.get(url) as () => void
            
            callFunc && callFunc() // 如果存在处理函数,则执行
            return !regex.test(url) // 如果 URL 不是资源路径,则拦截
          })
      }
    }
    .hideTitleBar(true) // 隐藏标题栏
    .navDestination(this.PageMap) // 设置页面导航映射
    .mode(NavigationMode.Stack) // 使用堆栈导航模式
  }
}

关键代码说明:

  • Web组件的onLoadIntercept方法,执行url拦截工作,如果不是以resource://开头的URL地址,则拦截。
  • functionsMap对象里,根据不同的请求源,注册相应的功能函数。
  • navPathStack.pushPath实现路由跳转,context.startAbility实现Ability跳转。
  • env.language根据环境变量的系统语言的值,来载入不同的前端页面。

6. 环境变量

6.1. 定义环境变量类

// entry/src/main/ets/common/utils/Env.ets
import { ConfigurationConstant } from '@kit.AbilityKit'

export class Env {
  language: string | undefined
  colorMode: ConfigurationConstant.ColorMode | undefined
  fontSizeScale: number | undefined
  fontWeightScale: number | undefined
}

export let env: Env = new Env()

6.2. 在EntryAbility里获取并设置环境变量

// entry/src/main/ets/entryability/EntryAbility.ets
// ...
import { env } from '../common/Env'

// ...

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ...
    env.language = this.context.config.language
    env.colorMode = this.context.config.colorMode
    env.fontSizeScale = this.context.config.fontSizeScale
    env.fontWeightScale = this.context.config.fontWeightScale
  }

  // ...
}

7. web前端页面

7.1. index_cn

<!-- entry/src/main/resource/rawfile/index_cn.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Document</title>
    <link rel="stylesheet" href="./css/styles.css">
  </head>

  <body>
    <div class="web_page_demo">
      <div class="title">Web和应用的跳转与拉起</div>
      <ul>
        <li>
          <a class="function_item" href="arkts://pages/toOriginPage">
            跳转到原生页面
          </a>
        </li>
        <li>
          <a class="function_item" href="./index1_cn.html">
            跳转到Web页面
          </a>
        </li>
        <li>
          <a class="function_item" href="network://pages/toSystemApp">
            拉起系统应用
          </a>
        </li>
      </ul>
    </div>
  </body>
</html>
<script></script>

7.2. index1_cn

<!-- entry/src/main/resource/rawfile/index1_cn.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Document</title>
    <link rel="stylesheet" href="./css/styles1.css">
</head>
<body>
<div class="web_page_demo">
    <div class="header">
        <div class="back">
            <img onclick="backup()" src="./statics/ic_back.png" alt=""/>
        </div>

        <span>Web 页面</span>
    </div>
    <div class="title">Hello Web</div>
    <a class="function_item" href="javascript:void(0);" onclick="backup()"
    >跳转回Web页面</a
    >
</div>
</body>
</html>
<script>
    function backup(event) {
      window.history.back()
    }
</script>

其他前端资源请到源码中获取。

8. 应用侧原始页面

// main/ets/pages/OriginPage.ets
import { Constants } from '../common/Constants'
import { env } from '../common/Env'

@ComponentV2
export struct OriginPage {
  @Consumer('navPathStack') navPathStack: NavPathStack = new NavPathStack()

  build() {
    NavDestination() {
      Column() {
        Button($r('app.string.back_to_web_page'))
          .width(Constants.FULL_SCREEN)
          .height($r('app.float.button_height'))
          .onClick(() => {
            this.navPathStack.pop()
          })
      }
      .width(Constants.FULL_SCREEN)
      .height(Constants.FULL_SCREEN)
      .justifyContent(FlexAlign.End)
      .padding({
        left: $r('app.float.space_24'),
        right: $r('app.float.space_24'),
        bottom: $r('app.float.space_bottom')
      })
    }
    .title(env.language == Constants.ENGLISH_LANGUAGE 
           ? Constants.ORIGIN_PAGE : Constants.ORIGIN_PAGE_CHINESE)
  }
}

关键代码说明:

  • @Consumer('navPathStack')通过消费父组件的navPathStack,获得路由栈对象。
  • env.language == Constants.ENGLISH_LANGUAGE根据系统语言显示title。

9. 结语

✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接