
说实话,rtc(实时通信)领域的异常处理,是我见过最容易被低估的工作。你可能觉得,不就是 try-catch 抓一下吗?但真正做过 rtc sdk 开发的都知道,这里的异常情况远比普通业务系统复杂得多——网络波动、设备兼容、协议超时、编解码失败……任何一个环节出问题,用户那边可能就是画面卡住、声音中断,甚至直接崩溃退出。
这篇文章,我想聊聊在做声网 rtc SDK 开发这些年,总结出的一套异常处理规范。这些经验不是从书上看来的,都是一次次线上事故换来的教训。我会尽量用直白的话讲清楚,不整那些玄乎的概念。
在谈怎么处理之前,我们得先弄清楚可能会遇到什么问题。我习惯把 RTC SDK 的异常分成几大类,这样处理的时候思路更清晰。
这是 RTC 最核心也最头疼的问题。网络这东西,用户端的状况我们完全控制不了。有可能用户在公司内网,走的是企业防火墙;有可能在家里,路由器不稳定;甚至有可能在地铁里,4G 信号时断时续。
常见的网络异常包括连接超时、连接断开、证书校验失败、DNS 解析失败、跨域问题等等。这里有个坑很多人踩过:只处理了连接失败的异常,却没处理连接成功后又断开的情况。结果就是用户明明已经连上了,突然就断了,SDK 还没任何反应,用户一脸懵。

麦克风权限被拒绝、摄像头被其他程序占用、扬声器设备不存在、耳机插入检测异常……这类问题在 Windows 上尤其突出,因为 Windows 的音频设备管理实在太灵活了。
我记得有个客户反馈说,用户电脑上有三个音频设备,系统默认输出设备、用户手动选的输出设备、还有虚拟设备,SDK 如果不做设备枚举和默认设备检测,很容易出现用户明明选了耳机,声音却从扬声器出来的情况。
这一块包括视频帧采集失败、音频采样失败、编解码器初始化失败、帧数据校验错误、时间戳跳跃等等。特别是时间戳问题,很容易被忽视。正常情况下,音视频帧的时间戳应该是递增的,但如果网络出现抖动或者设备时钟有偏差,时间戳可能会乱序或者跳变,导致播放端出现杂音或者画面闪烁。
比如重复加入频道、非法 token、频道人数超限、角色权限不足等等。这类异常通常是由于调用方使用不当造成的,需要给开发者清晰的错误提示,帮助他们快速定位问题。
说了这么多异常类型,那到底该怎么处理呢?我总结了几个基本原则,这些原则是血泪教训换来的。

这点太重要了。我见过很多 SDK,为了”用户体验”,把底层错误都包装成”网络异常,请检查网络”这种万能提示。结果呢?开发者根本不知道具体哪里出了问题,只能瞎排查。
好的做法是:给开发者足够的错误信息,但同时提供清晰的错误分类。比如声网的错误码体系,会把错误分成网络错误、设备错误、媒体错误、业务错误等几大类,每类下面有具体的错误码和说明文档。开发者一看错误码,就能快速定位问题方向。
不是所有异常都需要让用户重试的。有些异常是临时性的,比如网络抖动,过一会儿可能自己就好了;有些异常是永久性的,比如设备被永久禁用,你重试一万次也没用。
对于可恢复异常,应该提供自动重试或者明确的恢复建议;对于不可恢复异常,应该给开发者明确的终止信号,让他们知道这件事没戏了,赶紧做降级处理。
这里有个判断标准:如果同一操作重试成功的概率比较大,就值得自动重试;如果重试成功的概率很低甚至为零,就别浪费用户时间了。
这条是说给我们自己看的。在写异常处理代码的时候,一定要小心别把主流程给阻塞了。比如网络检测,可能需要一个单独的线程去做,不能让主线程卡在那里等结果。
还有一点,异常处理的日志要打,但不能打太多。见过有 SDK 一旦遇到异常就疯狂刷日志,十几秒打出好几个 G,用户电脑直接卡死。这种不是异常处理,这是制造新的问题。
原则说完了,我们来看点具体的。这部分我会结合声网 SDK 的一些实践做法来讲,都是可以直接抄作业的内容。
错误码是开发者和你 SDK 沟通的语言,设计得好不好直接影响开发者的使用体验。我建议采用「分类码 + 具体码」的结构。
比如,可以这样设计:
| 错误码范围 | 分类 | 示例 |
| 1-100 | 通用错误 | 参数无效、内存不足 |
| 101-200 | 网络错误 | 连接超时、DNS 解析失败 |
| 201-300 | 设备错误 | 摄像头不可用、麦克风静音 |
| 301-400 | 媒体错误 | 编解码器初始化失败 |
| 401-500 | 业务错误 | Token 过期、重复登录 |
这样设计的好处是,开发者一看错误码就知道问题大概出在哪个方向,不用去猜去试。
另外,错误码要有明确的文档对应。每个错误码都应该有详细的说明:可能的原因是什么、建议开发者怎么做、是否有自动恢复的可能。这些信息要写在文档里,也要通过 API 暴露给开发者。
RTC SDK 里面有很多异步操作,比如加入频道、开始推流、切换角色等等。这些操作的结果都应该有明确的回调,不能让开发者自己猜到底成功了没有。
回调里面要包含足够的信息:操作是否成功、如果不成功具体的错误是什么、有没有部分成功(比如视频成功了但音频失败了)。如果有可能的话,给出一点建议性的修复方向。
还有一点,回调要在合适的时机触发。有些人喜欢把回调放在 UI 线程触发,这样开发者用起来方便,不用自己切线程;但如果回调数据量很大,可能会阻塞 UI。声网的做法是提供选项,让开发者自己选择回调的线程环境,这个思路值得参考。
对于临时性故障,自动重试是提升用户体验的好办法。但重试不是简单地写个循环等几秒再试,那样效果不好,还可能帮倒忙。
好的重试策略应该考虑这些因素:
日志是个双刃剑。打得太少,出问题没法排查;打得太多,影响性能不说,真正有用的信息反而被淹没了。
我的建议是分级日志:
特别要说的是,ERROR 日志不要滥用。有些 SDK 动不动就打个 ERROR,其实只是个小问题,开发者看到满屏 ERROR 会慌。ERROR 应该留给真正影响用户使用的情况。
聊完规范,再来说说那些年我们踩过的坑。这些坑有些是我自己踩的,有些是看到的同行踩的,分享出来给大家提个醒。
什么意思呢?比如网络断开了,你检测到了,也通知了开发者,但之后网络恢复了,你却没做任何事情。用户以为应该自动重连,结果就是一直卡在那里。
好的做法是:检测到断线后,不仅要通知开发者,还要尝试自动重连。重连成功了,要再次通知开发者,让开发者知道服务恢复了。如果重连也失败了,再给开发者一个明确的失败信号。
这个坑我见过无数次。异常处理里面做了网络请求、文件读写、或者复杂的计算,主线程直接卡死。用户看到的就是 App 无响应,然后被系统杀掉。
记住一个原则:异常处理本身不能成为新的异常来源。任何可能阻塞的操作,都要放到后台线程去做。
RTC SDK 通常要支持多个平台,Windows、macOS、iOS、Android……每个平台的异常处理方式都不一样。
比如 iOS 上,麦克风权限被拒是一个明确的错误,通过系统的回调能拿到具体原因;但在 Android 上,不同厂商的实现不一样,有些机器上你根本拿不到具体的拒绝原因,只能知道「权限有问题」。
跨平台开发的时候,异常处理这块要格外小心,不能写一套代码期望在所有平台上都工作良好。
有段时间我们团队太关注 SDK 本身的异常处理了,忽略了业务层面的异常。比如频道满了,用户加不进来,这个信息 SDK 收到了,但没给开发者清晰的提示,导致开发者以为是自己调用方式有问题。
后来我们调整了策略:所有可能导致操作失败的业务规则,都通过错误码返回给开发者,让开发者能准确判断是 SDK 的问题还是自己业务逻辑的问题。
如果你正在开发 RTC SDK,或者准备开发,我想给你几点建议。这些建议看起来简单,但真正能做到的不多。
首先是文档先行。在写代码之前,先把异常情况想清楚,列出所有可能的错误场景,给每个场景分配一个错误码,写好文档说明。避免做到一半发现漏了情况,再回头补就麻烦了。
其次是亲身体验。异常处理代码写完之后,自己要去模拟各种异常情况来测试。不要只测正常流程,异常流程同样重要甚至更重要。你要假设用户在使用过程中可能遇到的一切问题,然后验证你的代码能不能正确处理。
最后是持续收集反馈。SDK 发出去之后,收集开发者的反馈。有的异常情况是你在开发环境里根本模拟不出来的,只有用户实际用了才能发现。声网这些年迭代了很多版本,很多改进都是来自用户的反馈。
异常处理这事儿,说起来简单,做起来全是细节。但正是这些细节,决定了一个 SDK 是「能用」还是「好用」。
希望这篇文章对你有帮助。如果你在 RTC 开发中遇到什么异常处理的问题,欢迎一起交流。
