前端 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');
    },
}

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

相关推荐
浮华似水13 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
baobao熊4 小时前
【HarmonyOS】时间处理Dayjs
华为·harmonyos
GZ_TOGOGO4 小时前
【2024最新】华为HCIE认证考试流程
大数据·人工智能·网络协议·网络安全·华为