ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android整合环信SDK实现聊天功能:从入门到踩坑指南

Android整合环信SDK实现聊天功能:从入门到踩坑指南 简介面向Android开发者的环信SDK即时通讯集成资源包聚焦实时聊天场景围绕单聊、群聊、好友管理、群邀请等核心功能提供完整工程实现适合具备一定Android基础、希望快速接入IM能力的开发者学习参考。包内共7811个文件既有Java源码、XML布局与资源配置也有编译生成的class、dex、so原生库以及可直接安装的APK文件整体压缩包约211MB可对照文档研究SDK初始化、登录鉴权、消息收发、群组管理、离线推送和异常处理等环节。目前已有1073人学习下载。资源中包含完整项目构建产物与多类型配置文件便于追溯各模块代码实现、排查构建或运行问题也可作为后续二次开发的基础模板帮助减少从零集成的成本。项目内保留大量编译中间产物与原生库可辅助深入分析SDK运行机制并提供可直接安装的APK用于真机验证。 项目标题: Android中整合环信SDK实现聊天功能 相关热搜词Android,环信SDK,聊天功能做Android开发这几年IM功能一直是很多应用的刚需。自己做一套IM后端WebSocket长连接、消息持久化、离线推送、多端同步这些坑足够踩上几个月。所以主流的做法就是接入现成的IM云服务环信SDK算是国内用得比较多的一家。这篇博文我就拿实际项目说事从零开始把环信SDK整合进Android工程实现单聊、会话列表、消息收发这些核心功能把关键步骤、配置代码和我在真正落地时趟过的坑都整理出来。想给App快速加聊天功能或者正在调研IM方案的朋友这篇应该能帮你省下不少时间。1. 做IM功能前先把技术选型这块整明白1.1 为什么要直接用环信SDK而不是自研先聊个稍微有点“老生常谈”但必须想清楚的问题为什么接入环信而不是自己搞一套我见过不少团队一开始觉得IM无非就是发消息、收消息自己用WebSocket撸一个系统也不难结果做到后面发现——首先是长连接稳定性。移动网络环境极其恶劣App切后台、手机休眠、电梯隧道切换基站TCP连接随时可能断开。自己维护长连接需要处理心跳机制、断线重连、消息补偿这每一项都涉及海量细节。环信这类SDK底层早就把这些能力做扎实了你只需要调用connect剩下的重连机制它在SDK内部自己就处理了。然后是消息可靠性和时序问题。IM不是简单的“把数据发出去”就完事还要考虑消息到达率、ACK确认、离线消息怎么补拉、消息顺序怎么保证。如果靠服务器端硬编码一套逻辑等上线了再发现消息乱序用户骂街是小业务流失是大。最后是推送通道的适配。Android端还要处理厂商推送小米、华为、OPPO、vivo等环信SDK提供了和FCM、厂商推送的整合方案省掉自己对接一堆SDK的额外工作量。所以说白了用环信SDK核心价值不在于“省掉写代码的时间”而在于躲开IM底层那些看不见摸不着但一出问题就头大的深坑。1.2 环信SDK能覆盖哪些聊天场景环信SDK目前提供的聊天能力覆盖了单聊、群聊、聊天室三大块同时附带会话列表管理、消息历史记录拉取、消息回调通知、系统消息下发等功能。对于绝大多数App内嵌IM的需求来说这套东西已经够用。我在接这个项目时做的第一件事就是确认产品需要的IM场景边界。如果你只需要单聊那么集成工作量并不大核心就是用户体系对接 消息收发 会话列表。如果涉及群聊还需要处理群成员管理、群公告、群禁言等能力。如果连聊天室都要上那就要额外考虑聊天室的生命周期与游客模式。先把这个范围定清楚后面编码时才不会越写越乱。注意环信SDK分为旧版和新版环信IM UIKit、环信IM SDK 3.x。现在官方主推的是环信IM SDK 3.x。我下面讲的都是基于这个版本来做。2. 环境准备与SDK工程接入2.1 创建环信应用的前置工作要在Android工程里用环信SDK第一步是到环信控制台注册账号并创建一个应用拿到这个应用对应的AppKey。AppKey的格式一般是“公司标识#应用标识”比如“abcd#demo”。这个AppKey贯穿后续所有初始化逻辑相当于App在环信服务器端的“身份证号”。在创建应用时控制台会要求填包名。这里有一个必须注意的地方填写的包名必须与你的Android应用实际applicationId保持一致否则后面跑起来会出现连接失败或者推送消息无法正确下发的问题。我在第一次做的时候随手填了一个测试包名结果调试了半天报错总是提示appkey无效最后发现就是包名对不上非常耽误时间。另外现在环信新应用默认开启了REST接口鉴权如果后面要服务端调用REST API比如批量发消息、创建群组记得在控制台把AppKey对应的Client ID和Client Secret准备好这些是服务端访问环信API的凭证。2.2 Gradle依赖与AndroidManifest配置创建好应用后打开你的Android工程在项目根目录的build.gradle中配置仓库地址把环信的maven仓库加进去。环信SDK目前是托管在https://download.ringcentral.com/android/repo这个地址如果你网速不好容易拉取超时建议在Gradle里配上国内镜像源否则可能卡在依赖下载这一步。依赖具体写法如下allprojects { repositories { maven { url https://download.ringcentral.com/android/repo } google() mavenCentral() } }然后在app/build.gradle的dependencies里加入环信SDK依赖implementation com.hyphenate:hyphenate-sdk:3.9.8这里我建议不要随便把版本号写成latest或者不写版本因为环信SDK不同版本间的API差异还挺大拉到高版本后有些方法被废弃了网上很多帖子还是老写法对不上就抓瞎。锁定一个稳定版本后续升级也容易排查。接着是AndroidManifest配置。环信SDK要求添加权限INTERNET网络通信必要、ACCESS_NETWORK_STATE检查网络状态用于断线重连判断、WAKE_LOCKCPU唤醒保证消息接收、VIBRATE消息提醒震动等。权限写完后还需要注册一个EMPushHelper相关的服务和广播接收器但因为SDK版本迭代较快具体类名在不同版本中可能略有差异。最稳妥的方法是参考环信官方文档“Android集成”章节里的最新Manifest配置。经验之谈集成第三方SDK最忌讳照着老博客抄配置。环信SDK的Manifest配置经历过多次调整直接以官方文档为准不要盲目复制网络教程里面的旧代码。3. 核心代码实现初始化、登录与消息收发3.1 初始化环信SDK的正确姿势在Android的Application类里进行初始化。这里要重点强调初始化时机和线程问题。环信SDK的初始化必须在onCreate里完成而且初始化过程中涉及的内容比较多如果直接在主线程跑遇到低端机可能会卡顿。官方推荐的方式是加一个延时或者放到子线程中操作。但实际使用中我发现初始化和登录不一定要同时进行初始化可以尽快执行登录则放在用户手动触发或者业务需要时再做。public class DemoApplication extends Application { Override public void onCreate() { super.onCreate(); // 初始化环信SDK EMOptions options new EMOptions(); // 默认开启聊天记录自动同步 options.setAutoLogin(false); // 是否需要接受已读回执 options.setRequireAck(true); // 设置AppKey options.setAppKey(你的AppKey); EMClient.getInstance().init(this, options); // 可选开启日志便于调试 EMClient.getInstance().setDebugMode(BuildConfig.DEBUG); } }有几个参数值得说道说道。setAutoLogin(false)表示不自动登录这个根据业务来。如果App设计了多账号切换建议关闭自动登录如果是单一账号常驻登录可以考虑开启但要注意自动登录失败时的退登逻辑。setRequireAck(true)用于消息已读回执如果产品不要求“对方已读”功能可以关掉能省一点流量和信令开销。3.2 注册与登录开着账号体系对接思路环信SDK的注册接口是EMClient.getInstance().createAccount(username, password)。这里需要特别注意这个注册是“环信侧的账号注册”而不是App业务侧的注册。实际项目里建议把业务服务器的账号体系和环信账号做绑定映射。也就是说用户在你们产品里注册成功后再用同一套用户名密码或单独的环信IM账号到环信注册最后在你们服务端保存业务账号与环信账号的映射关系。对于这一步我的建议是不要在客户端用createAccount去直接创建账号而应该让服务端调用环信REST API来创建IM账号然后把密码下发到客户端。原因很简单createAccount接口如果暴露给用户容易被恶意刷号安全层面有隐患。服务端创建完成后客户端只保留“登录”这个动作。登录就简单了EMClient.getInstance().login(username, password, new EMCallBack() { Override public void onSuccess() { // 登录成功跳转或加载会话列表 } Override public void onError(int code, String error) { // 登录失败根据code区分原因 } Override public void onProgress(int progress, String status) { // 登录进度 } });如果登录失败常见错误码包括EMError.USER_AUTHENTICATION_FAILED用户名密码错误、EMError.USER_NOT_FOUND用户不存在等。建议把错误码映射成用户看得懂的友好提示不要直接抛Raw error code。3.3 消息发送与接收从代码层面搞懂消息结构环信SDK里一条消息由一个EMMessage对象表示。消息有from、to、chatType单聊/群聊/聊天室、body消息体等关键属性。我们最常用的文本消息构造方式如下EMMessage message EMMessage.createTxtSendMessage(content, toChatUsername); // 设置消息类型为单聊 message.setChatType(EMMessage.ChatType.Chat); // 发送 EMClient.getInstance().chatManager().sendMessage(message);对刚接触IM的朋友来说有一个点需要特别理解消息发送成功与否不是看sendMessage方法是否异常退出而是要通过回调里判断。sendMessage只是把消息提交到发送队列真正的网络发送结果在EMCallBack里通知message.setMessageStatusCallback(new EMCallBack() { Override public void onSuccess() { // 消息发送成功 } Override public void onError(int code, String error) { // 消息发送失败可能是网络原因、对方不存在等 } Override public void onProgress(int progress, String status) { // 附件消息会回调进度文本消息一般不走这里 } });接收消息则需要注册消息监听器EMMessageListener。在进入聊天界面时注册退出时移除避免内存泄漏。EMMessageListener msgListener new EMMessageListener() { Override public void onMessageReceived(ListEMMessage messages) { // 收到消息通常在子线程回调 // 需要post到UI线程更新界面 } }; EMClient.getInstance().chatManager().addMessageListener(msgListener);这里有个高概率踩坑的点消息回调默认在子线程执行。我见过不少新手写完onMessageReceived后直接更新UI结果崩在Only the original thread that created a view hierarchy can touch its views。所以一定要切回主线程再更新界面。3.4 会话列表显示最近聊天记录会话列表是聊天功能里看起来简单、但实际处理起来比消息收发更繁琐的模块。环信SDK通过getAllConversations()获取所有会话返回一个MapString, EMConversation。Map的key是会话对方ID单聊或群组ID群聊。拿到会话对象后可以用conversation.getLastMessage()获取会话最后一条消息用conversation.getUnreadMsgCount()获取未读数量。真实的会话列表页需要同时处理多类型会话、按消息时间排序、展示未读角标还要处理头像昵称映射。头像昵称属于业务数据环信SDK本身不维护用户资料所以一般是在客户端维护一个Mapusername, UserInfo或者从服务端获取后缓存到数据库。我通常会在本地建一个ConversationEntity类把环信的EMConversation和业务数据封装一层方便排序和展示。否则直接拿Map来排序写着写着逻辑就绕晕了。4. 聊天界面与消息适配器设计4.1 RecyclerView实现聊天列表的细节聊天界面几乎都是基于RecyclerView 多类型Item实现。因为消息类型不止文本还有图片、语音、视频、文件等。每种类型对应一个Item布局类型适配器需要重写getItemViewType来判断消息类型。这里有一个我强烈建议的习惯消息列表的Item类型用常量而不是魔法数字。比如文本0、图片1、语音2、时间标签3如果直接写在getItemViewType里后期加一个“撤回消息”类型就会改得很痛苦。消息UI中最容易忽略的是消息时间的显示逻辑。不是每条消息都要显示时间一般会做一个时间比较如果当前消息和上一条消息的时间间隔超过5分钟或者在不同日期才显示时间标签。这个时间标签可以作为一种特殊的Item类型处理也可以作为同一个Item的header。我更推荐单独作为Item类型可以让时间居中对齐样式也更好控制。还有一个体验细节发送消息后消息气泡应该自动滚到底部收到新消息且列表正处于底部时应联动滚动如果用户已经往上翻看历史消息了就不要强制滚动否则体验很差。判断是否在底部可以通过layoutManager.findLastVisibleItemPosition()来确认再决定是否执行smoothScrollToPosition。4.2 图片消息与附件消息的处理要点图片消息不能简单地把URL塞进EMMessage里就完事。环信的图片消息默认会上传缩略图发送方需要先构造EMImageMessageBody。EMImageMessageBody body new EMImageMessageBody(imageFile, thumbnailFile); EMMessage message EMMessage.createSendMessage(EMMessage.Type.IMAGE); message.addBody(body);这里有个重点缩略图文件最好提前生成好因为SDK如果发现缩略图缺失会自动生成但在部分机型上生成缩略图可能会失败导致发送后对方无法显示预览图。我处理这个问题时会在选择图片后利用ThumbnailUtils或者Glide的asBitmap提前压缩出一张宽高不超过200px的缩略图再传给SDK。图片消息展示时接收方的EMImageMessageBody.getRemoteUrl()是原图地址thumbnailUrl是缩略图地址。列表页展示应该先加载缩略图点击查看大图再加载原图这样既省流量又保证滑动流畅。4.3 本地数据库缓存与消息分页加载每次进入聊天界面都去环信服务器拉取所有历史消息显然不现实。环信SDK本身在本地做了一个消息缓存库你可以通过EMConversation对象来从本地加载历史消息EMConversation conversation EMClient.getInstance().chatManager().getConversation(username); ListEMMessage messages conversation.loadMoreMsgFromDB(startMsgId, pageSize);这里需要注意loadMoreMsgFromDB返回的消息列表是按时间倒序的也就是说返回的是从startMsgId往前翻的pageSize条消息。如果你想要正序显示就得在Adapter里reverse一下。这个“顺序坑”比较经典我在第一次实现分页加载时就在这上面调了半天。更稳妥的方案是进入聊天页时先从本地数据库中加载最近20条消息向上滚动到底部时再调用loadMoreMsgFromDB加载更早的消息。因为本地缓存已经在SDK内部实现了增量同步所以稳定性比每次都调REST API强很多。5. 实操中的高频问题与排错手册5.1 消息发送失败先看错误码再动手消息发送失败的原因非常多但我总结了几个高频场景。最常遇到的是EMError.NETWORK_ERROR这时候一般不是代码问题而是网络通道还没建立好。如果刚启动App就立刻发消息SDK的长连接还没起来发送就会失败。处理方式是监听连接状态回调EMConnectionListener等连接成功后再恢复发送。其次是EMError.MESSAGE_INCLUDE_ILLEGAL_CONTENT翻译过来就是消息里包含非法字符。这个通常在服务端做了敏感词过滤后会返回。遇到这种问题客户端没法绕过去只能提示用户修改内容。还有一种情况比较隐晦发送图片时提示EMError.FILE_NOT_FOUND。这很可能是图片文件被相册清理了或者上传路径写错了。判断方法是打印body.getLocalUrl()看看文件是否真实存在。如果App测试时用了一个刚生成的文件对象路径又没存对就会出现这种问题。5.2 连接不稳定与自动重连机制环信SDK内置了自动重连机制但有个前提必须正确配置Manifest中的Service和Receiver并保证App进程不被用户手动杀掉。在小米、华为这些国内ROM上一键清理后台会把进程整个杀掉SDK的重连机制自然也就失效了。这里想分享一个实用处理方式只在前台聊天场景要求高实时性App退到后台后不必强行保活而是通过厂商推送通道下发通知。这样既符合国内ROM对后台进程的限制也避免因为常驻服务被用户反感而给应用带来差评。环信官方也提供了整合厂商推送的接入方案按文档走一遍即可。如果App处于前台时发现连接断开可以监听EMConnectionListener在onDisconnected回调中提示用户“网络连接断开正在重连中”在onConnected回调中隐藏提示。仅在App退到后台时做前台通知提醒而不是频繁唤醒网络请求。5.3 未读消息数对不上多半是会话同步问题未读消息数不对是接入IM比较容易遇到的排查点。最常见的原因是登录后没有主动拉取服务端离线消息。环信SDK在登录成功后会默认同步最近消息列表但如果你在初始化时设置了setAutoLogin(false)且登录之后直接查询本地会话可能出现会话列表为空或者未读数不准确的情况。解决方法是登录成功后主动调用一下EMClient.getInstance().chatManager().fetchConversationsFromServer(new EMValueCallBackMapString, EMConversation() { Override public void onSuccess(MapString, EMConversation value) { // 拿到服务端会话和本地合并 } Override public void onError(int error, String errorMsg) { // 拉取失败可重试 } });拉取到服务端会话后用conversation.getUnreadMsgCount()获取未读数再和本地已有的会话列表做合并处理避免重复添加。另一个容易忽略的点进入会话A读了消息后未读数清零是本地操作但如果你退出App重装未读数会从服务端再次同步下来。所以产品要考虑是否需要“服务端已读回执”来同步多端的未读状态。环信SDK的markAllMessagesAsRead只是本地标记不同端之间的已读同步需要借助已读回执或服务端逻辑。5.4 常见报错速查表报错或现象常见原因处理建议SDK初始化后登录时提示 应用未注册AppKey填写错误或包名与控制台不一致核对AppKey和AndroidManifest里的包名消息一直显示发送中网络未连接或长连接未建立检查网络状态监听EMConnectionListener登录后收不到离线消息未开启聊天记录同步初始化时开启setAutoLogin(true)或登录后手动拉取发送图片失败图片文件路径失效或太大压缩后重新选择发送检查本地文件是否存在回调里更新UI崩溃环信回调默认在子线程使用runOnUiThread或Handler切到主线程会话列表重复本地与服务器数据重复用conversationId做唯一键去重6. 一点建议IM功能开发把边界理清楚再动手最后再聊一点我的个人体会。整合环信SDK做聊天功能技术难度其实不在“SDK怎么调”而在于产品边界和业务数据梳理。像我前面反复强调的客户端该做的核心事情就是初始化、登录、消息收发、会话展示。而账号体系对接、敏感词过滤、历史消息云端存储天数、多端同步策略这些最好在动工前就和产品经理、服务端商量清楚。IM一旦上线消息数据结构变更的成本非常高前期多花一天理清需求后期能省下一周填坑。另外团队有精力的话可以尽早把“消息的本地缓存管理”纳入规划。虽然环信SDK自带本地数据库缓存但它更多是作为SDK内部缓存存在当App卸载重装、换机登录时这些本地缓存就全部丢失了。如果产品对聊天记录的持久性有要求还是需要自己再做一层业务层的消息记录归档。否则换机之后聊天记录是空的用户会非常困惑。这也是我每次接IM需求时都会提前提醒团队的点别把云服务商的SDK当成万能的数据库IM SDK帮你解决了长连接和实时收发但业务层的数据设计才是真正决定体验上限的地方。本文还有配套的精品资源点击获取
返回列表