前端 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: 组件

TypeScript 复制代码
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%')
    }
  }
}

interface CommandModel { // 泛型: 交互
  sn: string,
  args: Object,
  command: string,
}

class WebCallAppClass {
  constructor() {
  }

  WebCallApp(value: string): string { // 采用该 Json 对象模式,通过解析对象中的 type 后细化对应不同的子方法
    console.log('[交互] --- WebCallApp - 测试')
    console.log(value)
    let params:CommandModel = JSON.parse(value)
    console.info(params.sn)
    console.info(params.command)
    console.info(JSON.stringify(params.args))
    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 段落后,在业务所需的地方调用即可

javascript 复制代码
// 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
  })();
</script>
TypeScript 复制代码
// 业务所需的点击事件方法中调用
methods : {
    onClickGoBack() {
        let str = WebCallAppSsss.CmdTest('[交互] - 测试'); // 方式一
        // this.webApp.WebCallApp('CmdTest', '111111'); // 方式二
        Toast.success('abc');
    },
}

方式二.通过自定义 js

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

javascript 复制代码
// 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 sn = null;
        let params = {
          args: args || {}, // 入参
          command: command, // 交互协议
          sn: this.getSerialNumber()
        };
        console.log('[鸿蒙] - 入参');
        console.log(params);

        // if (typeof WebCallApp === 'undefined') { // 移至 AppMsgHandlers 中
        //   Elf.WebCallApp = {
        //     WebCallApp: (args) => {}
        //   };
        // }
        // window.WebCallApp.Elf = Elf;


        let Str = WebCallApp['WebCallApp'](JSON.stringify(params));
        console.log(Str);
      } 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('');
  },
}
javascript 复制代码
/* eslint-disable */
// AppMsgHandlers
import webApp from './index';

/*
 *
 * app对接
 * 移动端种植Elf对象
 * window => Elf
 * 
 */
(function () {
  if (!window.applicationCache&&typeof(Worker)=='undefined') {
    alert("E001-检测到您的环境不支持HTML5,程序终止运行!");//不支持HTML5
    return;
  }
  // iOS & Android
  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
  // Harmony
  if (typeof WebCallApp === 'undefined') { // var global = window; 
    global.WebCallApp = {
      WebCallApp: (args) => {}
    };
  }
  //global.WebCallApp.Elf = Elf;
  WebCallApp.global = global;  
})();

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);
    }
  };
}
// if (typeof WebCallApp === 'undefined') { // var global = window;
//   Elf.WebCallApp = {
//     WebCallApp: (args) => {}
//   };
// }
// window.WebCallApp.Elf = Elf;

export {
  AppCallBacks,
  AppCommendBackHandlers
}

业务方法调用

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

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

相关推荐
恋猫de小郭34 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
一只大侠的侠5 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端