详细解析环信 IM Demo 头像昵称处理流程

本文系统地解析了环信 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 聊天界面如何获取用户信息

聊天界面通过以下调用链获取用户信息:

  • 聊天Adapter → DemoHelper.getInstance().getDataModel().getUser(userId)
  • 如果本地有数据,直接使用本地数据
  • 如果没有,触发拉取(如 ProfileInfoViewModel.fetchUserInfoAttribute

关键代码:

// DemoDataModel.kt
fun getUser(userId: String?): DemoUser? {
    if (userId.isNullOrEmpty()) return null
    if (contactList.containsKey(userId)) {
        return contactList[userId]
    }
    return getUserDao().getUser(userId)
}
  • contactList 为内存缓存,getUserDao() 为Room数据库。

2. 拉取层(Repository层)

2.1 用户资料拉取调用链

  • ProfileInfoViewModel.fetchUserInfoAttributeProfileInfoRepository.getUserInfoAttribute
  • 调用环信SDK的 userInfoManager().fetUserInfo 接口

关键代码:

// ProfileInfoViewModel.kt
fun fetchUserInfoAttribute(userIds: List<String>, attributes: List<ChatUserInfoType>) =
    flow {
        emit(mRepository.getUserInfoAttribute(userIds, attributes))
    }

// ProfileInfoRepository.kt
suspend fun getUserInfoAttribute(userIds: List<String>, attributes: List<ChatUserInfoType>): Map<String, ChatUserInfo> =
    withContext(Dispatchers.IO) {
        ChatClient.getInstance().userInfoManager().fetUserInfo(userIds,attributes)
    }
  • 拉取成功后,ViewModel会将数据存入本地。

3. 存储层(本地数据库+缓存)

3.1 存入本地数据库和缓存

调用链:

  • 拉取到的 ChatUserInfo → 转为 ChatUIKitProfileDemoDataModel.insertUser

关键代码:

// DemoDataModel.kt
fun insertUser(user: ChatUIKitProfile, isInsertDb: Boolean = true) {
    if (isInsertDb){
        getUserDao().insertUser(user.parseToDbBean())
    }
    contactList[user.id] = user.parseToDbBean()
}
  • parseToDbBean() 将Profile转为数据库实体。

4. UI刷新层

4.1 通知UI刷新

调用链:

  • 存入本地后 → ChatUIKitClient.updateUsersInfo → 通知UIKit刷新
  • Adapter/Fragment监听数据变化,自动刷新头像昵称

关键代码:

// DemoDataModel.kt
if (users.isNotEmpty()){
    ChatUIKitClient.updateUsersInfo(users)
}
  • 这样UI层会自动感知到数据变化。

5. 用户主动修改头像/昵称

5.1 修改昵称

调用链:

  • UI点击修改 → ProfileInfoViewModel.updateUserNickNameProfileInfoRepository.updateNickname → 环信SDK
  • 成功后本地更新并刷新UI

关键代码:

// ProfileInfoViewModel.kt
fun updateUserNickName(nickName:String) =
    flow {
        emit(mRepository.updateNickname(nickName))
    }

// ProfileInfoRepository.kt
suspend fun updateNickname(nickname: String) =
    withContext(Dispatchers.IO) {
        ChatClient.getInstance().userInfoManager().updateOwnAttribute(ChatUserInfoType.NICKNAME, nickname)
    }

5.2 修改头像

调用链:

  • UI选择图片 → ProfileInfoViewModel.uploadAvatarProfileInfoRepository.uploadAvatar(上传到App服务器)→ uploadAvatarToChatServer(同步到环信)
  • 本地更新并刷新UI

关键代码:

// ProfileInfoViewModel.kt
fun 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(数据库表结构)

@Entity
data 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(数据库操作)

@Dao
interface 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.kt
fun synchronizeProfile(isSyncFromServer:Boolean = false) =
    flow {
        emit(mRepository.synchronizeProfile(isSyncFromServer))
    }

// ProfileInfoRepository.kt
suspend 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.kt
fun 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
    }
}

解析说明:

  • 通过本地缓存获取最新的用户信息
  • 用coil.load异步加载头像,设置占位图和错误图
  • 昵称为空时显示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))
}

解析说明:

  • updateUserCache会触发UIKit的用户缓存刷新
  • 通过FlowBus广播用户资料变更事件,其他界面(如聊天页、会话列表)可监听到并刷新

9.4 调用链总结

1.拉取用户资料(头像/昵称)→ 存本地 → 刷新UIKit缓存 → 刷新当前页面UI → 广播变更事件
2.其他界面监听到事件后,也会自动刷新对应用户的头像昵称

环信提供 Android、iOS、Web、小程序、uniapp、Flutter 和 React Native 平台的 Demo,源码开源,开箱即用。

如果您计划开发带聊天功能的应用,环信开源的IM Demo可以帮您不必从零构建IM模块。它提供了可运行的参考,比阅读文档更高效,且实现了IM最核心的功能,比如单聊,群聊,会话列表,通讯录等,您可以参考示例代码或基于 Demo进行二次开发,大大提高了开发效率,使开发精力更集中在打造自己应用的独特价值上。

如果您在使用环信Demo中遇到问题,欢迎联系环信专业的技术支持解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值