探索环信IM-集成uni-app、H5、小程序类SDK断线重连的较好实践。(有用请点赞、评论、收藏)

2,441 阅读8分钟

探索环信IMuni-app(小程序)在集成中遇到的断联问题,如何保持较为稳定的websocket连接?

BB在前

说一下写这个文章的契机吧,目前在自己负责的项目中尤其在移动端H5,以及uni-app开发小程序项目当中较为经常会遇到,登录环信IM之后长连接断开问题,主要出现的场景也较为集中,下面列举一下我复现出现的场景,附带有一些我的解决场景,不能保证百分百的解决大家的问题,不求有功,但求有用。

常见复现的链接断开场景

异常场景一

用户长时间息屏,此类操作较为高频复现,操作步骤就是open环信IM,然后进入聊天界面,发送几条消息后直接熄灭手机屏幕长时间不再进行开屏操作(这个长时间怎么理解?五分钟十分钟甚至三十分钟都可能),然后重新打开评估直接进行消息发送,如果你使用的是4.x SDK 这时候发送消息直接会出现type 39 not login类报错,如果不是是3.x之类的SDK可能会发送不产生success消息回调,无任何反应。【高复现】

异常场景二

用户原来在聊天界面进行消息发送,退出聊天界面到后台进行挂起,然后就手机撂那了,对就是直接撂那了🤔,经过了一段时间等待用户又回到了聊天界面进行聊天,偶现消息发送无反应等待1、2秒自动进行了连接。【偶现】

异常场景三

用户退出聊天界面,开始进行其他应用内的操作,下班了,准备刷会B站,随手点开了微信,在微信里面回了一个小时的工作信息。(艹、血压开始高了),然后回到聊天界面发送消息出现没有反应。【长时间切出高复现】

异常场景四

长时间挂起中间可能出现了网络波动,自动进行了网络切换发现继续消息发送没有反应。【偶现】

异常场景五

用户发现4G信号不太好用,手动连接了一下WIFI,结果发现WIFI信号更差,又切了回来,结果发现这几通操作之后发送消息失败或者不触发消息成功回调。【较高复现】

解决时主要用到的方法&API

  1. 环信SDK当中提供的几种判断是否正在连接的API
/* 主动调用类 **/
WebIM.conn.open() //打开IM登录
WebIM.conn.close() //关闭IM连接
WebIM.conn.isOpened() //boolean false未连接 true连接
WebIM.logOut //boolean false在登录 true已退出
/* 被动触发类 **/
//消息监听,两种类型写法一种为3.x 一种为4.x
WebIM.conn.listen({
    onOpened: function () {}, //连接成功回调 
    onClosed: function () {}, //连接关闭回调
})
WebIM.conn.addEventHandler("handlerId", {
  onConnected: () => {
    console.log("onConnected");
  },
  onDisconnected: () => {
    console.log("onDisconnected");
  }
  //实验性监听(最新版本4.1.3以上支持),SDK触发重连会回调该方法。
  //可在该方法内提供重连中提示。
  onReconnecting: () => {
      console.log("onReconnecting");
  }
  )
  1. 【以uni-app为例】生命周期钩子函数
<script>
    export default {
		onShow: function() {
			
		},
		onHide: function() {
			
		}
	}
</script>
  1. 【以uni-app为例】网络状态变更监听API
uni.onNetworkStatusChange()

上述场景优化的方式方法

优先推荐方式

使用SDK内置onReconnecting 回调监听,等待触发之后增加toast提示”重连中“,此时SDK将会自动进行重连,重连失败则会触发onDisconnected回调监听,可以在onDisconnected中判断,如果业务登录状态(可以理解自己本身的业务会有一个登录状态loginstatus)仍然为true,但是SDK已经断开则说明需要重新登录,此时我们可以手动调用conn.open进行补偿登陆。

示例代码如下

//IM 连接监听回调
  const connectListenFunc = {
    onConnected: () => {
      console.log('connected...');
    },
    onDisconnected: () => {
      //断开回调触发后,如果业务登录状态为true则说明异常断开需要重新登录
      if (!loginStore.loginStatus) {
        uni.showToast({
          title: '退出登录',
          icon: 'none',
          duration: 2000,
        });
        uni.redirectTo({
          url: '../login/login',
        });
        EMClient.close();
      } else {
        //执行通过token,重新登录
        const loginUserId = uni.getStorageSync('myUsername');
        const loginUserToken =
          loginUserId && uni.getStorageSync(`EM_${loginUserId}_TOKEN`);
        EMClient.open({ user: loginUserId, accessToken: loginUserToken.token });
      }
    },
    onReconnecting: () => {
      uni.showToast({
        title: 'IM 重连中...',
        icon: 'none',
      });
    },
  };
  EMClient.addEventHandler(
    listenerEventName || HANDLER_EVENT_NAME.CONNECT_EVENT,
    connectListenFunc
  );

关于场景一、二、三的优化探索:

回顾一下场景一、二、三主要面临的问题是,用户在进入聊天页面操作后息屏了手机可能去忙其他的事情,或者进入到其他应用,后台挂起。

过了很长时间才再次恢复操作手机,进入到IM聊天页面,并且很有可能开屏直接就展示了聊天界面并且进行继续聊天用户用户在整个操作流程中是无感知的并且应该是无感知的。 这里分享一个在探索无感知重连过程中,实际使用并且有效的解决方案,以及代码片段。

  1. IM登录后全局保留登录状态,可以存储在globdata或者Vuex之类的全局状态管理方法里。
  2. 在App.vue的根组件中增加onShow生命周期钩子函数,或者在IM相关页面中增加onShow钩子函数,主要目的想必大家也已经猜到了,每次用户开屏或者进入到页面应用中都会触发onShow钩子函数,在此钩子函数中获取当前应用的登录状态,并且调用SDK内部提供的获取SDK本地连接状态的api去进行判断是否需要手动补偿登录,话不多说上代码。
<script>
   export default {
       globalData: {
           isLoginIM: false
       },
       onShow() {
   	console.log('>>>>>this.isLoginIM', this.globalData.isLoginIM)
        //判断的逻辑是如果全局已经是登录状态,但是获取当前SDK本地状态却为false未连接那么需要进行手动补偿重新登录。
   	if (this.globalData.isLoginIM && !uni.WebIM.conn.isOpened()) {
   		console.log('执行重连逻辑')
   		uni.WebIM.conn.close && uni.WebIM.conn.close();
                       //增加延时目的是为了,确保连接完全断开再去执行重新openIM操作
                       setTimeout(()=>{
                           this.loginIM()
                       },500)
   		
   	} else {
   		console.log('不需要执行重新登陆')
   	}
   },
   }
</script>

关于场景四、五的优化探索:

场景四、五的点其实主要还是在弱网切换或者网络波动的情况下出现的连接层面问题,网络的稳定其实是较为不可控的,我们没有办法保证终端用户时刻保持在一个较为良好稳定的网络环境下,默认情况下,环信SDK是有重连逻辑在里面的,默认重连次数为5次,触发时机也基本为网络切换或者网络完全断开,重连结束之后就没有办法再进一步进行重连了。

下面探讨的优化是如果觉得切换网络后SDK的重连速度不能满足需求(其实在4.1.2后续的SDK版本中切网重连速度进行了优化,基本满足了实际需求,老的版本确实发现有切网连接较慢问题。),没有在第一时间就介入进行重连,所以通过监听网络层面的变化手动将其断开再进行IM连接的形式进行重连。

<script>
    exprot default {
        onLoad(){
            //通过uni提供的网络状态变化监听,主动监听网络进行了变化就进行断开手动进行连接。
            uni.onNetworkStatusChange((info) => {
                 console.log('>>>>>>>>>>>>>网络变化', info);
                 uni.showToast({
                   icon: 'none',
                   title: '网络变化',
                 });

                 uni.WebIM.conn.close();
                 console.log('>>>>>重新连接', this.login);
                 //加延时是断开是异步操作,有可能还未断开就进行了登录,此时登录是无效的。
                 setTimeout(() => {
                   this.login();
                 }, 500);
               });
        }
    }
</script>

极端补偿连接的手段

这种方案在非常极端的情况下考虑使用,在App.vue中增加onShow,onHide钩子函数(加在App.vue根组件是因为全局退出进入必然会触发这个组件,不用担心在某个页面没加载的时候不触发的情况),应用息屏切出,都必然会触发onHide钩子函数,在这一步直接选择断开与环信的链接,不用担心断开后又收到消息的问题,因为离线后收到的消息是会存储在环信的离线服务器当中的,再次登录后会再次进行投递的,恢复页面后会触发onShow再次进行IM连接这样,就规避调用切出切入连接不稳定的问题,这种方案可以用但是不推荐,因为断开重连太过于频繁属于比较重的操作,并且据观察,不同机型上,可能选择发送图片从系统中选择文件都会触发全局的onHide,这种断开肯定是我们不希望的,而且通常上述方案就已经能够满足使用。

<script>
    export default {
		globalData:{
                    isChangeConnect:true //这个是因为在安卓上选择相册也会触发onHide onShow,所以增加个状态在选择发图片的时候更改,让其不触发断开重连。
		},
		onLaunch: function() {
			console.log('App Launch')
			this.listenIM()
		},
		onShow: function() {
			console.log('App Show',this.globalData.isChangeConnect)
			if (this.globalData.isChangeConnect) {
				this.loginIM()
			}
		},
		onHide: function() {
			console.log('App Hide',this.globalData.isChangeConnect)
			if (this.globalData.isChangeConnect) {
				this.closeIM()
			}
		},
</script>

BB在后

上面是我遇到的一些场景优化手段,为什么是探索呢?因为有些点也是在进行尝试的手段,不一定是最佳实践,也不一定必然能够解决全部场景的问题,我看这个还能重新再编辑,后续如果有更高的形式,或者方案再加进去,或者在评论区与大家共同探讨,如果觉得这篇小破文对你有帮助,留个赞吧。