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

基于鸿蒙 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');
    },
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

github删除某个仓库

在GitHub上删除一个仓库&#xff0c;按照以下步骤操作&#xff1a; 1、登录到GitHub账户。 2、导航到想要删除的仓库页面。 3、点击仓库页面上的"Settings"&#xff08;设置&#xff09;选项卡。 4、向下滚动到页面底部&#xff0c;找到"Danger Zone"&…

【Linux多线程】LWP和pthread_t

文章目录 LWPclone系统调用查看线程LWP理解LWP与TID pthread_id LWP LWP是Linux中线程的具体实现形式&#xff0c;在linux中&#xff0c;进程和线程本质上都是相同的&#xff0c;都是通过task_struct结构体来表示的。LWP是内核级线程&#xff0c;TID是其唯一标识符&#xff0c…

Day43 代码随想录打卡|二叉树篇---左叶子之和

题目&#xff08;leecode T404&#xff09;&#xff1a; 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 方法&#xff1a; 迭代法&#xff1a;计算所有的左叶子节点&#xff0c;那我们就必然要找到所有的左叶子节点。那么怎么找呢&#xff1f;如何针对cur->l…

电路分析答疑 1

三要素法求解的时候&#xff0c; 电容先求U&#xff0c;再利用求导求I 电感先求I&#xff0c;再利用求导求U 若I的头上没有点点&#xff0c;那就是求有效值 叠加定理&#xff0c;不要忘记 若电流值或者电压值已经给出来了&#xff0c;那就说明这一定是直流电。 在画画圈的时候…

【Qt】Qt Style Sheets (QSS) 指南:打造个性化用户界面

文章目录 前言&#xff1a;1. QSS 选择器2. 子控件选择器&#xff08;Sub-Controls&#xff09;2.1. 示例&#xff1a;给 QComboBox 给下拉按钮加上图标2.2. 示例&#xff1a;修改进度条颜色 3. 伪类选择器3.1. 代码示例: 设置按钮的伪类样式.3.2. 代码示例: 使用事件方式实现同…

超级羊毛!Splashtop SOS 远程软件免费用!

​作为新时代的 IT 青年&#xff0c;您是否经常被亲朋好友、同事、客户们请求帮助解决各种电脑、手机问题&#xff1f;您是否需要一个免费好用的远程支持软件&#xff1f; 机会来了&#xff0c;我们现在免费送您一年专业商业远程支持软件——Splashtop SOS&#xff0c;价值990…

计算病理学的视觉语言基础模型| 文献速递-视觉通用模型与疾病诊断

Title 题目 A visual-language foundation model for computational pathology 计算病理学的视觉语言基础模型 01 文献速递介绍 数字病理学的快速普及和深度学习的进步使得可以开发出针对各种疾病和患者群体的各种病理学任务的强大模型。然而&#xff0c;由于医学领域标签…

windows下 Qt 操作xlsx 和 csv

需求: 工作中遇到一个需求,有两张表格,一个xlsx表,一个csv表格,格式如下: 以csv表格中船台标识为基础,读取xlsx中的数据,如果存在该MMSI则把船名写道csv中对应船名的后面,不存在的话,则添加进csv中,合并两个表格。由于表格数据非常多,有十几万个,所以只能通过程序…

ClickHouse内幕(1)数据存储与过滤机制

本文主要讲述ClickHouse中的数据存储结构&#xff0c;包括文件组织结构和索引结构&#xff0c;以及建立在其基础上的数据过滤机制&#xff0c;从Part裁剪到Mark裁剪&#xff0c;最后到基于SIMD的行过滤机制。 数据过滤机制实质上是构建在数据存储格式之上的算法&#xff0c;所…

GNN与Transformer创新结合!模型性能起飞!

前言 近年来&#xff0c;图神经网络&#xff08;GNN&#xff09;和Transformer模型分别凭借其独到的优势&#xff0c;在处理复杂数据结构和识别序列间的相互依赖性方面取得了突破性进展&#xff0c;这些优势使得GNN和Transformer的结合成为图表示学习领域的一个有前景的研究方…

微信小程序实现图生图(AI动漫特效)效果代码(触站API)

1.效果 触站AI图生图 2.本次用的是触站平台的API,我申请的适用积分,有水印(博主没钱)。如果需要没有水印的可以去买他们的资源包 3.首先我们需要去触站官网平台注册/登录账号(已注册可跳过该步骤) 4.开通API权限 我们可以在主页看到自己免费获取的500积分,用于接口调用…

Echarts 中type是value的X轴在设置了interval间隔后没有展示

文章目录 问题分析问题 Echarts中type是value的X轴在设置了interval间隔后没有展示 分析 之前代码是这样写的:axisLabel 属性中设置了 interval ,但未起作用,原因如下 在 ECharts 中,interval 属性是用于类目型(category)轴的刻度间隔设置,并不适用于数值型(value)…

2024会声会影激活码免费注册码大揭秘!

在当今数字化时代&#xff0c;视频编辑已经成为了许多人日常生活和工作中不可或缺的一部分。无论是制作短视频、Vlog还是专业影视剪辑&#xff0c;一款优秀的视频编辑软件都能让我们事半功倍。而市面上众多的视频编辑软件中&#xff0c;会声会影无疑是备受瞩目的一款。本文将为…

如何将华为Ascend手机的短信和联系人安全传输到电脑

华为Ascend系列手机以其流畅的使用体验、光滑的触感以及轻巧的设计赢得了市场的青睐。不仅如此&#xff0c;Ascend系列手机还以亲民的价格和出色的用户体验&#xff0c;搭载了众多先进功能&#xff0c;如Ascend P6的4.7英寸大屏、海思四核处理器、2GB RAM和800万像素摄像头等。…

3 - 大的国家(高频 SQL 50 题基础版)

3.大的国家 -- 查询属性&#xff1a;国家名称、人口和面积 select name,population,area fromWorld where area>3000000 OR population>25000000;

【启明智显分享】Model3A 7寸彩屏应用于智能中控屏解决方案

比尔盖茨曾在出版的《未来之路》中预言&#xff1a;“在不远的未来&#xff0c;没有智能家居系统的住宅会像不能上网的住宅一样不合潮流”。随着5G时代的到来&#xff0c;国内十几万家企业竞逐智能家居的产业赛道。智能家居的风口已然到来&#xff0c;智能家居产品也不断进行升…

图片如何修改尺寸?四种好用的修改图片尺寸方法!

图片如何修改尺寸&#xff1f;图片是一种常见的文件类型&#xff0c;它存在于什么生活的方方面面&#xff0c;虽然图片很好用&#xff0c;但是大家日常也要注意图片的尺寸&#xff0c;如果图片尺寸不对是会带来很多问题的&#xff0c;下面小编就举例说明几个问题&#xff0c;首…

Android Studio 中文汉化教程

1. 中文语言包 一般jetbrains系列软件都可以使用“中文语言包”进行汉化&#xff0c;语言包如下图所示&#xff1a; 然而&#xff0c;Android Studio的Marketplace并没有类似的中文语言包&#xff08;如下图&#xff09;&#xff0c;经过查阅相关资料发现需要去jetbrains的插件…

【C语言】结构体(及位段)

你好&#xff01;感谢支持孔乙己的新作&#xff0c;本文就结构体与大家分析我的思路。 希望能大佬们多多纠正及支持 &#xff01;&#xff01;&#xff01; 个人主页&#xff1a;爱摸鱼的孔乙己-CSDN博客 欢迎 互粉哦&#x1f648;&#x1f648;&#xff01; 目录 1. 声明结构…

查看 samba 文件共享服务器地址的具体 IP

问题背景 在某个局域网中&#xff0c;已知 samba 文件共享服务器的地址如 \\samba_share在该局域网的子网中&#xff0c;由于 dns 服务器缺失&#xff0c;无法通过地址 \\samba_share 直接访问该服务器 解决方法 使用 ping 命令查看某个地址的 ip &#xff1a; ping [addre…