使用uniapp实现微信小程序webview与h5通信以及踩坑记录

7,680 阅读4分钟

摘要

本文记录了菜鸟作者在使用uniapp开发项目时,遇到的众多问题其二:实现开发微信小程序的webview与h5页面的间接实时通信。

吐槽:不愧是大厂啊,就是这么财大气粗有个性。

前言

开发工具: uniapp 3.1.22;微信小程序开发者工具 Stable 1.05.2107090

研发背景:在开发支付宝小程序时,发现webview和H5是可以实时通讯的,因此h5页面在开发时使用了不少这种接口。但是,当开发微信小程序时,惊讶的发现它竟然不支持实时通信,不愧是大厂,就是这么傲娇。

正文

一、h5发送消息到webview

因为到目前为止,微信小程序的wx.miniProgram.postMessage()方法还不能实时通信,而只能在特定时机(小程序后退、组件销毁、分享)触发组件的message事件,所以要实现这个目标还要另寻出路。

有没有方法呢?

当然,还是有方法的。废话哈,否则就不会有这篇博客了。我在网络上就看到过别人给出的解决方案。这里就直接拿过来用了。这是一个绕远路的方式,先用官方api中的wx.miniProgram.navigateTo()方法,从A页面中的H5页面(命名为a)打开第二个页面B,然后在B页面通过const pages = getCurrentPages()方法来拿到A页面的实例,从而调用A页面中的方法,同时关闭页面B。

(一)具体代码如下:

1、页面A: 通过web-view标签打开一个H5页面a

<template>
    <view>
        <web-view :src="src" @message="getMessage" ></web-view>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                src: '',
            };
        },
        onLoad: function(option) {
            this.src = "http://127.0.0.1:8080/#/a";
        },
	methods: {
            getMessag(){
                
            },
            callback({  // 接收页面B回传的参数
                response,
                method
            }){
                console.log("callback.response: ", response); // 显示 {name: "张三"}
                console.log("callback.method: ", method); // 显示 passToA
            },
	}
    };
</script>

<style>
	
</style>

2、H5页面a: 这里只做一件事,打开新页面B

function passToWebView(){
    wx.miniProgram.navigateTo({
        url: "/pages/wxWebView/B?name=张三"
    });
}

3、页面B: 接受参数,然后转发给页面A

<template>
    <view>
        通信中...
    </view>
</template>

<script>
    export default {
        data(){
            return {
                prevPageResponse: {}
            };
        },
        onLoad(option) {
            this.prevPageResponse = option;
            wx.navigateBack(); // 立刻关闭页面
        },
        onUnload(){ // 关闭页面时调用的生命周期
            const pages = getCurrentPages();
            const prevPage = pages[pages.length - 2]; // 上一个页面,也就是页面A
            if (prevPage.$vm.callback != undefined) {
                prevPage.$vm.callback({
                    response: this.prevPageResponse,
                    method: "passToA"
                });
            }
        }
    }
</script>

<style>
</style>

(二)弊端

这么做会有一个弊端,就是会把页面B显示出来,这里没有办法,只能这么做。如果有大佬有更好的方法,请不吝赐教。

二、webview发送消息到h5

想要用官方api实现,几乎是不可能的,菜鸟作者并没有找到这种方法,网上貌似也没有,因为我没找到。

但是,经过我再三的尝试,发现了一种实现方式,通过h5监听hashchange事件来实现!使用这种解决方式的前提是url必须是hashmode,如果不是也可以根据这种思路,摸索出类似的解决方式。

重要的事情说三遍:这不是官方方法!这不是官方方法!这不是官方方法!

所以,存在一定的风险。将来如果鹅厂抽风了,把这种方式给更新掉了,我们也只能xxxxx。不过,到时候可能还会有新的解决方式也不一定。万一哪天鹅厂大佬想通了,把实时通信放开了呢。

不过,考虑最坏的情况出现的话,还可以通过后台提供websocket或者轮询等服务,来解决这个通信问题。

(一)代码实现

1、页面A-更新: 在callback方法接收到回传参数后,直接调用通信h5方法

<template>
    <view>
        <web-view :src="src" @message="getMessage" ></web-view>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                src: '',
                backupSrc: ''
            };
        },
        onLoad: function(option) {
            this.src = "http://127.0.0.1:8080/#/a";
            this.backupSrc = this.src; // 保存原始地址,方便多次通信时,参数不会被污染。
        },
	methods: {
            getMessag(){
                
            },
            callback({  // 接收页面B回传的参数
                response,
                method
            }){
                console.log("callback.response: ", response); // 显示 {name: "张三"}
                console.log("callback.method: ", method); // 显示 passToA
                
                let src = this.backupSrc;
                if (src.indexOf("?") <= -1) {
                    src += "?";
                } else {
                    src += "&";
                }
                src += "__callback=1&__callbackResponse=" + JSON.stringify(response);

                if (method) {
                    src += "&__callbackMethod=" + method;
                }
                
                src += "&" + Math.random(); // 当需要多次通信时,避免src没有变化导致hashchange失效
                
                this.src = src; // 通过修改src,来让hashchange监听事件执行
            },
	}
    };
</script>

<style>
	
</style>

2、h5页面a-更新: 更新passToWebView方法,实现接收返回参数

let hashchange = (method) => {
	
    return new Promise((resolve, reject) => {
        // 微信小程序在webview的src被修改过的情况下,会在关闭页面时,
        // 触发其他页面的postmessage,导致连锁反应从而触发其他页面的hashchange
        window.addEventListener('hashchange', function(event){

            let callback = JSUtil.getQueryVariable("__callback");
            let callbackMethod = JSUtil.getQueryVariable("__callbackMethod");
            let callbackResponse = JSUtil.getQueryVariable("__callbackResponse");
            if (JSUtil.isNotEmpty(callback)) {
                if (callbackMethod == method 
                        || (JSUtil.isEmpty(callbackMethod) && JSUtil.isEmpty(method))) {

                    if (callbackResponse && typeof callbackResponse == 'string' && callbackResponse.indexOf("{") == 0) {
                            callbackResponse = JSON.parse(decodeURIComponent(callbackResponse));
                    }

                    if (callback == 1) {
                            resolve(callbackResponse);
                    } else {
                            reject(callbackResponse);
                    }
                }
            }
        });
    });
}

function passToWebView(){
    wx.miniProgram.navigateTo({
        url: "/pages/wxWebView/B?name=张三"
    });
    
    hashchange("passToA").then((res) => {
        // 这里只是做个示范,把参数返回了,实际开发中,可以在小程序页面完成一些操作后,把操作结果传进来
        console.log(res); // 显示 {name: "张三"}
    });
}

(二)优点

这么做有一个优点,不知道大家发现没有。就是传参时,不会把参数暴露到网络上,只会在客户内部传递,除非微信的webview本身有漏洞,否者一般不会有暴露的风险。这样就可以传递一些比较敏感的信息,比如token、秘钥等等。这也是我为什么废了这么多时间要实现这种传递参数的方式的原因。

三、采坑

1、webview标签的src路径千万不要出现“//”的路径,否则页面会不停刷新。

2、hashchange接收的callbackResponse是自动编码过的,必须用decodeURIComponent进行解码

四、后记

如果有官方的方法,当然更好,哪位大佬还有更好的方法的话,还请多多指教。

这里再重复一遍,考虑出现最坏的情况,这种方式不能用了的时候,还可以通过后台提供websocket或者轮询等服务,来解决这个通信问题。

有时候,解决问题的方法,往往出人意料又在知识体系之内,不试试还真不知道能有什么方式来解决困难。生活也是如此,需要勇敢一点,需要有点实现理想的勇气,万一实现了呢?