uni-app中h5和App通讯,兼容鸿蒙

2,546 阅读8分钟

前言

随着华为纯血鸿蒙系统的到来,App对于鸿蒙的支持也需要提上日程,幸运的是,之前App是基于跨端框架uni-app开发的,uni-app最近也慢慢的实现了对于鸿蒙的支持,当然还有许多问题需要完善,需要陪其一起踩坑。不幸的是,我们之前使用的是vue2,uni-app的vue2版本没有支持鸿蒙,改造需要太多的工作量,所以前期先采用取巧的方式,先用vue3版本开发一个鸿蒙的App,然后之前项目编译为h5通过web-view的方式嵌入,这样子就需要考虑到cookie/token的共享,h5怎么调用App原生方法等问题,这就涉及到了h5和App之间通信。

这篇文章能学习到的:

  • App和h5之间的通信,支持鸿蒙
  • h5调用App原生方法封装
  • 使用Promise封装通信库
  • 开发过程中遇到的一些坑,及对应的解决方案
  • 提供完整代码

原理

h5向App发送数据

h5向App发送数据,主要是使用postMessage方法,按照uni-app官网讲的,可以使用window.postMessage,但经过我的实践,这个方法并没办法发送数据,所以采用另外一种方式uni.postMessage,值得注意的是这个方法虽然是以uni开头,但在h5中并没有这个方法,需要额外引入一个js(uni.webview.1.5.6.js)才能支持,需要1.5.6版本才支持鸿蒙系统。另外一个点是这个文件提供的全局变量是uni,如果你的子应用也是uni-app开发的,那可能会导致变量冲突,因为这个文件是一个commonjs,可以直接修改全局变量,我将其改为了webUni,修改后的代码在下面有提供uni.webview.js

uni.webview.js已经是commonjs代码了,不需要在进行打包(如果进行重新编译后会报错),可以直接在index.html中引入,对于不需要打包文件,你可能想着将js文件放在public文件夹下,但这样子在uni-app中并不行,它不会帮你拷贝到构建结果dist中,推荐方式可以放在static文件夹下,所以在index.html添加代码:

<script type="text/javascript" src="/static/js/uni.webview.js"></script>

需要等待UniAppJSBridgeReady 事件触发后,才能调用对应API,这个事件是由uni.webview.js触发的。传递数据是格式要求的,必须写在 data 对象中。所以发送消息的具体代码如下:

document.addEventListener('UniAppJSBridgeReady', function() {
  uni.postMessage({
    data: {
      action: 'message'
    }
  });
})

App端接收h5发送的数据

h5向APP发送消息后,可以在 <web-view> 的 message 事件回调 event.detail.data 中接收消息。event.detail.data是一个数组,正常只有一个数据,所以直接取数组的第一项就可以了。

接收消息的代码如下:

<template>
  <web-view id="webview" @message="handleMessage" src="http://www.test.com"></web-view>
</template>
<script setup>
const handleMessage = (e) => {
  const data = e.detail.data[0];
  console.log(data);
};
</script>

App向h5发送数据

App向h5发送数据主要是在h5中将事件挂载到window全局对象上,然后App端获取当前页面的webview对象,通过webView.evalJS方法执行h5被挂载到window上的方法,将数据作为方法的参数,这个样子在h5中的方法被执行时,可以获取到来自App传递的参数。当然对于App获取webview对象,在Android和IOS系统鸿蒙系统是存在差异的,以及evalJS方法也存在坑点。

Android和IOS系统

在以前vue2时是通过this.$scope.$getAppWebview()来获取当前页面的webview对象。在vue3的组合式 API (Composition API)并没办法直接使用this,我采用getCurrentInstance获取当前实例,可惜的是当前实例中没有$scope属性。所以采用另外一种方式,获取当前页面路由实例,在其中有包含$getAppWebview方法。uni-app并没有提供直接获取当前页面路由的方法,可以通过getCurrentPages方法获取所有路由,然后取出最后一个,就是当前页面的路由。

$getAppWebview这个方法也不是直接获取到webview实例,需要在children中获取,根据官网描述,还需要有个setTimout的延迟。如果我们需要将App的token发送给H5,那么需要在H5的window上挂载一个方法,例如window.getToken,那么Android和IOS系统发送token给H5的代码就是这样子。

  • App端
// 获取所有页面路由
const pages = getCurrentPages();
// 找到最后一个索引
const index = pages.length - 1;
// 获取到当前页面的webView实例
const currentWebview = pages[index].$getAppWebview();
setTimeout(() => {
  // 取到真正的webview
  const webView = currentWebview.children()[0];
  // 通过evalJS执行H5中的getToken方法,并将token作为参数传递
  const token = 'token_123456789';
  webView.evalJS(`getToken('${token}')`);
});
  • H5端
// 在window上挂载getToken方法,等待被执行,然后获取Token
window.getToken = function(token) {
   // 被App执行后,就可以获取来自App的token
   console.log(token)
}

鸿蒙系统

在鸿蒙系统中,获取webView对象的方式更简单一些,可以通过uni.createWebviewContext(webviewId, component?)方法来创建webview实例,这个方法需要两个参数,第一个参数为webviewId,第二个参数为组件实例,也就是this。官网说第二个参数为可选,我实践中发现如果不传第二个参数的话是会报错的。需要注意的点:

  • 需要在web-view标签上添加id属性,我设置的id为webview;
  • getCurrentInstance不要写到函数内部里面,比如handleMessage里面
  • 注意鸿蒙这边webview.evalJs的最一个s是小写的,而Android和IOS系统最后一个s为大写
  • 一般App向h5发送数据,需要H5的window上挂载了事件,这也意味着需要h5资源正常加载了,不然就找不到window事件了。那App怎么知道H5加载了呢,可以在H5端向App发送个数据,告知App我已经加载好了,你可以发送数据了,所以我这边把发送数据写在了handleMessage中。从另外一个角度来讲,不需要App主动去发送数据,而是H5主动来拿数据。

鸿蒙系统的代码如下:

<template>
  <web-view id="webview" @message="handleMessage" src="http://www.test.com"></web-view>
</template>
<script setup>
import { getCurrentInstance } from 'vue';
const context = getCurrentInstance();
const handleMessage = (e) => {
  const data = e.detail.data[0];
  // 这边可以加类型判断
  const webview = uni.createWebviewContext('webview', context);
  const token = 'token_xxx';
  webview.evalJs(`getToken('${token}')`);
};
</script>

Promise封装

我们现在已经实现了H5向App发送数据,以及App向H5发送数据,满足了基本需求,但是总感觉还差了点什么,有点割裂感,两者之间互不相关。或者说我从H5发送了个数据,想要等待App那边处理完在返回给我,但我并不知道在哪里等待,或者处理起来很麻烦。聪明的你一定想到了,裹一层Promise不就能满足我们的需求了嘛,那就一起看看怎么将通信Promise化。我们这边主要是对于H5向App通讯封装为Promise。

对于将一些功能Promise化,我们已经熟练到脚趾头都能实现,新增一个函数,返回一个Promise,成功的话,执行resolve,失败的话,执行reject。我们这个也是一样的,将uni.postMessage放在一个函数中,然后执行函数的时候返回一个Promise,主要问题是怎么才能让App执行resolve,reject呢。我们又想到了App执行的是H5挂载到window上的方法,那我们将resolve,reject挂载到window上,不就实现了。我们将token通信改为这样子:

  • H5端代码
getToken() {
  return new Promise((resolve, reject) => {
    const data = { type: 'token' };
    webUni.postMessage({
      data,
    });
    window.tokenResolve = resolve;
    window.tokenReject = reject;
  });
}
// 使用方式
// 当tokenResolve方法被App执行,就能在then中获取token了
getToken().then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});
  • App端
const handleMessage = (e) => {
  const data = e.detail.data[0];
  const webview = uni.createWebviewContext('webview', context);
  const token = 'token_xxx';
  // 这边就可以执行H5中Promise的resolve方法了
  webview.evalJs(`tokenResolve('${token}')`);
};

原生事件的支持

我们除了一些数据通信之外,H5没办法执行App原生事件也是我们的一大痛点,我们还需要将这个功能做一下封装。对于Android和IOS来说原生事件有uni和plus两部分,例如:扫码功能uni.scanCode、获取当前应用的APPID的plus.runtime.appid。而对于鸿蒙来说,由于不支持plus,所以只有uni相关事件。但我们还是需要支持uni和plus两种原生事件的调用。

uni-app提供的原生方法主要有两种,一种是函数类型的,执行一个函数,传递一个对象,对象中包含函数参数和success和fail回调,成功的话执行success回调,失败了执行fail回调,例如:uni.scanCode,一种是属性类型的,执行完之后就能直接获取到值,例如:plus.runtime.appid,下面提供两种类型的大致代码结构。

// 函数类型
uni.scanCode({
  // 函数参数
  scanType: ['barCode'],
  success: function (res) {
    console.log(res);
  },
  fail: function(err) {
    console.log(err);
  }
});
// 属性类型
plus.runtime.appid// 输出 xxxxxxx

因为这两种调用方式的结构还是比较固定的,就可以有个设想,H5向App传递一个函数名称和参数(可选),例如:{fn: 'uni.scanCode', params?: {scanType: ['barCode']}},然后判断fn是否为函数,如果不是函数的话,可以直接执行,然后将直接结果传递给H5;如果fn是函数的话,将params作为函数参数进行执行,手动添加success和fail方法,在success中执行H5挂载在window的resolve,fail中执行H5挂载在window的reject方法,这样子就可以在H5中的then中获取到直接结果,才catch中获取到报错内容,这样子说的话可能有点抽象,可以直接看如下代码。

  • 我们只对于uni和plus方法进行处理,当然可以根据自己的需要进行扩展
  • H5传递过来的函数名称只是一个字符串'uni.scanCode',并没办法执行,所以需要对其进行处理,然后从uni或者plus对象上获取到真正的函数或者属性才能进行执行。
// 解析函数名称
const parseNativeFn = (fnName) => {
  const uniReg = /^uni.(.+)/;
  const plusReg = /^plus.(.+)/;
  let nativeFn = null;
  let reg = null;
  if (uniReg.test(fnName)) {
    nativeFn = uni;
    reg = uniReg;
  } else if (plusReg.test(fnName)) {
    nativeFn = plus;
    reg = plusReg;
  } else {
    return;
  }
  fnName = fnName.match(reg)[1];
  const fnNameArr = fnName.split('.');
  for (const key of fnNameArr) {
    nativeFn = nativeFn[key];
  }
  return nativeFn;
};
  • 这边的data就是H5传递过来的参数
  • evalJS是webview上的webview.evalJS
  • invokeResolve和invokeReject是H5执行原生插件时Promise的resolve和reject
  • 假设fn为扫码功能uni.scanCode,那么params就是其需要的参数{scanType: ['barCode']}
  • App传给H5的参数需要为字符串,所以加了JSON.stringify。
// 执行原生插件
const invokeNativePlugin = (data, evalJS) => {
  const { fn, params } = data;
  if (!fn) {
    evalJS(`invokeReject('请传入函数名称!')`);
    return;
  }
  // 字符串转为真实的原生方法
  const nativeFn = parseNativeFn(fn);
  if (!nativeFn) {
    evalJS(`invokeReject('${fn}该方法不存在!')`);
    return;
  }
  if (typeof nativeFn === 'function') {
    nativeFn({
      ...params,
      success: function (res) {
        evalJS(`invokeResolve(${JSON.stringify(res)})`);
      },
      fail: function (err) {
        evalJS(`invokeReject(${JSON.stringify(err)})`);
      },
    });
  } else {
    // 不是函数,例如plus.runtime.appid,执行完就可以获取到值
    evalJS(`invokeResolve('${nativeFn}')`);
  }
};
  • 在H5中就可以这么使用
/**
 * 调用 uni.xxx 方法,例如 uni.getLocation
 * @param {string} fn 函数名称 必填
 * @param {object} params 函数参数 可选
 * @returns 结果为Promise
 */
uni.nativeInstance
  .invoke({
    fn: 'uni.getLocation',
    params: {
      type: 'wgs84',
    },
  })
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

完整代码

APP端代码

页面代码

<template>
  <web-view
    id="webview"
    @message="handleMessage"
    src="http://www.test.com"
  ></web-view>
</template>

<script setup>
import { getCurrentInstance } from 'vue';
import invokeNative from '@/utils/invokeNative.js';
const context = getCurrentInstance();
const handleMessage = (e) => {
  const data = e.detail.data[0];
  invokeNative(data, context);
};
</script>

src/utils/invokeNative.js

// 解析函数名称
const parseNativeFn = (fnName) => {
  const uniReg = /^uni\.(.+)/;
  const plusReg = /^plus\.(.+)/;
  let nativeFn = null;
  let reg = null;
  if (uniReg.test(fnName)) {
    nativeFn = uni;
    reg = uniReg;
  } else if (plusReg.test(fnName)) {
    nativeFn = plus;
    reg = plusReg;
  } else {
    return;
  }
  fnName = fnName.match(reg)[1];
  const fnNameArr = fnName.split('.');
  for (const key of fnNameArr) {
    nativeFn = nativeFn[key];
  }
  return nativeFn;
};

// 执行原生插件
const invokeNativePlugin = (data, evalJS) => {
  const { fn, params } = data;
  if (!fn) {
    evalJS(`invokeReject('请传入函数名称!')`);
    return;
  }
  const nativeFn = parseNativeFn(fn);
  if (!nativeFn) {
    evalJS(`invokeReject('${fn}该方法不存在!')`);
    return;
  }
  if (typeof nativeFn === 'function') {
    nativeFn({
      ...params,
      success: function (res) {
        evalJS(`invokeResolve(${JSON.stringify(res)})`);
      },
      fail: function (err) {
        evalJS(`invokeReject(${JSON.stringify(err)})`);
      },
    });
  } else {
    evalJS(`invokeResolve('${nativeFn}')`);
  }
};

const getToken = async (evalJS) => {
  // TODO: 根据实际方式获取
  const token = 'token_123456';
  evalJS(`tokenResolve('${token}')`);
};

const getEvalJs = async (context) => {
  return new Promise((resolve) => {
    // #ifdef APP-HARMONY
    const webview = uni.createWebviewContext('webview', context);
    resolve(webview.evalJS || webview.evalJs);
    // #endif
    // #ifndef APP-HARMONY
    const pages = getCurrentPages();
    const index = pages.length - 1;
    const currentWebview = pages[index].$getAppWebview();
    setTimeout(() => {
      const webView = currentWebview.children()[0];
      resolve((code) => webView.evalJS(code));
    });
    // #endif
  });
};

const invokeNative = async (data, context) => {
  const { type } = data;
  const evalJS = await getEvalJs(context);
  if (type === 'token') {
    getToken(evalJS);
  } else if (type === 'invoke') {
    invokeNativePlugin(data, evalJS);
  }
};

export default invokeNative;

h5端代码

index.html

<script>
  document.addEventListener('UniAppJSBridgeReady', function () {
    window.__UniAppJSBridgeReady__ = true;
  });
</script>
<script type="text/javascript" src="/static/js/uni.webview.js"></script>

/static/js/uni.webview.js

!(function (e, n) {
  'object' == typeof exports && 'undefined' != typeof module
    ? (module.exports = n())
    : 'function' == typeof define && define.amd
    ? define(n)
    : ((e = e || self).webUni = n());
})(this, function () {
  'use strict';
  try {
    var e = {};
    Object.defineProperty(e, 'passive', {
      get: function () {
        !0;
      },
    }),
      window.addEventListener('test-passive', null, e);
  } catch (e) {}
  var n = Object.prototype.hasOwnProperty;
  function i(e, i) {
    return n.call(e, i);
  }
  var t = [];
  function o() {
    return window.__dcloud_weex_postMessage || window.__dcloud_weex_;
  }
  function a() {
    return window.__uniapp_x_postMessage || window.__uniapp_x_;
  }
  var r = function (e, n) {
      var i = { options: { timestamp: +new Date() }, name: e, arg: n };
      if (a()) {
        if ('postMessage' === e) {
          var r = { data: n };
          return window.__uniapp_x_postMessage
            ? window.__uniapp_x_postMessage(r)
            : window.__uniapp_x_.postMessage(JSON.stringify(r));
        }
        var d = {
          type: 'WEB_INVOKE_APPSERVICE',
          args: { data: i, webviewIds: t },
        };
        window.__uniapp_x_postMessage
          ? window.__uniapp_x_postMessageToService(d)
          : window.__uniapp_x_.postMessageToService(JSON.stringify(d));
      } else if (o()) {
        if ('postMessage' === e) {
          var s = { data: [n] };
          return window.__dcloud_weex_postMessage
            ? window.__dcloud_weex_postMessage(s)
            : window.__dcloud_weex_.postMessage(JSON.stringify(s));
        }
        var w = {
          type: 'WEB_INVOKE_APPSERVICE',
          args: { data: i, webviewIds: t },
        };
        window.__dcloud_weex_postMessage
          ? window.__dcloud_weex_postMessageToService(w)
          : window.__dcloud_weex_.postMessageToService(JSON.stringify(w));
      } else {
        if (!window.plus)
          return window.parent.postMessage(
            { type: 'WEB_INVOKE_APPSERVICE', data: i, pageId: '' },
            '*'
          );
        if (0 === t.length) {
          var u = plus.webview.currentWebview();
          if (!u) throw new Error('plus.webview.currentWebview() is undefined');
          var g = u.parent(),
            v = '';
          (v = g ? g.id : u.id), t.push(v);
        }
        if (plus.webview.getWebviewById('__uniapp__service'))
          plus.webview.postMessageToUniNView(
            { type: 'WEB_INVOKE_APPSERVICE', args: { data: i, webviewIds: t } },
            '__uniapp__service'
          );
        else {
          var c = JSON.stringify(i);
          plus.webview
            .getLaunchWebview()
            .evalJS(
              'UniPlusBridge.subscribeHandler("'
                .concat('WEB_INVOKE_APPSERVICE', '",')
                .concat(c, ',')
                .concat(JSON.stringify(t), ');')
            );
        }
      }
    },
    d = {
      navigateTo: function () {
        var e =
            arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
          n = e.url;
        r('navigateTo', { url: encodeURI(n) });
      },
      navigateBack: function () {
        var e =
            arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
          n = e.delta;
        r('navigateBack', { delta: parseInt(n) || 1 });
      },
      switchTab: function () {
        var e =
            arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
          n = e.url;
        r('switchTab', { url: encodeURI(n) });
      },
      reLaunch: function () {
        var e =
            arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
          n = e.url;
        r('reLaunch', { url: encodeURI(n) });
      },
      redirectTo: function () {
        var e =
            arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
          n = e.url;
        r('redirectTo', { url: encodeURI(n) });
      },
      getEnv: function (e) {
        a()
          ? e({ uvue: !0 })
          : o()
          ? e({ nvue: !0 })
          : window.plus
          ? e({ plus: !0 })
          : e({ h5: !0 });
      },
      postMessage: function () {
        var e =
          arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
        r('postMessage', e.data || {});
      },
    },
    s = /uni-app/i.test(navigator.userAgent),
    w = /Html5Plus/i.test(navigator.userAgent),
    u = /complete|loaded|interactive/;
  var g =
    window.my &&
    navigator.userAgent.indexOf(
      ['t', 'n', 'e', 'i', 'l', 'C', 'y', 'a', 'p', 'i', 'l', 'A']
        .reverse()
        .join('')
    ) > -1;
  var v =
    window.swan && window.swan.webView && /swan/i.test(navigator.userAgent);
  var c =
    window.qq &&
    window.qq.miniProgram &&
    /QQ/i.test(navigator.userAgent) &&
    /miniProgram/i.test(navigator.userAgent);
  var p =
    window.tt &&
    window.tt.miniProgram &&
    /toutiaomicroapp/i.test(navigator.userAgent);
  var _ =
    window.wx &&
    window.wx.miniProgram &&
    /micromessenger/i.test(navigator.userAgent) &&
    /miniProgram/i.test(navigator.userAgent);
  var m = window.qa && /quickapp/i.test(navigator.userAgent);
  var f =
    window.ks &&
    window.ks.miniProgram &&
    /micromessenger/i.test(navigator.userAgent) &&
    /miniProgram/i.test(navigator.userAgent);
  var l =
    window.tt &&
    window.tt.miniProgram &&
    /Lark|Feishu/i.test(navigator.userAgent);
  var E =
    window.jd && window.jd.miniProgram && /jdmp/i.test(navigator.userAgent);
  var x =
    window.xhs &&
    window.xhs.miniProgram &&
    /xhsminiapp/i.test(navigator.userAgent);
  for (
    var S,
      h = function () {
        (window.UniAppJSBridge = !0),
          document.dispatchEvent(
            new CustomEvent('UniAppJSBridgeReady', {
              bubbles: !0,
              cancelable: !0,
            })
          );
      },
      y = [
        function (e) {
          if (s || w)
            return (
              window.__uniapp_x_postMessage ||
              window.__uniapp_x_ ||
              window.__dcloud_weex_postMessage ||
              window.__dcloud_weex_
                ? document.addEventListener('DOMContentLoaded', e)
                : window.plus && u.test(document.readyState)
                ? setTimeout(e, 0)
                : document.addEventListener('plusready', e),
              d
            );
        },
        function (e) {
          if (_)
            return (
              window.WeixinJSBridge && window.WeixinJSBridge.invoke
                ? setTimeout(e, 0)
                : document.addEventListener('WeixinJSBridgeReady', e),
              window.wx.miniProgram
            );
        },
        function (e) {
          if (c)
            return (
              window.QQJSBridge && window.QQJSBridge.invoke
                ? setTimeout(e, 0)
                : document.addEventListener('QQJSBridgeReady', e),
              window.qq.miniProgram
            );
        },
        function (e) {
          if (g) {
            document.addEventListener('DOMContentLoaded', e);
            var n = window.my;
            return {
              navigateTo: n.navigateTo,
              navigateBack: n.navigateBack,
              switchTab: n.switchTab,
              reLaunch: n.reLaunch,
              redirectTo: n.redirectTo,
              postMessage: n.postMessage,
              getEnv: n.getEnv,
            };
          }
        },
        function (e) {
          if (v)
            return (
              document.addEventListener('DOMContentLoaded', e),
              window.swan.webView
            );
        },
        function (e) {
          if (p)
            return (
              document.addEventListener('DOMContentLoaded', e),
              window.tt.miniProgram
            );
        },
        function (e) {
          if (m) {
            window.QaJSBridge && window.QaJSBridge.invoke
              ? setTimeout(e, 0)
              : document.addEventListener('QaJSBridgeReady', e);
            var n = window.qa;
            return {
              navigateTo: n.navigateTo,
              navigateBack: n.navigateBack,
              switchTab: n.switchTab,
              reLaunch: n.reLaunch,
              redirectTo: n.redirectTo,
              postMessage: n.postMessage,
              getEnv: n.getEnv,
            };
          }
        },
        function (e) {
          if (f)
            return (
              window.WeixinJSBridge && window.WeixinJSBridge.invoke
                ? setTimeout(e, 0)
                : document.addEventListener('WeixinJSBridgeReady', e),
              window.ks.miniProgram
            );
        },
        function (e) {
          if (l)
            return (
              document.addEventListener('DOMContentLoaded', e),
              window.tt.miniProgram
            );
        },
        function (e) {
          if (E)
            return (
              window.JDJSBridgeReady && window.JDJSBridgeReady.invoke
                ? setTimeout(e, 0)
                : document.addEventListener('JDJSBridgeReady', e),
              window.jd.miniProgram
            );
        },
        function (e) {
          if (x) return window.xhs.miniProgram;
        },
        function (e) {
          return document.addEventListener('DOMContentLoaded', e), d;
        },
      ],
      M = 0;
    M < y.length && !(S = y[M](h));
    M++
  );
  S || (S = {});
  var P = 'undefined' != typeof webUni ? webUni : {};
  if (!P.navigateTo) for (var b in S) i(S, b) && (P[b] = S[b]);
  return (P.webView = S), P;
});

/src/utils/getNativeInstance.js

class NativeInstance {
  constructor() {
    this.readyPromise = new Promise((resolve) => {
      if (window.__UniAppJSBridgeReady__) {
        resolve();
      }
      document.addEventListener('UniAppJSBridgeReady', function () {
        resolve();
      });
    });
  }
  async invoke(data) {
    await this.readyPromise;
    data = {
      type: 'invoke',
      ...data,
    };
    webUni.postMessage({
      data,
    });
    return new Promise((resolve, reject) => {
      window.invokeResolve = resolve;
      window.invokeReject = reject;
    });
  }
  async getToken() {
    await this.readyPromise;
    const data = { type: 'token' };
    webUni.postMessage({
      data,
    });
    return new Promise((resolve) => {
      window.tokenResolve = resolve;
    });
  }
  async logout() {
    await this.readyPromise;
    const data = { type: 'logout' };
    webUni.postMessage({
      data,
    });
  }
  async close() {
    await this.readyPromise;
    const data = { type: 'close' };
    webUni.postMessage({
      data,
    });
  }
  async switchTitle(title) {
    await this.readyPromise;
    const data = { type: 'title', title };
    webUni.postMessage({
      data,
    });
  }
}

let instance = null;

export default () => {
  // #ifdef H5
  if (!instance) {
    instance = new NativeInstance();
  }
  // #endif
  return instance;
};

main.js

import getNativeInstance from '@/utils/getNativeInstance.js';
uni.nativeInstance = getNativeInstance();

使用方式

获取token

/**
 * 获取Token
 * @returns 结果为Promise
 */
uni.nativeInstance
  .getToken()
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

调用uni原生方法

/**
 * 调用 uni.xxx 方法,例如 uni.getLocation
 * @param {string} fn 函数名称 必填
 * @param {object} params 函数参数 必填
 * @returns 结果为Promise
 */
uni.nativeInstance
  .invoke({
    fn: 'uni.getLocation',
    params: {
      type: 'wgs84',
    },
  })
  .then((res) => {
    console.log(res);
    alert(res);
  })
  .catch((err) => {
    console.log(err);
  });

调用plus原生方法

/**
 * 调用 plus.xxx.xxx 方法,例如 plus.device.uuid
 * @param {string} fn 函数名称 必填
 * @param {object} params 函数参数 选填
 * @returns 结果为Promise
 */
uni.nativeInstance
  .invoke({
    fn: 'plus.device.uuid',
  })
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });  

总结

这篇文章提供了uni-app的vue2版本或者普通vue项目支持鸿蒙App的一种方式,同时讲解Android和IOS以及鸿蒙系统中App和H5如何进行通信,并对其做了Promise封装,支持双向通信,在通信中衍生出了H5对于App原生方法的调用,最后提供了完整代码。

如果觉得这篇文章对您有帮助,欢迎点赞收藏,有问题也欢迎指出。