本文系統地解析了環信 IM Demo 中用戶頭像和昵稱的完整實現邏輯,涵蓋了從UI顯示到數據存儲和同步的完整流程。接下來我們逐一拆解每個環節的實現細節,帶您全面了解環信Demo中的用戶信息管理機制。

環信 IM Demo 下載:http://www.daisymarcjacobs.cn/download/demo

源碼地址:https://doc.easemob.com/product/demo.html

1. 展示層(UI層)

以下均以Android端示例代碼(kotlin)為例

1.1 聊天界面如何獲取用戶信息

聊天界面通過以下調用鏈獲取用戶信息:

  • 聊天Adapter → DemoHelper.getInstance().getDataModel().getUser(userId)

  • 如果本地有數據,直接使用本地數據

  • 如果沒有,觸發拉取(如 ProfileInfoViewModel.fetchUserInfoAttribute

關鍵代碼:

// DemoDataModel.ktfun 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.fetchUserInfoAttribute → ProfileInfoRepository.getUserInfoAttribute

  • 調用環信SDK的 userInfoManager().fetUserInfo 接口

關鍵代碼:

// 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)
    }
  • 拉取成功后,ViewModel會將數據存入本地。

3. 存儲層(本地數據庫+緩存)

3.1 存入本地數據庫和緩存

調用鏈:

  • 拉取到的 ChatUserInfo → 轉為 ChatUIKitProfile → DemoDataModel.insertUser

關鍵代碼:

// DemoDataModel.ktfun 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.ktif (users.isNotEmpty()){
    ChatUIKitClient.updateUsersInfo(users)}
  • 這樣UI層會自動感知到數據變化。

5. 用戶主動修改頭像/昵稱

5.1 修改昵稱

調用鏈:

  • UI點擊修改 → ProfileInfoViewModel.updateUserNickName → ProfileInfoRepository.updateNickname → 環信SDK

  • 成功后本地更新并刷新UI

關鍵代碼:

// 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 修改頭像

調用鏈:

  • UI選擇圖片 → ProfileInfoViewModel.uploadAvatar → ProfileInfoRepository.uploadAvatar(上傳到App服務器)→ uploadAvatarToChatServer(同步到環信)

  • 本地更新并刷新UI

關鍵代碼:

// 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    }}

解析說明:

  • 通過本地緩存獲取最新的用戶信息

  • 用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中遇到問題,歡迎聯系環信專業的技術支持解決。