本文系统地解析了环信 IM Demo 中用户头像和昵称的完整实现逻辑,涵盖了从UI显示到数据存储和同步的完整流程。接下来我们逐一拆解每个环节的实现细节,带您全面了解环信Demo中的用户信息管理机制。
环信 IM Demo 下载:https://www.easemob.com/download/demo
源码地址:https://doc.easemob.com/product/demo.html
1. 展示层(UI层)
以下均以Android端示例代码(kotlin)为例
1.1 聊天界面如何获取用户信息
聊天界面通过以下调用链获取用户信息:
关键代码:
// DemoDataModel.ktfun getUser(userId: String?): DemoUser? {
if (userId.isNullOrEmpty()) return null
if (contactList.containsKey(userId)) {
return contactList[userId]
}
return getUserDao().getUser(userId)}2. 拉取层(Repository层)
2.1 用户资料拉取调用链
关键代码:
// ProfileInfoViewModel.ktfun fetchUserInfoAttribute(userIds: List<String>, attributes: List<ChatUserInfoType>) =
flow {
emit(mRepository.getUserInfoAttribute(userIds, attributes))
}// ProfileInfoRepository.ktsuspend fun getUserInfoAttribute(userIds: List<String>, attributes: List<ChatUserInfoType>): Map<String, ChatUserInfo> =
withContext(Dispatchers.IO) {
ChatClient.getInstance().userInfoManager().fetUserInfo(userIds,attributes)
}3. 存储层(本地数据库+缓存)
3.1 存入本地数据库和缓存
调用链:
关键代码:
// DemoDataModel.ktfun insertUser(user: ChatUIKitProfile, isInsertDb: Boolean = true) {
if (isInsertDb){
getUserDao().insertUser(user.parseToDbBean())
}
contactList[user.id] = user.parseToDbBean()}4. UI刷新层
4.1 通知UI刷新
调用链:
关键代码:
// DemoDataModel.ktif (users.isNotEmpty()){
ChatUIKitClient.updateUsersInfo(users)}5. 用户主动修改头像/昵称
5.1 修改昵称
调用链:
关键代码:
// ProfileInfoViewModel.ktfun updateUserNickName(nickName:String) =
flow {
emit(mRepository.updateNickname(nickName))
}// ProfileInfoRepository.ktsuspend fun updateNickname(nickname: String) =
withContext(Dispatchers.IO) {
ChatClient.getInstance().userInfoManager().updateOwnAttribute(ChatUserInfoType.NICKNAME, nickname)
}5.2 修改头像
调用链:
关键代码:
// ProfileInfoViewModel.ktfun uploadAvatar(filePath: String?) =
flow {
emit(mRepository.uploadAvatar(filePath))
}.flatMapConcat { result ->
ChatUIKitClient.getCurrentUser()?.let {
it.avatar = result
DemoHelper.getInstance().getDataModel().insertUser(it)
ChatUIKitClient.updateCurrentUser(it)
}
flow {
emit(mRepository.uploadAvatarToChatServer(result))
}
}6. 本地数据结构
DemoUser(数据库表结构)
@Entitydata class DemoUser(
@PrimaryKey val userId: String,
val name: String?,
val avatar: String?,
val remark: String? = null,
@ColumnInfo(name = "update_times")
var updateTimes: Int = 0)
DemoUserDao(数据库操作)
@Daointerface DemoUserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: DemoUser)
@Query("SELECT * FROM DemoUser WHERE userId = :userId")
fun getUser(userId: String): DemoUser?
@Query("SELECT * FROM DemoUser")
fun getAll(): List<DemoUser>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(users: List<DemoUser>)
@Update
fun updateUser(user: DemoUser)
@Query("UPDATE DemoUser SET name = :name, avatar = :avatar, remark = :remark WHERE userId = :userId")
fun updateUser(userId: String, name: String, avatar: String, remark: String)
@Query("UPDATE DemoUser SET update_times = update_times + 1 WHERE userId IN (:userIds)")
fun updateUsersTimes(userIds: List<String>)
@Query("UPDATE DemoUser SET update_times = 0")
fun resetUsersTimes()
@Delete
fun deleteUser(user: DemoUser)
@Query("DELETE FROM DemoUser WHERE userId = :userId")
fun deleteUserById(userId: String)
@Query("DELETE FROM DemoUser WHERE userId IN (:userIds)")
fun deleteUsersByIds(userIds: List<String>)}7. 用户资料同步机制
7.1 Profile同步
应用提供了用户资料同步机制,确保本地数据与服务器数据保持一致:
关键代码:
// ProfileInfoViewModel.ktfun synchronizeProfile(isSyncFromServer:Boolean = false) =
flow {
emit(mRepository.synchronizeProfile(isSyncFromServer))
}// ProfileInfoRepository.ktsuspend fun synchronizeProfile(isSyncFromServer:Boolean):ChatUIKitProfile? =
withContext(Dispatchers.IO) {
val currentProfile = ChatUIKitClient.getCurrentUser()?:ChatUIKitProfile(ChatClient.getInstance().currentUser)
val user = DemoHelper.getInstance().getDataModel().getUser(currentProfile.id)
suspendCoroutine { continuation ->
if (user == null || isSyncFromServer){
// 从服务器获取用户信息
currentProfile.let { profile->
val ids = mutableListOf(profile.id)
val type = mutableListOf(ChatUserInfoType.NICKNAME,ChatUserInfoType.AVATAR_URL)
// ... 获取用户信息逻辑
}
}else{
// 使用本地用户信息
currentProfile.let {
it.name = user.name
it.avatar = user.avatar
ChatUIKitClient.updateUsersInfo(mutableListOf(it))
}
continuation.resume(currentProfile)
}
}
}7.2 缓存更新
应用提供了更新用户缓存的机制:
关键代码:
// DemoDataModel.ktfun updateUserCache(userId: String?) {
if (userId.isNullOrEmpty()) {
return
}
val user = contactList[userId]?.parse() ?: return
ChatClient.getInstance().contactManager().fetchContactFromLocal(userId)?.remark?.let { remark ->
user.remark = remark }
ChatUIKitClient.updateUsersInfo(mutableListOf(user))}8. 完整调用链总结
UI展示 → DemoHelper.getInstance().getDataModel().getUser(userId) → 本地有则直接展示
本地无数据 → ProfileInfoViewModel.fetchUserInfoAttribute → ProfileInfoRepository.getUserInfoAttribute → 环信SDK拉取
拉取成功 → DemoDataModel.insertUser → ChatUIKitClient.updateUsersInfo → UI自动刷新
用户主动修改 → ProfileInfoViewModel.updateUserNickName/uploadAvatar → ProfileInfoRepository → 环信SDK → 本地更新 → UI刷新
用户资料同步 → ProfileInfoViewModel.synchronizeProfile → ProfileInfoRepository.synchronizeProfile → 服务器/本地数据 → 本地更新 → UI刷新
9. ChatContactCheckActivity获取头像昵称后的更新逻辑详解
9.1 拉取用户信息
在 initData() 中,调用 ProfileInfoViewModel.fetchUserInfoAttribute 拉取用户的昵称和头像:
lifecycleScope.launch {
user?.let { user->
model.fetchUserInfoAttribute(listOf(user.userId), listOf(ChatUserInfoType.NICKNAME, ChatUserInfoType.AVATAR_URL))
.catchChatException { ... }
.collect {
it[user.userId]?.parseToDbBean()?.let {u->
u.parse().apply {
remark = ChatClient.getInstance().contactManager().fetchContactFromLocal(id)?.remark
ChatUIKitClient.updateUsersInfo(mutableListOf(this))
DemoHelper.getInstance().getDataModel().insertUser(this)
}
updateUserInfo()
notifyUpdateRemarkEvent()
}
}
}}解析说明:
拉取到的用户信息(昵称、头像)转为本地数据库实体
insertUser(this):存入本地数据库和内存缓存
updateUsersInfo(mutableListOf(this)):通知UIKit刷新用户缓存
updateUserInfo():刷新当前页面UI
notifyUpdateRemarkEvent():通过FlowBus广播用户资料变更事件
9.2 刷新UI
private fun updateUserInfo() {
DemoHelper.getInstance().getDataModel().getUser(user?.userId)?.let {
val ph = AppCompatResources.getDrawable(this, R.drawable.uikit_default_avatar)
val ep = AppCompatResources.getDrawable(this, R.drawable.uikit_default_avatar)
binding.ivAvatar.load(it.parse().avatar ?: ph) {
placeholder(ph)
error(ep)
}
binding.tvName.text = it.name?.ifEmpty { it.userId } ?: it.userId }}解析说明:
9.3 广播用户资料变更事件
private fun notifyUpdateRemarkEvent() {
DemoHelper.getInstance().getDataModel().updateUserCache(user?.userId)
ChatUIKitFlowBus.with<ChatUIKitEvent>(ChatUIKitEvent.EVENT.UPDATE + ChatUIKitEvent.TYPE.CONTACT + DemoConstant.EVENT_UPDATE_USER_SUFFIX)
.post(lifecycleScope, ChatUIKitEvent(DemoConstant.EVENT_UPDATE_USER_SUFFIX, ChatUIKitEvent.TYPE.CONTACT, user?.userId))}解析说明:
9.4 调用链总结
1.拉取用户资料(头像/昵称)→ 存本地 → 刷新UIKit缓存 → 刷新当前页面UI → 广播变更事件
2.其他界面监听到事件后,也会自动刷新对应用户的头像昵称
环信提供 Android、iOS、Web、小程序、uniapp、Flutter 和 React Native 平台的 Demo,源码开源,开箱即用。
如果您计划开发带聊天功能的应用,环信开源的IM Demo可以帮您不必从零构建IM模块。它提供了可运行的参考,比阅读文档更高效,且实现了IM最核心的功能,比如单聊,群聊,会话列表,通讯录等,您可以参考示例代码或基于 Demo进行二次开发,大大提高了开发效率,使开发精力更集中在打造自己应用的独特价值上。
如果您在使用环信Demo中遇到问题,欢迎联系环信专业的技术支持解决。