在网页与快应用之间如何构造可靠通信通道?

本文介绍了一种解决快应用与网页通信中消息丢失、格式不一致问题的方法,通过创建辅助库实现有序发送、确保消息到达且不重复。快应用端和网页端的代码示例分别展示如何使用此通信机制。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

本文作者:空空叶

 

 

背 景

 

快应用版本: 1020

 

在使用web组件进行快应用与网页间的通信的时候,有这几个发现:

 

1、运行'npm run watch'时会提示web组件不支持message事件,但实际上是支持的(只是提示上的小问题)

2、快应用发信息到网页,与网页发信息到快应用,用的方法名都是postMessage,但数据格式并不一致,使用起来不方便

3、消息会丢失(比如网页端消息处理函数还没执行到就给网页发信息等情况)

4、网页给快应用发信息时,快应用发送给网页的信息才会带回

 

特 点

 

因此,这里花了点力气,做了个辅助的库,特点:

 

1、消息有序发送

2、消息不会丢失,保证到达

3、消息不会接收多次(去重)

4、消息分类型,类似事件的监听机制,更加方便

 

快应用端代码

 

快应用里,新建一个channel.js文件,内容:

 

 
/**	
 * 通道,用来与html进行可靠的通信	
 *	
 * 1. 有序	
 * 2. 回传与ack机制,保证到达	
 * 3. id验证机制,去重	
 *	
 * @param thisObj {Object} this对象	
 * @param webId {String} web的id	
 * @param timeout {Number} 超时时间,超时后会重试,单位ms,默认1000	
 */	
var Channel = function (thisObj, webId, timeout) {	
    timeout = timeout || 1000	
	
    var that = this	
	
    var debug = (...msgs) => console.debug.apply(null, ['[通道]', ...msgs])	
    var log = (...msgs) => console.log.apply(null, ['[通道]', ...msgs])	
    var error = (...msgs) => console.error.apply(null, ['[通道]', ...msgs])	
	
    //消息id生成器	
    that.idGenerator = 0	
    //消息发送队列	
    that.sendQueue = [	
        // {	
        //     data: {},	
        //     resolve: Object,	
        // },	
    ]	
    //***列表	
    that.listeners = {	
        // type: [listener1, listener2],	
    }	
    //当前接收的消息id	
    that.receivedId = 0	
    /**	
     * @param type {String} 消息类型	
     * @param data {Object} 消息内容	
     * @return {Promise}	
     */	
    that.sendMsg = function (type, data) {	
        var resolve;	
        var promise = new Promise(resolve_ => resolve = resolve_)	
        that.sendQueue.push({	
            data: {	
                pType: 'msg',	
                id: ++that.idGenerator,	
                type: type,	
                data: data,	
            },	
            resolve	
        })	
        return promise	
    }	
    /**	
     * 监听	
     * @param type 消息类型	
     * @param callback 回调	
     */	
    that.on = function (type, callback) {	
        var typeListeners = that.listeners[type] || []	
        that.listeners[type] = typeListeners	
        typeListeners.push(callback)	
    }	
    /**	
     * 收到消息时调用(设置web组件的message事件的处理函数)	
     */	
    that.onMsg = function ({message, url}) {	
        debug('[收到消息]', message, '('+url+')')	
        var {pType, id, type, data} = JSON.parse(message)	
	
        if (pType === 'ack') {//ack包	
            if (id === that.idGenerator) {//是当前的ack,有效,删除元素	
                var nowPackets = that.sendQueue.splice(0, 1);	
                that.valid_(nowPackets.length === 1)	
                nowPackets[0].resolve()	
            }	
        }else if (pType === 'msg') {//正常消息	
            if (id === that.receivedId+1) {//是下个准备接收的包,有效	
                //更新缓存值	
                that.receivedId++	
                //处理	
                var typeListeners = that.listeners[type] || []	
                log('[处理消息]', '类型: '+type, '处理器数量: '+typeListeners.length)	
                for (var i=0;i<typeListeners.length;i++) {	
                    try {	
                        typeListeners[i](data)	
                    } catch (e) {	
                        error('[处理器错误]', e)	
                    }	
                }	
                //响应ack	
                that.send_({	
                    pType: 'ack',	
                    id,	
                })	
            }	
        }else {//没有pType,当作ping包处理,忽略	
            return	
        }	
    }	
    /**	
     * 发包	
     */	
    that.send_ = function (packet) {	
        var str = JSON.stringify(packet)	
        thisObj.$element(webId).postMessage({	
            message: str	
        })	
        debug('[发送消息]', str)	
    }	
    /**	
     * 下个包	
     */	
    that.next_ = function() {	
        if (that.sendQueue.length > 0) {	
            that.send_(that.sendQueue[0].data)	
        }	
    }	
    /**	
     * 验证	
     */	
    that.valid_ = function(bool, errMsg) {	
        if (!bool) {	
            throw new Error(errMsg || 'Valid Fail!')	
        }	
    }	
    //计时器: 不断重试发送包	
    var timerId = setInterval(function () {	
        if (thisObj.$valid) {//仍然有效	
            that.next_()	
        }else {//取消计时器	
            log('已经失效,取消计时器')	
            clearInterval(timerId)	
        }	
    }, timeout)	
}	
	
module.exports = Channel

 

快应用端使用方式(参考):

 
<template>	
    <web id="web" src="xxx" @message="{{onMessage}}"></web>	
</template>	
	
<script>	
    import Channel from './channel'	
	
    export default {	
        channel: null,	
        onInit() {	
            this.channel = new Channel(this, 'web')	
	
            //通道监听	
            this.channel.on('type1', function (data) {	
                //处理收到的数据	
            })	
	
            //发送信息	
            this.channel.sendMsg('type2', {	
                //数据	
            })	
        },	
        onMessage(param) {	
            this.channel.onMsg(param)	
        },	
    }	
</script>

 

网页端代码

 

网页端js(可以新建一个js或复制到script脚本里):

 

 
/**	
 * (在html端,实际上一个页面只能new一个通道)	
 *	
 * 通道,用来与html进行可靠的通信	
 *	
 * 1. 有序	
 * 2. 回传与ack机制,保证到达	
 * 3. id验证机制,去重	
 *	
 * @param logger {Function} 日志记录器,可为null	
 * @param timeout {Number} 超时时间,超时后会重试,单位ms,默认1000	
 */	
var Channel = function (logger, timeout) {	
    timeout = timeout || 1000	
	
    if (!logger) logger = () => {}	
	
    var that = this	
    //消息id生成器	
    that.idGenerator = 0	
    //消息发送队列	
    that.sendQueue = [	
        // {	
        //     data: {},	
        //     resolve: Object,	
        // },	
    ]	
    //***列表	
    that.listeners = {	
        // type: [listener1, listener2],	
    }	
	
    //当前接收的消息id	
    that.receivedId = 0	
    /**	
     * @param type {String} 消息类型	
     * @param data {Object} 消息内容	
     * @return {Promise}	
     */	
    that.sendMsg = function (type, data) {	
        var resolve;	
        var promise = new Promise(resolve_ => resolve = resolve_)	
        that.sendQueue.push({	
            data: {	
                pType: 'msg',	
                id: ++that.idGenerator,	
                type: type,	
                data: data,	
            },	
            resolve	
        })	
        return promise	
    }	
    /**	
     * 监听	
     * @param type 消息类型	
     * @param callback 回调	
     */	
    that.on = function (type, callback) {	
        var typeListeners = that.listeners[type] || []	
        that.listeners[type] = typeListeners	
        typeListeners.push(callback)	
    }	
    /**	
     * 收到消息时调用(设置为onmessage的处理函数)	
     */	
    that.onMsg = function (message) {	
        logger('[收到消息]' + message)	
        var {pType, id, type, data} = JSON.parse(message)	
	
	
        if (pType === 'ack') {//ack	
            if (id === that.idGenerator) {//是当前的ack,有效,删除元素	
                var nowPackets = that.sendQueue.splice(0, 1);	
                that.valid_(nowPackets.length === 1)	
                nowPackets[0].resolve()	
            }	
        } else if (pType === 'msg') {//正常消息	
            if (id === that.receivedId + 1) {//是下个准备接收的包,有效	
                //更新缓存值	
                that.receivedId++	
                //处理	
                var typeListeners = that.listeners[type] || []	
                logger('[处理消息]类型: ' + type + ' 处理器数量: ' + typeListeners.length)	
                for (var i = 0; i < typeListeners.length; i++) {	
                    try {	
                        typeListeners[i](data)	
                    } catch (e) {	
                        logger('[处理异常]'+JSON.stringify(e))	
                    }	
                }	
                //响应ack	
                that.send_({	
                    pType: 'ack',	
                    id,	
                })	
            }	
        } else {//没有pType,忽略	
            return	
        }	
    }	
    /**	
     * 发包	
     */	
    that.send_ = function (packet) {	
        var str = JSON.stringify(packet)	
        system.postMessage(str)	
        logger('[发送消息]' + str)	
    }	
    /**	
     * 下个包	
     */	
    that.next_ = function () {	
        if (that.sendQueue.length > 0) {	
            that.send_(that.sendQueue[0].data)	
        }	
    }	
    /**	
     * 验证	
     */	
    that.valid_ = function (bool, errMsg) {	
        if (!bool) {	
            throw new Error(errMsg || 'Valid Fail!')	
        }	
    }	
    //计时器: 不断重试发送包	
    setInterval(function () {	
        that.next_()	
    }, timeout)	
	
    //计时器: ping	
    //(必须不断地ping,因此html不发请求到app,那么app发送给html的消息就不会过来,蛋疼)	
    setInterval(function () {	
        that.send_({})	
    }, 200)	
    //对接	
    system.onmessage = that.onMsg	
}

 

网页端使用方法(参考):

 

 
var channel = new Channel()	
	
//通道监听	
channel.on('type1', function (data) {	
    //处理收到的数据	
})	
//发送信息	
channel.sendMsg('type2', {	
    //数据	
})

 

最 后

 

1、快应用发信息到网页,官方文档写的信息格式是messageString,但这个在文档里并没说格式是怎样的,在官方的demo里可以看到注释的代码里格式是{message: 'xxx'}

2、网页端的日志不好显示

 

相关阅读:

一个前端攻城狮的快应用开发之路

快应用的用法和常见问题解答

快应用开发漫谈-部署与调试

快应用开发新手入门指南

 

写在最后

在去年的开发者大赛征文中,我们通过多个社区联合活动收集了很多优质文章,有入坑指南、开源项目、开发模板、常见问题总结等多个方面,这些内容为很多开发者提供了参考,感谢大家的支持和参与,今年的我们的征文活动还在继续,感兴趣的开发者可以点阅读原文查看详情哦!

 

快应用生态平台

赋能开发者 

拓展场景未来

640?wx_fmt=jpeg

快来关注我们吧

 

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值