简介:本资源面向Android开发者,尤其是需要在应用中快速落地即时通讯能力的初中级工程师,提供环信SDK整合与聊天功能实现的完整工程参考。内容围绕一对一单聊、多人群聊、好友管理、群邀请、离线消息推送与异常处理等核心场景展开,覆盖从SDK依赖引入、初始化配置、登录注册到消息收发与界面展示的完整链路,适合作为项目集成时的对照模板与排错依据。资源包共7811个文件,约210.98MB,包含341个dex、2961个class、186个java源码、554个xml布局与配置、443个json数据、327个png图片及196个so库等,涵盖编译产物、资源文件与依赖组件,目录结构完整,便于按模块检索与复用。目前已有1078人学习下载,可帮助读者快速理解环信SDK的接入流程、消息管理机制与常见问题处理思路,减少从零搭建聊天模块的试错成本。
1. 从零到一:Android 整合环信 SDK 的聊天功能到底值不值得做
如果你正在做一个 Android 端需要即时通讯的项目,大概率绕不开一个选择:自研 XMPP 还是接第三方 IM SDK。自研意味着你要处理长连接保活、消息可靠投递、离线推送、多端同步、群组管理这一整套黑匣子,没个三五人月的投入根本稳不住。而环信 SDK 这类方案,核心价值就是把「消息通道」这件事封装成几个 API,让你专注在业务层。我这次拆的是一个典型的 Android 整合环信实现单聊加群聊的 Demo 工程,包含初始化、登录、会话列表、消息收发、消息漫游几个模块。适合谁?适合手上有 Android 基础、需要在两周内把聊天功能跑通、又不想在长连接上翻车的开发者。下面按实际落地顺序,把每一步的参数和坑讲透。
2. 环信 SDK 集成前的选型与工程配置:从 Gradle 依赖到混淆规则
2.1 为什么选环信而不是自研或别的方案
先说选型逻辑。即时通讯的核心难点不在 UI,而在消息的可靠性和实时性。自研方案通常基于 Netty 或 OkHttp 长连接,你得自己实现心跳、重连退避、消息去重、ACK 机制、离线存储。这些逻辑写出来不难,难的是在弱网、切后台、系统杀进程这些场景下保持稳定。环信 SDK 把这些都封装在 native 层和 service 里,对外只暴露EMClient和EMChatManager两个核心入口。
从工程角度看,环信的优势在于:消息通道和业务解耦,SDK 内部维护了 SQLite 消息库,支持消息漫游和离线拉取。代价是包体积会增加大概 3 到 5 MB,以及你需要接受它的初始化流程和回调线程模型。如果你的 App 只是偶尔发个通知,那用推送就够了;但如果是 IM 为核心功能,接 SDK 是性价比最高的路径。
2.2 Gradle 依赖与 Manifest 配置
集成第一步是引入依赖。环信 SDK 分两个部分:核心 IM SDK 和可选的推送 SDK。单聊群聊只需要核心包。
// app/build.gradle android { defaultConfig { ndk { // 环信 SDK 包含 so 库,按需保留架构,全保留会增大包体积 abiFilters "armeabi-v7a", "arm64-v8a", "x86" } } } dependencies { // 环信 IM SDK 核心包,版本号以官方最新为准 implementation 'io.hyphenate:hyphenate-sdk:4.9.0' }这里abiFilters是关键参数。环信 SDK 的 native 库支持多种 CPU 架构,如果你全放进去,APK 会多出好几 MB。常见做法是只保留armeabi-v7a和arm64-v8a,模拟器调试时再加x86。上线前记得用splits或bundle做架构分包。
Manifest 里需要声明网络权限和环信的后台服务:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application> <!-- 环信核心服务,负责长连接维护 --> <service android:name="com.hyphenate.chat.EMChatService" android:exported="true" /> <!-- 可选:消息推送服务,不接推送可去掉 --> <service android:name="com.hyphenate.chat.EMJobService" android:permission="android.permission.BIND_JOB_SERVICE" /> </application>EMChatService是环信维持长连接的核心组件,必须声明。WAKE_LOCK权限用于在息屏时保持连接,不加的话部分机型切后台几分钟就断连。注意从 Android 8.0 开始后台服务受限,环信内部用 JobScheduler 做了适配,所以EMJobService建议保留。
2.3 混淆规则:别让 SDK 被裁掉
release 包开启混淆后,环信 SDK 的反射调用和 native 方法容易被误删。必须加 keep 规则:
# 环信 SDK 混淆规则 -keep class com.hyphenate.** { *; } -keep class com.hyphenate.chat.** { *; } -dontwarn com.hyphenate.** # 保留 native 方法对应的类 -keepclasseswithmembernames class * { native <methods>; }我见过有人 release 包登录一直失败,debug 包正常,最后定位就是混淆把EMClient的反射入口裁了。血泪经验:每次开混淆后,务必在 release 包上完整走一遍登录、发消息、收消息流程。
3. 初始化与登录:EMClient 的调用时序和线程模型
3.1 初始化必须在 Application 中完成
环信 SDK 的初始化有严格的时序要求:必须在任何其他 SDK 调用之前完成,且只执行一次。标准做法是放在自定义 Application 的onCreate里。
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // 初始化参数:上下文 + 是否开启 debug 日志 EMOptions options = new EMOptions(); // 自动登录:下次启动时用上次的 token 直接登录 options.setAutoLogin(true); // 是否允许 SDK 自动将消息附件上传到环信服务器 options.setAutoTransferMessageAttachments(true); // 设置离线推送时显示的昵称 options.setPushConfig(...); // 初始化,第二个参数为 true 时输出 SDK 日志 EMClient.getInstance().init(this, options); // 开启 debug 模式,上线前关闭 EMClient.getInstance().setDebugMode(true); } }EMOptions里几个参数值得展开。setAutoLogin(true)表示 SDK 会缓存上次登录成功的 token,App 重启后自动恢复登录态,省去手动登录。setAutoTransferMessageAttachments(true)控制图片、语音等附件是否自动上传,如果你们有自己的文件服务器,可以关掉。setDebugMode上线前一定要关,否则日志会暴露敏感信息。
3.2 登录流程与回调线程
初始化完成后才能调登录。环信支持两种登录方式:账号密码登录和 token 登录。token 登录更安全,适合服务端签发凭证的场景。
// 账号密码登录 EMClient.getInstance().login(username, password, new EMCallBack() { @Override public void onSuccess() { // 登录成功,此回调在子线程 // 加载本地会话和消息 EMClient.getInstance().chatManager().loadAllConversations(); EMClient.getInstance().groupManager().loadAllGroups(); runOnUiThread(() -> { // 切换到主界面 }); } @Override public void onError(int code, String error) { // 登录失败,code 是错误码,error 是描述 // 常见:200 用户不存在,202 密码错误 } @Override public void onProgress(int progress, String status) { // 登录进度,一般不用处理 } });这里有个容易翻车的点:onSuccess回调运行在子线程,直接在里面更新 UI 会崩。我一般用runOnUiThread或 Handler 切回主线程。另外loadAllConversations()和loadAllGroups()必须在登录成功后调用,否则会话列表是空的。这两个方法是同步阻塞的,数据量大时会卡一下,建议放在子线程执行完再通知 UI。
注意:登录成功后不要重复调用
login,否则会返回「已登录」错误。如果需要切换账号,先调logout。
3.3 注册与 token 登录的差异
如果你的 App 有自己的账号体系,通常用「服务端注册环信账号 + 客户端 token 登录」的模式。服务端调 REST API 创建环信用户,返回 token 给客户端,客户端用 token 登录:
// token 登录,token 由你的服务端从环信 REST API 获取 EMClient.getInstance().loginWithToken(username, token, new EMCallBack() { @Override public void onSuccess() { // 同样需要加载会话 } // ... });token 登录的好处是客户端不接触密码,且 token 可以设置有效期。常见做法是服务端在用户登录自己的系统时,顺带签发环信 token,客户端拿到后直接登录 IM。这样账号体系是统一的,不会出现两套密码。
4. 消息收发与会话管理:从 sendMessage 到消息漫游
4.1 发送文本、图片和自定义消息
环信的消息模型是EMMessage,通过EMChatManager发送。先构造消息体,再指定会话 ID 和聊天类型。
// 创建文本消息 EMMessage message = EMMessage.createTxtSendMessage("你好,这是一条测试消息", toChatUsername); // 设置聊天类型:单聊 message.setChatType(EMMessage.ChatType.Chat); // 发送消息,第二个参数是发送回调 EMClient.getInstance().chatManager().sendMessage(message); // 发送图片消息,第一个参数是本地路径 EMMessage imgMsg = EMMessage.createImageSendMessage(imagePath, false, toChatUsername); imgMsg.setChatType(EMMessage.ChatType.Chat); EMClient.getInstance().chatManager().sendMessage(imgMsg);createImageSendMessage的第二个参数sendOriginalImage控制是否发送原图。设为false时 SDK 会压缩,默认压缩到 100KB 左右,适合聊天场景。如果要做「查看原图」功能,就传true,但要注意流量和上传时间。
自定义消息用于业务扩展,比如订单卡片、位置分享:
// 自定义消息:event 是自定义事件名,params 是内容 map EMMessage customMsg = EMMessage.createSendMessage(EMMessage.Type.CUSTOM); EMCustomMessageBody body = new EMCustomMessageBody("order_card"); body.setParams(params); customMsg.setBody(body); customMsg.setTo(toChatUsername); customMsg.setChatType(EMMessage.ChatType.Chat); EMClient.getInstance().chatManager().sendMessage(customMsg);接收自定义消息时,在EMMessageListener里判断body.getEvent()来分发处理。
4.2 消息监听与 EMMessageListener
收消息靠注册监听器,建议在登录成功后注册,退出登录时移除。
EMMessageListener msgListener = new EMMessageListener() { @Override public void onMessageReceived(List<EMMessage> messages) { // 收到新消息,可能多条 for (EMMessage msg : messages) { // 更新会话列表和未读数 // 注意:此回调在子线程 } } @Override public void onCmdMessageReceived(List<EMMessage> messages) { // 收到透传消息,不显示在会话里 } @Override public void onMessageRead(List<EMMessage> messages) { // 消息已读回执 } @Override public void onMessageDelivered(List<EMMessage> messages) { // 消息送达回执 } @Override public void onMessageRecalled(List<EMMessage> messages) { // 消息撤回 } @Override public void onMessageChanged(EMMessage message, Object change) { // 消息状态变化 } }; // 注册 EMClient.getInstance().chatManager().addMessageListener(msgListener); // 退出时移除,避免内存泄漏 EMClient.getInstance().chatManager().removeMessageListener(msgListener);onMessageReceived是核心回调,新消息都从这里来。注意它跑在子线程,更新 UI 要切线程。另外这个回调可能一次带回多条消息,别只处理第一条。
4.3 会话列表与消息漫游
会话列表通过loadAllConversations()获取,返回Map<String, EMConversation>:
Map<String, EMConversation> conversations = EMClient.getInstance().chatManager().getAllConversations(); for (EMConversation conv : conversations.values()) { // conv.conversationId() 会话 ID // conv.getUnreadMsgCount() 未读数 // conv.getLastMessage() 最后一条消息 }消息漫游是指从服务器拉取历史消息,换设备后也能看到记录。默认漫游 7 天,可以在环信控制台调整。拉取历史消息用EMConversation.loadMoreMsgFromDB或searchMsgFromDB:
// 从本地数据库拉取更早的消息,startMsgId 是当前最早消息的 ID conversation.loadMoreMsgFromDB(startMsgId, pageSize);如果本地没有,SDK 会自动从服务器同步。这里有个坑:漫游消息的拉取是异步的,且受网络影响,做「下拉加载更多」时要处理加载中和失败状态。
5. 避坑与排查:整合环信 SDK 时最容易翻车的五个点
5.1 登录一直失败但错误码不明确
现象:调login后onError返回 200 或 202,但账号密码确认没错。原因:环信账号是独立于你 App 账号体系的,如果你没在环信服务端注册过这个用户,登录必然失败。200 表示用户不存在,202 表示密码错误。解决:确认服务端是否调用了环信的注册 REST API。调试阶段可以在环信控制台手动创建测试账号。另外检查 AppKey 是否和初始化时一致,AppKey 错了也会报类似错误。
5.2 收不到消息,但发送正常
现象:能发出去,对方也能收到,但自己收不到别人发的消息。原因:最常见的是没注册EMMessageListener,或者注册了但在退出页面时被移除了。另一个可能是消息回调被切到了后台线程,UI 没刷新,看起来像没收到。解决:把监听器注册放在登录成功回调里,全局只注册一次。在onMessageReceived里打日志确认是否触发。如果触发了但 UI 没更新,检查线程切换。
5.3 切后台几分钟后连接断开
现象:App 切到后台,过几分钟再回来,消息延迟很久才到,或者要重新登录。原因:Android 系统为了省电会限制后台网络,尤其是国产 ROM。环信虽然有保活机制,但被系统杀掉后需要重连。解决:确保EMChatService和EMJobService在 Manifest 里正确声明。接入厂商推送(小米、华为、OPPO 等)作为离线消息通道。在EMOptions里开启setUseFCM或对应厂商推送。测试时用真机,别用模拟器判断保活。
5.4 混淆后 release 包崩溃或功能异常
现象:debug 包一切正常,release 包登录失败或收不到消息。原因:混淆把环信 SDK 的类或 native 方法裁掉了。解决:加上前面给的 proguard 规则,重点 keepcom.hyphenate.**。每次改混淆配置后,在 release 包上完整回归一遍核心流程。
5.5 消息重复或顺序错乱
现象:同一条消息收到两次,或者时间顺序不对。原因:消息去重没做好,或者本地消息和漫游消息合并时没按msgId去重。环信的消息有唯一的msgId,但本地发送的消息在服务器确认前是本地 ID,确认后会替换。解决:在onMessageReceived里用msgId做去重。展示消息时按getMsgTime()排序,不要依赖接收顺序。发送中的消息用getStatus()判断状态,避免把发送中的消息当成已发送。
6. 进阶技巧:消息已读回执与多端同步的落地细节
已读回执是 IM 里体验提升最明显的功能之一,但环信默认不开,需要手动发回执。发送方在消息里开启setRequireReadAck(true),接收方收到后调sendMessageReadAck:
// 发送方:标记这条消息需要已读回执 message.setRequireReadAck(true); // 接收方:进入会话后,对未读消息发送已读回执 for (EMMessage msg : unreadMessages) { EMClient.getInstance().chatManager().sendMessageReadAck(msg); }发送方在EMMessageListener.onMessageRead里收到回执,更新 UI 上的「已读」标记。注意回执本身也是一条消息,会占用流量,别对每条消息都发,通常只对最后一条未读消息发。
多端同步是指同一账号在手机、平板、网页同时登录时,消息要同步。环信默认支持多端,但需要处理「一端已读,其他端未读数要清零」的逻辑。常见做法是收到onMessageRead时,同步更新本地会话的未读数。另外EMConversation的markAllMessagesAsRead()可以清空未读,但要注意别把没看过的消息也标了。
验证整合是否成功,我一般走这个清单:登录后杀进程重开,看是否自动登录;发一条消息,看对方是否实时收到;断网发消息,看是否进入发送中状态,恢复网络后是否自动重发;换一台设备登录同一账号,看历史消息是否漫游下来。这套走完,基本能覆盖 90% 的线上问题。
从那以后我每次接 IM SDK,都会先在 release 包上把登录、收发、断网重连、多端同步这四件事各跑一遍,再动业务代码。希望帮到你。
本文还有配套的精品资源,点击获取