鸿蒙6.0应用开发——ArkWeb和应用的跳转与拉起

【高心星出品】

概述

在使用ArkTS与ArkWeb进行混合开发时,应用内的部分页面使用了前端相关能力进行了开发,结合Web组件进行了页面加载,在这种场景下涉及到从ArkWeb加载的页面向其他页面跳转,以及从ArkWeb页面拉起应用。

ArkWeb页面与ArkTS页面互相跳转

ArkWeb页面跳转ArkWeb页面

开发者在做Hybrid App混合开发时,Web页面的跳转可以直接在前端侧使用HTML提供的a标签来进行跳转,修改href为跳转后的地址即可。

<a href="www.example.com">跳转到其他页面</a>

ArkTS页面跳转ArkWeb页面

在HarmonyOS应用开发中,会有Web页面和ArkTS页面互相之间进行跳转的场景,例如列表页用了ArkTS进行开发,而详情页设计上只有简单的内容展示并没有复杂的逻辑操作,于是使用了Web开发并使用了ArkTS中的Web组件进行了加载,在这种场景下,从列表页跳转到详情页就是从ArkTS页面跳转到Web页面,在这种场景下,开发者只需要在ArkTS页面对应的事件回调函数中使用路由栈提供的跳转功能即可实现。关于Navigation组件的使用开发者可以参考:组件导航(Navigation)

NavDestination() {
  Column() {
    Button($r('app.string.back_to_web_page'))
      .width('100%')
      .height(40)
      .onClick(() => {
        this.navPathStack.pushPath({ name: 'WebPage' });
      })
  }

  // ...
}
.title('ArkTS页面')

代码逻辑走读:

  1. 定义了一个导航目的地组件 NavDestination,用于构建用户界面中的一个特定页面。
  2. NavDestination内部,使用 Column组件来垂直排列子组件。
  3. Column中嵌套了一个 Button组件,按钮的文本通过 $r('app.string.back_to_web_page')获取资源字符串。
  4. 设置按钮的宽度为 100%,高度为 40
  5. 为按钮绑定了一个点击事件 onClick,当按钮被点击时,调用 this.navPathStack.pushPath({ name: 'WebPage' })将名为 ‘WebPage’ 的路径推入导航路径堆栈,实现页面跳转。
  6. 使用注释 // ...表示代码片段中可能存在其他未展示的逻辑。
  7. 使用 .title('ArkTS页面')设置导航目的地的标题为 “ArkTS页面”。

ArkWeb页面跳转ArkTS页面

同样开发者也会有从Web页面跳转到ArkTS页面的场景,例如刚刚的场景中希望返回到ArkTS页面,就是从Web页面跳转回ArkTS页面的场景,在这种场景下,实现步骤如下:

  1. 在HTML页面内使用a标签的href属性自定义跳转链接。

     <a class="function_item" href="arkts://pages/toOriginPage">跳转到ArkTS页面</a>
    

    说明

    开发者可以根据业务场景自行定义href,此处定义的href并不作为a标签跳转后的地址,而是会在ArkTS侧进行跳转拦截,当检测到该链接时执行自定义逻辑。

  2. 然后在Web页面中,需要在

    onLoadIntercept()

    回调函数中进行跳转拦截,获取跳转的url,如果与自定义的跳转链接一致,那么可以使用路由栈进行页面跳转。

    NavDestination() {
      Column() {
        Web({
          src: $rawfile('index.html'),
          controller: this.controller
        })
          .zoomAccess(false)
          .onLoadIntercept((event) => {
            const url: string = event.data.getRequestUrl();
            if (url === 'arkts://pages/toOriginPage') {
              this.navPathStack.pop();
            }
            // ...
          })
      }
    }
    

    代码逻辑走读:

    1. 定义了一个导航目的地组件NavDestination,该组件通常用于导航框架中,表示一个特定的页面或视图。
    2. NavDestination内部,使用Column组件创建了一个垂直布局,用于组织其子组件。
    3. Column内部嵌套了一个Web组件,用于加载和显示HTML内容。
    4. Web组件通过src属性指定了要加载的HTML文件,文件路径通过$rawfile函数获取,这通常意味着文件存储在项目的资源文件夹中。
    5. Web组件的controller属性被设置为this.controller,这可能用于后续对Web视图的控制,如导航或刷新。
    6. 通过调用zoomAccess(false)方法,禁用了Web组件的缩放功能。
    7. 使用onLoadIntercept方法拦截Web组件的加载事件。当Web组件加载页面时,会触发该事件。
    8. 在拦截事件的回调函数中,通过event.data.getRequestUrl()获取当前页面的URL。
    9. 如果URL等于'arkts://pages/toOriginPage',则通过this.navPathStack.pop()从导航路径栈中移除当前页面,实现页面导航回退。
    10. 代码片段中存在一个省略号// ...,表示可能还有其他未展示的逻辑或配置。

ArkWeb页面指定应用跳转

开发者在HarmonyOS应用内使用了Web页面做了部分页面的实现,同时出于推广,或者需要在其他应用内处理一些逻辑等目的,需要拉起其他的指定应用,例如跳转到支付应用进行支付,在这些场景下,就需要用到指定应用跳转的相关知识,首先在实现方案上,指定应用跳转建议使用如下两种方案:

  1. 使用Deep Linking实现应用拉起。
  2. 使用App Linking实现应用拉起。

方案1:使用Deep Linking。

  1. 在目标方配置module.json5文件,保证entities中包含entity.system.browsable、actions中包含ohos.want.action.viewData,最后再配置uris,用户可以自定义scheme,host,port以及path。具体含义可以参考:

    uris标签说明

    {
      "module": {
        // ...
        "abilities": [
          {
            // ...
            "skills": [
              // ...
              {
                "entities": [
                  "entity.system.browsable"
                ],
                "actions": [
                  "ohos.want.action.viewData"
                ],
                "uris": [
                  {
                    "scheme": "appScheme",
                    "host": "www.test.com",
                    "port": "80",
                    "path": "path1"
                  }
                ]
              }
            ]
          }
        ],
        // ...
      }
    }
    
  2. 在调用方的module.json5文件中配置

    querySchemes

    ,标识允许当前应用进行跳转查询的URL schemes。

    "querySchemes": [
      "app1Scheme"
    ],
    
  3. 根据目标方的uris配置拼凑出完整的link地址,拼接方式为:scheme://host:port/path,例如上述配置对应的拉起地址为:appScheme://www.test.com:80/path1。

    const link: string = "appScheme://www.test.com:80/path1";
    
  4. 通过

    canOpenLink()

    接口判断link是否可以打开,如不能打开链接开发者可以自定义响应逻辑,此处直接返回。

    if (!bundleManager.canOpenLink(link)) {
      return true;
    }
    
  5. 配置拉起时的启动参数

    openLinkOptions

    ,在该配置中可以进行参数传递以及配置appLinkingOnly属性。代码参考如下:

    Navigation(this.navPathStack) {
      Column() {
        Web({
          src: $rawfile('index.html'),
          controller: this.controller
        })
          .zoomAccess(false)
          .onLoadIntercept((event) => {
            const url: string = event.data.getRequestUrl();
            if (url === 'third-party://pages/toThirdApp') {
              const link: string = "appScheme://www.test.com:80/path1";
              if (!bundleManager.canOpenLink(link)) {
                return true;
              }
              // Configuration parameter.
              const openLinkOptions: OpenLinkOptions = {
                appLinkingOnly: false,
                parameters: {
                  name: 'test'
                }
              };
              // Open the application using the openLink interface.
              this.context.openLink(link, openLinkOptions).then(() => {
                console.info('open link success.');
              }).catch((err: BusinessError) => {
                console.error(`open link failed. Code is ${err.code}, message is ${err.message}`);
              })
            }
            return url !== 'resource://rawfile/index2.html';
          })
      }
    }
    

    代码逻辑走读:

    1. 导航组件初始化:使用Navigation(this.navPathStack)初始化一个导航组件,this.navPathStack可能是一个用于管理导航路径的栈。
    2. 布局结构:在导航组件内部使用Column()布局,这意味着内容将垂直排列。
    3. Web视图加载:在Column中嵌入一个Web组件,用于加载Web内容。src: $rawfile('index.html')表示从应用的资源文件中加载名为index.html的文件,controller: this.controller可能是用于控制Web视图的控制器。
    4. 禁用缩放:通过.zoomAccess(false)禁用Web视图的缩放功能。
    5. 加载拦截:使用.onLoadIntercept((event) => {...})对Web视图的加载进行拦截。当Web视图尝试加载URL时,会触发该拦截器。
    6. URL检查:在拦截器中,首先获取当前加载的URLconst url: string = event.data.getRequestUrl()
    7. 特定URL处理:检查URL是否为'third-party://pages/toThirdApp'。如果是,则执行以下操作:
      • 构建一个应用scheme的URLconst link: string = "appScheme://www.test.com:80/path1"
      • 使用bundleManager.canOpenLink(link)检查当前应用是否可以打开该链接。如果不能,则直接返回true,表示阻止加载。
      • 配置打开链接的选项const openLinkOptions: OpenLinkOptions = {...}
      • 使用this.context.openLink(link, openLinkOptions)尝试打开链接,并处理成功和失败的情况。
    8. URL过滤:如果URL不是'resource://rawfile/index2.html',则返回true,表示允许加载。否则返回false,表示阻止加载。

方案2:当开发者希望无论应用是否已安装,用户都可以访问到链接对应的内容,当应用安装时优先打开应用去呈现内容;当应用未安装时,则打开浏览器呈现Web版的内容,就可以使用App Linking的方式,App Linking配置可以参考:使用App Linking实现应用间跳转,以下为在ArkWeb页面中使用App Linking进行跳转的操作步骤。

在HTML文件中直接使用a标签的href配置为要跳转应用详情的App Linking地址(需要应用已上架到应用市场),此处以华为应用市场中的华为商城为例。

<a class="function_item" href="https://appgallery.huawei.com/app/detail?id=com.huawei.hmos.vmall">去应用市场下载</a>
<a class="function_item" href="https://www.huawei.com">打开应用</a>

ArkWeb组件内部完成了对App Linking的自动适配,不需要开发者再做拦截处理,执行默认逻辑即可打开应用市场中的华为商城。

  1. ArkWeb内核已完成对App Linking的自动适配,集成了ArkWeb内核的应用,在ArkWeb中访问目标应用的App Linking地址时,系统将在应用已安装时自动拉端,未安装时跳转到对应网页,ArkWeb的宿主应用无需特殊处理。
  2. 如果网页嵌入的拉端链接为App Linking时,需要注意App Linking链接的域名与当前访问网页的域名不能相同,即Host部分不可以完全相同,否则会认为用户希望继续停留在网页中访问,不跳转目标端。因此在该场景下需要跨域处理,建议使用子域名的方式区分App Linking的链接与H5网页中嵌入的链接。
  3. 直接在浏览器地址栏中输入App Linking链接,不会触发自动拉端逻辑,需要在Web页面中嵌入代码逻辑进行实现链接跳转的功能。
  4. 若开发者不希望触发App Linking应用跳转逻辑,而是在应用内以ArkWeb继续浏览,可以在onLoadIntercept()回调中拦截对应的App Linking地址,并手动通过loadUrl()的方式加载网页内容,需注意获取到的链接地址末尾会被自动添加“/”。
  5. 无法在浏览器地址栏输入App Linking或Deep Linking打开应用,只能通过网页内的跳转代码实现(a标签、window.open()、window.location.href等)。
  6. 目前HarmonyOS设备上华为浏览器对上述两种链接地址做了支持。
  7. App Linking跳转失败时,默认配置下将会打开开发者自定义的网页版应用提供给用户进行浏览,通常此时网页内会嵌入按钮实现“在应用市场下载”,目前HarmonyOS设备的跳转逻辑与其他品类设备并不一致,所以需要开发者通过区分UA标识来判断是否为HarmonyOS设备,从而定制HarmonyOS设备上的跳转体验,实现上开发者可以参考通过UserAgent识别HarmonyOS设备

图1 Web页面打开效果图
在这里插入图片描述

因此,Deep Linking适用于需要在已安装的应用之间进行跳转,实现相对简单,但当无应用匹配时用户体验不佳。而App Linking适用于社交分享、广告引流等需要外部链接访问应用的场景,以及对安全性和用户体验要求较高的场景。AppLinking在Deep Linking的基础上增加了域名校验,提高了链接的安全性和可靠性,且无论应用是否安装,用户都能访问内容。

ArkWeb页面指定类型跳转

在某些场景下,系统存在多个同类的应用,希望由用户按个人偏好自行选择在哪个应用中进行处理,例如用户收到了一个地址,而系统内有多个导航软件,希望让用户自行选择偏好的软件进行导航。实现上开发者可以参考:拉起指定类型的应用。以下参考代码以调用方拉起地图导航类应用为例。

  1. 在HTML页面中使用a标签规定拉起指定应用的字符串。

    <a class="function_item" href="arkts://pullSpeciallyApp">拉起指定类型应用</a>
    
  2. 在Web组件中,当匹配到对应的文本时,执行拉起指定类型的操作。

    Navigation(this.navPathStack) {
      Column() {
        Web({
          src: $rawfile('index.html'),
          controller: this.controller
        })
          .zoomAccess(false)
          .onLoadIntercept((event) => {
            const url: string = event.data.getRequestUrl();
            if (url === 'arkts://pullSpeciallyApp') {
              const wantParam: Record<string, Object> = {
                'sceneType': 1,
                'destinationLatitude': 32.060844,
                'destinationLongitude': 118.78315,
                'destinationName': 'xx市xx路xx号',
                'destinationPoiIds': {
                  1: '111111111111',
                  2: '222222222222'
                } as Record<number, string>,
                'originName': 'xx市xx公园',
                'originLatitude': 31.060844,
                'originLongitude': 120.78315,
                'originPoiIds': {
                  1: '333333333333',
                  2: '444444444444'
                } as Record<number, string>,
                'vehicleType': 0
              };
              const abilityStartCallback: common.AbilityStartCallback = {
                onError: (code: number, name: string, message: string) => {
                  hilog.error(0x0000, 'Sample', '%{public}s', 'onError code ' + code + 'name: ' + name + 'message: ' + message);
                },
                onResult: (result:ESObject) => {
                  hilog.error(0x0000, 'Sample', '%{public}s', 'onResult result: ' + JSON.stringify(result));
                }
              };
              this.context.startAbilityByType('navigation', wantParam, abilityStartCallback);
            }
            return url !== 'resource://rawfile/index.html';
          })
      }
    }
    

    代码逻辑走读:

    1. 导航组件初始化:使用Navigation组件初始化导航路径栈this.navPathStack

    2. 布局结构:在Navigation内部使用Column布局组件来组织子组件。

    3. Web视图嵌入:在Column中嵌入一个Web组件,该组件加载本地HTML文件index.html,并使用this.controller控制Web视图。

    4. 缩放权限设置:通过.zoomAccess(false)禁用Web视图的缩放功能。

    5. 加载拦截:

      通过.onLoadIntercept((event) => {…})监听Web视图的加载事件。

      • 获取加载的URL并存储在url变量中。
      • 检查url是否等于arkts://pullSpeciallyApp。
        • 如果是,构造一个导航参数对象wantParam,包含目的地、起点、车辆类型等信息。
        • 定义一个abilityStartCallback回调对象,用于处理导航启动后的错误和结果。
        • 调用this.context.startAbilityByType('navigation', wantParam, abilityStartCallback)启动导航功能。
      • 返回url是否不等于resource://rawfile/index.html,决定是否继续加载其他资源。

ArkWeb页面跳转系统应用页面

从ArkWeb页面拉起系统应用界面,也是一个常见的场景,例如开发者有发布图片的需求,而且图片上传的界面在前端界面已经有了实现并且做了多端适配,现在希望复用原有的界面,但是具体的图片选择的逻辑以及上传的逻辑希望修改成ArkTS侧的实现。实现步骤如下:

  1. 在Web页面内配置跳转的链接地址。此处使用a标签并不意味着上传的HTML元素就是a标签,而是以它为例,理论上开发者可以用任何HTML元素绑定点击事件,通过设置window.location.href属性进行页面跳转。

    <a class="function_item" href="photo://pages/selectPhoto">拉起系统应用</a>
    
  2. 使用系统提供的照片选择Picker进行图片的选择以及后续逻辑的开发。

    Navigation(this.navPathStack) {
      Column() {
        Web({
          src: $rawfile('index.html'),
          controller: this.controller
        })
          .zoomAccess(false)
          .onLoadIntercept((event) => {
            const url: string = event.data.getRequestUrl();
            if (url === 'photo://pages/selectPhoto') {
              const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
              photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // Filter and select the media file type as IMAGE
              photoSelectOptions.maxSelectNumber = 5; // Select the maximum number of media files
              let uris: Array<string> = [];
              const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
              photoViewPicker.select(photoSelectOptions)
                .then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
                  uris = photoSelectResult.photoUris;
                  console.info('photoViewPicker.select to file succeed and uris are:' + uris);
                })
                .catch((err: BusinessError) => {
                  console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
                })
            }
            return url !== 'resource://rawfile/index3.html';
          })
      }
    }
    .hideTitleBar(true)
    .navDestination(this.PageMap)
    

代码逻辑走读:

  1. 导航组件初始化:使用Navigation(this.navPathStack)初始化导航组件,this.navPathStack可能是一个用于管理导航路径的栈。
  2. 布局结构:在Navigation组件内部使用Column布局来组织子组件。
  3. Web视图加载:在Column中嵌套一个Web组件,用于加载本地HTML文件(通过$rawfile('index.html')获取)。this.controller可能是一个控制Web视图的控制器。
  4. 禁用缩放:通过.zoomAccess(false)禁用Web视图的缩放功能。
  5. 加载拦截:使用.onLoadIntercept((event) => {...})对Web视图的加载进行拦截,event.data.getRequestUrl()获取当前请求的URL。
  6. URL判断与照片选择:
    • 如果URL等于'photo://pages/selectPhoto',则创建PhotoSelectOptions对象,设置MIME类型为图片类型,最大选择数量为5,并使用PhotoViewPicker来选择照片。
    • 成功选择照片后,将照片的URI存储在uris数组中,并在控制台输出成功信息。
    • 如果选择失败,则在控制台输出错误信息。
  7. 返回值处理:拦截器返回url !== 'resource://rawfile/index3.html',决定是否阻止加载该URL。
  8. 隐藏标题栏:通过.hideTitleBar(true)隐藏导航栏的标题。
    event) => {…})对Web视图的加载进行拦截,event.data.getRequestUrl()`获取当前请求的URL。
  9. 导航目标设置:使用.navDestination(this.PageMap)设置导航的目标页面。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐