前端 Web 与原生应用端 WebView 通信交互 - HarmonyOS Next

news2025/1/19 23:14:06

基于鸿蒙 HarmonyOS Next 与前端 Vue 通信交互相关小结;
DevEco Studio NEXT Developer Preview2
Vue js

两端相互拟定好协议后,通过前端页面的点击事件,将所需的数据传输给原生移动端组件方法中,处理后将消息回传至前端.

根据官方文档的案例尝试,但没成功 ... 后经过几经尝试后两端握手成功 ... (官方文档略显粗糙,好一番折腾)

一.应用端

基于 import web_webview from '@ohos.web.webview' 并初始化 Web 组件;
调用 javaScriptProxy 方法,定义配置协议、参数和方法相关,具体可参考如下 code 片段;
name: 交互协议
object: 定义交互对象
methodList: 交互对象中所涵盖的方法,支持多个,也可以通过一个对象方法再细化多个子方法
controller: 组件

import web_webview from '@ohos.web.webview'

@Entry
@Component
export struct HomePage {
  controller: web_webview.WebviewController = new web_webview.WebviewController()
  ports: web_webview.WebMessagePort[] = [];
  nativePort: web_webview.WebMessagePort | null = null;
  message: web_webview.WebMessageExt = new web_webview.WebMessageExt();

  // 声明注册对象
  @State WebCallAppMethod: WebCallAppClass = new WebCallAppClass()

  aboutToAppear(): void {
    try {
      // 启用网页调试功能
      web_webview.WebviewController.setWebDebuggingAccess(true);
    } catch (error) {
      let e: business_error.BusinessError = error as business_error.BusinessError;
      console.log(`Error Code: ${e.code}, Message: ${e.message}`);
      this.controller.refresh(); // 页面异常,刷新
    }
  }


  build() {
    Row() {
      Column({ space: 20 }) {
        Web({ src: 'http://192.168.12.108:8080', controller: this.controller })
          .width('100%')
          .height('100%')
          .backgroundColor(Color.White)
          .multiWindowAccess(true)
          .javaScriptAccess(true)
          .geolocationAccess(true)
          .imageAccess(true)
          .onlineImageAccess(true)
          .domStorageAccess(true)
          .fileAccess(true)
          .mediaPlayGestureAccess(true)
          .mixedMode(MixedMode.All)
          .layoutMode(WebLayoutMode.FIT_CONTENT) // 自适应布局
          .verticalScrollBarAccess(true)
          .horizontalScrollBarAccess(false)
          .cacheMode(CacheMode.Default)
          .zoomAccess(false)// 禁止手势缩放
          .onConsole((event) => {
            console.log('[交互] - onConsole')
            LogUtils.info(event?.message.getMessage())
            return false
          })
          .onPageBegin(() => { // 页面加载中
            // this.registerWebJavaScript()
          })
          .onPageEnd(() => { // 页面加载完成
            console.log('[Web] - 页面加载完成')
            // this.registerWebJavaScript()
          })
          .onErrorReceive((event) => { // 异常: 无网络,页面加载错误时
            if (event) {
              console.info('getErrorInfo:' + event.error.getErrorInfo());
              console.info('getErrorCode:' + event.error.getErrorCode());
              console.info('url:' + event.request.getRequestUrl());
              console.info('isMainFrame:' + event.request.isMainFrame());
              console.info('isRedirect:' + event.request.isRedirect());
              console.info('isRequestGesture:' + event.request.isRequestGesture());
              console.info('getRequestHeader_headerKey:' + event.request.getRequestHeader().toString());
              let result = event.request.getRequestHeader();
              console.info('The request header result size is ' + result.length);
              for (let i of result) {
                console.info('The request header key is : ' + i.headerKey + ', value is : ' + i.headerValue);
              }
            }
          })
          .onHttpErrorReceive((event) => { // 异常: 网页加载资源 Http code >= 400 时
            if (event) {
              console.info('url:' + event.request.getRequestUrl());
              console.info('isMainFrame:' + event.request.isMainFrame());
              console.info('isRedirect:' + event.request.isRedirect());
              console.info('isRequestGesture:' + event.request.isRequestGesture());
              console.info('getResponseData:' + event.response.getResponseData());
              console.info('getResponseEncoding:' + event.response.getResponseEncoding());
              console.info('getResponseMimeType:' + event.response.getResponseMimeType());
              console.info('getResponseCode:' + event.response.getResponseCode());
              console.info('getReasonMessage:' + event.response.getReasonMessage());
              let result = event.request.getRequestHeader();
              console.info('The request header result size is ' + result.length);
              for (let i of result) {
                console.info('The request header key is : ' + i.headerKey + ' , value is : ' + i.headerValue);
              }
              let resph = event.response.getResponseHeader();
              console.info('The response header result size is ' + resph.length);
              for (let i of resph) {
                console.info('The response header key is : ' + i.headerKey + ' , value is : ' + i.headerValue);
              }
            }
          })
          .onConfirm((event) => { // 提示框处理相关
            AlertDialog.show({
              title: '温馨提示',
              message: event?.message,
              confirm: {
                value: 'onAlert',
                action: () => {
                  event?.result.handleConfirm()
                }
              },
              cancel: () => {
                event?.result.handleCancel()
              }
            })
            return true;
          })
          .onShowFileSelector((event) => { // 文件上传处理相关
            console.log('MyFileUploader onShowFileSelector invoked');
            const documentSelectOptions = new picker.PhotoSelectOptions();
            let uri: string | null = null;
            const documentViewPicker = new picker.PhotoViewPicker();
            documentViewPicker.select(documentSelectOptions).then((documentSelectResult) => {
              uri = documentSelectResult[0];
              console.info('documentViewPicker.select to file succeed and uri is:' + uri);
              if (event) {
                event.result.handleFileList([uri]);
              }
            }).catch((err: BusinessError) => {
              console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
            })
            return true;
          })
          .javaScriptProxy({ // web call app
            // 对象注入 web
            object: this.WebCallAppMethod,
            name: 'WebCallApp', // AppCallWeb WebCallAppSsss  WebCallApp
            methodList: ['WebCallApp', 'CmdTest', 'CmdOpenUrl'],
            controller: this.controller
          })
      }.width('100%').height('100%')
    }
  }
}

class WebCallAppClass {
  constructor() {
  }

  WebCallApp(value: string): string { // 采用该 Json 对象模式,通过解析对象中的 type 后细化对应不同的子方法
    console.log('[交互] --- WebCallApp - 测试')
    console.log(value)
    console.log(JSON.parse(value))
    return '[交互] --- WebCallApp - 测试 - 回调123123123'
  }

  CmdOpenUrl(): Object {
    console.log('[交互] --- WebCallApp - CmdOpenUrl');
    new Object({
      'command': '111',
    })
    return Object;
  }

  CmdTest(value: string): string {
    console.log('[交互] --- WebCallApp - test');
    console.log(value);
    return '[交互] --- WebCallApp - test';
  }
}

二.前端

前端配置有两种方式,可以通过 index.html 配置 js 后调用,也可以单独另起一个 js 类方法去适配,具体可参考如下 code 按需尝试;

方式一.通过 index.html

在项目根目录的 index.html 文件中添加如下 script 段落后,在业务所需的地方调用即可

// index.html
<script>
  // eruda.init()
  (function () {
    if (!window.applicationCache && typeof(Worker)=='undefined') {
      alert("E001-检测到您的环境不支持HTML5,程序终止运行!");//不支持HTML5
      return;
    }

    var global = window; // create a pointer to the window root
    if (typeof WebCallAppSsss === 'undefined') {
      global.WebCallApp = { // 此处的 WebCallApp 与原生端 javaScriptProxy 中的 name 相互匹配
        // 如下方法对应的是 javaScriptProxy 中 object 对象中的方法
        WebCallApp: (arg) => {},
        CmdOpenUrl: (arg) => {},
        CmdTest: (arg) => {},
      }; // create elf root if not existed

    }

    window.WebCallAppSsss.global = global; // add a pointer to window
    window.AMap.global = global;
  })();
</script>
// 业务所需的点击事件方法中调用
methods : {
    onClickGoBack() {
        let str = WebCallAppSsss.CmdTest('[交互] - 测试'); // 方式一
        // this.webApp.WebCallApp('CmdTest', '111111'); // 方式二
        Toast.success('abc');
    },
}

方式二.通过自定义 js

自定义 webCallApp 类,通过引入类方法调用

// WebCallApp.js
import {
  AppCallBacks,
  AppCommendBackHandlers,
} from './AppMsgHandlers'

export default {
  WebCallApp(command,args,callback,context) {
    /**
     * 交互
     *
     * 协议:command
     * 入参:args
     * 回调:callback
     * 参数:context
     * */
    if (typeof Elf.AppCallWeb != "undefined") {
      if (this.isInHarmonyOS()) { // 鸿蒙 HarmonyOS Next
        console.log('[OpenHarmony] - ' + command);

        let params = {
          args: args, // 入参
          command: command // 交互协议
        };
        console.log('[鸿蒙] - 入参');
        console.log(params);

        if (typeof WebCallApp === 'undefined') {
          Elf.WebCallApp = {
            WebCallApp: (args) => {}
          };
        }
        window.WebCallApp.Elf = Elf;
      } else { // Android & iOS
        context = context || window;//默认为window对象
        args = args || {};
        let  sn = null;
        //IOS调用相机--sn特殊处理
        if (command == "callCamera") {
          sn = "examCamera";
        } else {
          sn = this.getSerialNumber();//请求App统一加水单号
        }
        let params = {
          args: args,
          command: command
        };
        //绑定回调函数
        if (callback) {
          AppCallBacks[sn] = {
            callback: callback,
            context: context
          };
        }
        if (window.webkit && window.webkit.messageHandlers) {
          //IOS
          params.sn = sn;
          window.webkit.messageHandlers["WebCallApp"].postMessage(JSON.stringify(params));
        } else if (Elf.WebCallApp) {
          //Android
          params.sn = sn;
          Elf.WebCallApp(JSON.stringify(params));
        } else {

        }
      }
    }
  },
  isInApp() {
    if (typeof Elf.AppCallWeb != "undefined") {
      return !!((window.webkit && window.webkit.messageHandlers) || typeof Elf.WebCallApp == "function" || typeof Elf.WebCallCef == "function");
    }
  },
  isInIOS() {
    return window.webkit && window.webkit.messageHandlers;
  },
  isInAndroid() {
    if (typeof Elf.AppCallWeb != "undefined") {
      return typeof Elf.WebCallApp == "function";
    }
  },
  isInHarmonyOS() {
    if (navigator.userAgent.toLowerCase().indexOf('openharmony') !== -1) {
      return true;
    } else {
      return false;
    }
  },
  getSerialNumber() {
    var uuid = this.UUID(3,8);
    return new Date().format("yyyyMMddhhmmssS") + uuid;
  },
  UUID(len,radix) {
    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    var uuid = [],
      i;
    radix = radix || chars.length;
    if (len) {
      for (i = 0; i < len; i++) {
        uuid[i] = chars[0 | Math.random() * radix];
      }
    } else {
      var r;
      uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
      uuid[14] = '4';
      for (i = 0; i < 36; i++) {
        if (!uuid[i]) {
          r = 0 | Math.random() * 16;
          uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
        }
      }
    }
    return uuid.join('');
  },
}
/* eslint-disable */
// AppMsgHandlers
import webApp from './index';

/*
 *
 * app对接
 * 移动端种植Elf对象
 * window => Elf
 * 
 */
(function () {
  if (!window.applicationCache&&typeof(Worker)=='undefined') {
    alert("E001-检测到您的环境不支持HTML5,程序终止运行!");//不支持HTML5
    return;
  }
  
  var global = window;//create a pointer to the window root
  if (typeof Elf === 'undefined') {
      global.Elf = {};//create elf root if not existed
  }
  Elf.global = global;//add a pointer to window
})();

var AppCallBacks = {},//动态数据流水列表
    AppCommendBackHandlers = [],//APP后退监听事件列表
    APPCommendBookStateHandlers = [],//下载状态监听事件列表
    AppCommendRefreshHandlers = [],//刷新监听事件列表
    APPCommendAddToBookShelfHandlers = [],//添加到书架监听事件列表
    APPCommendAddToObtainedBookHandlers = [],//添加到已获得图书列表监听
    APPCommendReBackHandlers = [],//监听重新回到页面通知
    AppCommendNetworkHandlers = [],//监听网络链接状态
    AppCommendAppStartingHandlers = [],//监听APP进入后台运行
    AppCommendAppReactivateHandlers = [],//监听APP重新进入前台运行
    AppCommendScreenShotss = [],//监听手机截屏
    AppCommendKeyboardBounceUp = [];//监听移动端软键盘事件

if (typeof Elf != "undefined") {
  Elf.AppCallWeb = (sn,result) => {
    if (result && typeof result == "string") {
      result = decodeURIComponent(result.replace(/\+/g,'%20'));
      try {
        result = JSON.parse(result);//解决空格变成+的问题
      } catch (error) {
        AppCallBacks[sn].callback.call(AppCallBacks[sn].context,result);
        return;
      }
      if (result.sn) {
        AppCallBacks[sn].callback.call(AppCallBacks[sn].context,result.QrCode);
        return;
      }
    }
    if (AppCallBacks[sn]) {
      if (JSON.parse(result.opFlag)) {
        //执行对应回调
        AppCallBacks[sn].callback.call(AppCallBacks[sn].context,(typeof result.serviceResult == "string") ? JSON.parse(result.serviceResult) : result.serviceResult);
      } else {
        //接口调用返回失败信息,统一处理错误消息
        Toast(result.errorMessage ? result.errorMessage : "服务器异常!");
      }
      //调用完成删除对象
      delete AppCallBacks[sn];
    } else if (AppMsgHandlers[sn] && typeof AppMsgHandlers[sn] == "function") {
      //处理消息通知
      AppMsgHandlers[sn].call(window,result);
    }
  };
}

export {
  AppCallBacks,
  AppCommendBackHandlers
}

业务方法调用

// 业务所需的点击事件方法中调用
methods : {
    onClickGoBack() {
        // let str = WebCallAppSsss.CmdTest('[交互] - 测试'); // 方式一
        this.webApp.WebCallApp('CmdTest', '111111'); // 方式二
        Toast.success('abc');
    },
}

以上便是此次分享的全部内容,希望能对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1790187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字智能数字人直播带货软件系统 实现真人形象的1:1克隆 前后端分离 带完整的安装代码包以及搭建教程

系统概述 数字智能数字人直播带货小程序源码系统是一套集人工智能、3D建模、云计算等技术于一体的综合性解决方案。该系统通过深度学习算法&#xff0c;能够实现对真人形象的精准捕捉和1:1克隆&#xff0c;使数字人在直播过程中呈现出与真人无异的表现力。同时&#xff0c;系统…

YOLO-Worldv2两分钟快速部署

本次部署使用的框架基于ultralytics&#xff0c; 并且已经集成最新版本的YOLOv8框架&#xff1a; 一键环境配置 pip install ultralytics基础使用 训练 from ultralytics import YOLOWorld model YOLOWorld(yolov8x-worldv2.pt) results model.train(datacoco8.yaml, epo…

第三方软件测试报告模版分享

第三方软件测试报告是由独立的第三方机构进行的软件测试后所生成的详细报告。它包含了软件测试的各个方面&#xff0c;包括功能测试、性能测试、安全测试等。通过第三方的客观评估&#xff0c;该报告能够全面、准确地反映出软件的优点与缺陷&#xff0c;为软件开发商和用户提供…

数据动态变化时实现多选及回显

<template><el-dialog title"设置权限" :visible.sync"showDialog" :close-on-click-modal"false" :append-to-body"true" width"800px"><div v-loading"loading"><el-radio-group v-model&…

IntelliJ IDEA智能编程插件AI Assistant

IntelliJ IDEA集成开发工具最新版本提供了人工智能AI编程助手的插件&#xff0c;AI Assistant使用手册的文档地址是AI Assistant | IntelliJ IDEA Documentation AI Assistant提供以下的编程能力以及工具特性&#xff1a; 与AI Assistant聊天&#xff0c;提问与项目相关或者与…

快递单信息抽取【三】--五条标注数据提高准确率,仅需五条标注样本,快速完成快递单信息任务

五条标注数据搞定快递单信息抽取 本项目将演示如何通过五条标注样本进行模型微调&#xff0c;快速且准确抽取快递单中的姓名、电话、省、市、区、详细地址等内容&#xff0c;形成结构化信息。辅助物流行业从业者进行有效信息的提取&#xff0c;从而降低客户填单的成本。 1. 任…

美国年轻人热衷床上“摆烂”,沃尔玛发掘床上用品新商机!

美国年轻人近年来热衷于床上“摆烂”生活方式&#xff0c;这反映了他们对舒适放松的追求和现代生活的压力。沃尔玛作为零售业巨头&#xff0c;敏锐地捕捉到这一市场变化&#xff0c;发现了床上用品的新商机。 美国年轻人忙碌中渴望宁静空间。床成为他们放松、逃离现实压力的理想…

【C++】———list容器

前言 1.list容器简单来说其实就是之前的链表结构。 2.这里的list用的是双向带头结点的循环链表。 目录 前言 一 构造函数 1.1 list (); 1.2 list (size_type n, const value_type& val value_type() ); 1.3 list (InputIterator first, InputIterator last…

和平饭店短视频:成都科成博通文化传媒公司

和平饭店短视频&#xff1a;历史的沉淀与现代的演绎 随着短视频平台的兴起&#xff0c;越来越多的人开始通过镜头记录生活、分享故事。在众多短视频中&#xff0c;以“和平饭店”为主题的短视频独树一帜&#xff0c;以其独特的魅力吸引了大量观众的目光。成都科成博通文化传媒…

【AndroidStudio旧版本BUG问题】完美解决运行报错问题Invalid keystore format

由于之前安装的版本导致AndroidStudio 运行报错&#xff1a;Invalid keystore format 在如下截图的路径中删了debug.keystore重新打开Android Studio运行一下就好了&#xff01;&#xff01;&#xff01; 下面介绍各个模块功能&#xff1a; adbkey 是 Android Debug Bridge (AD…

Deap因子挖掘:比gplearn强100倍(代码+数据)

原创文章第550篇&#xff0c;专注“AI量化投资、世界运行的规律、个人成长与财富自由"。 续前面两篇&#xff0c;继续使用Deap做因子挖掘——与咱们的Quantlab因子表达式引擎直接关联起来了&#xff1a; 1、生成的因子&#xff0c;在训练集和测试集上计算ic值。 def ma…

SQL进阶day9————聚合与分组

目录 1聚合函数 1.1SQL类别高难度试卷得分的截断平均值 1.2统计作答次数 1.3 得分不小于平均分的最低分 2 分组查询 2.1平均活跃天数和月活人数 2.2 月总刷题数和日均刷题数 2.3未完成试卷数大于1的有效用户 1聚合函数 1.1SQL类别高难度试卷得分的截断平均值 我的错误…

云动态摘要 2024-06-05

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 [1元/年起]618大促-对象存储分会场 腾讯云 2024-06-03 对象存储限时破价秒杀&#xff0c;标准存储新老同享历史低价&#xff0c;新客首单低至1元&#xff0c;爆款资源包低于2折购 云服务器…

安全测试用例及解析(Word原件,直接套用检测)

5 信息安全性测试用例 5.1 安全功能测试 5.1.1 标识和鉴别 5.1.2 访问控制 5.1.3 安全审计 5.1.4 数据完整性 5.1.5 数据保密性 5.1.6 软件容错 5.1.7 会话管理 5.1.8 安全漏洞 5.1.9 外部接口 5.1.10 抗抵赖 5.1.11 资源控制 5.2 应用安全漏洞扫描 5.2.1 应用安全漏洞扫描 5.3…

王道408数据结构CH2_线性表

概述 2 线性表 2.1 基本操作 2.2 顺序表示 线性表的元素从1开始&#xff0c;数组元素下标从0开始 2.2.1 结构体定义 #define Maxsize 50typedef struct{ElemType data[Maxsize];int length; }SqList;#define Initsize 100typedef struct{ElemType *data;int Maxsize ,length;…

基于EasyX的贪吃蛇小游戏 - C语言

游戏基本功能演示&#xff1a; 1.主菜单界面 2.自定难度界面 在这里可以自行设定游戏的难度&#xff0c;包括蛇的移动速度&#xff0c;初始节数&#xff0c;以及默认模式&#xff0c;参考线&#xff08;网格&#xff09;。这些设定的数据都会在右上角的游戏属性栏中实时显示。…

十、结果处理器

这一章和上一章参数处理器类似 首先是在XML解析的时候&#xff0c;顺便解析resultMap和resultType&#xff0c;一般更多的可能用的是resultType&#xff0c;为了实现统一&#xff0c;使用 resultType 的情况下&#xff0c;Mybatis也会创建一个resultMap实体类映射。 使用的时…

云服务器安装宝塔Linux面板全流程,新手教程!

云服务器如何宝塔Linux面板&#xff1f;阿小云以阿里云服务器为例安装宝塔Linux面板全流程&#xff0c;非常简单&#xff1a; 使用阿里云服务器安装宝塔面板教程&#xff0c;阿里云服务器网以CentOS操作系统为例&#xff0c;安装宝塔Linux面板&#xff0c;先远程连接到云服务器…

化栈为队00

题目链接 化栈为队 题目描述 注意点 只能使用标准的栈操作假设所有操作都是有效的 解答思路 使用两个栈模拟队列&#xff0c;第一个栈stk1是按正常栈顺序存储元素&#xff0c;第一个栈stk2是按队列顺序存储元素&#xff0c;初始入栈都是将元素添加到stk1中&#xff0c;当需…

【漯河市人才交流中心_登录安全分析报告-Ajax泄漏滑动距离导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…