You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android聊天图片发送遇TransactionTooLargeException及列表重复显示问题

代码片段整理

图片选择启动代码

mBinding.attachBtn.setOnClickListener {
    mGetContent.launch("image/*")
}

初始化图片选择器

mGetContent = registerForActivityResult(ActivityResultContracts.GetContent(),
    ActivityResultCallback {
        val intent = Intent(APP_ACTIVITY, CropperAcrtivity::class.java)
        intent.putExtra("DATA", it.toString())
        startActivityForResult(intent, 101)
    })

获取图片裁剪返回结果

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == -1 && requestCode == 101) {
        var result = data!!.getStringExtra("RESULT")
        var resultUri: Uri = Uri.parse(result)
        var bitmap = MediaStore.Images.Media.getBitmap(APP_ACTIVITY.contentResolver, resultUri)
        sendMessageAsImage(bitmap)
    }
}

上传按钮点击事件

mBinding.uploadBtn.setOnClickListener {
    uploadImageMessage()
}

图片消息上传方法

private fun uploadImageMessage(){
    val message = hashMapOf<String, Any>()
    message.put(KEY_IS_CHECKED, false)
    message.put(SENDER_ID, preferenceManager.getString(KEY_USER_ID)!!)
    message.put(RECEIVER_ID, receiverUser.uid)
    message.put(KEY_MESSAGE, mBinding.textImages.text)
    message.put(KEY_TIMESTAMP, Date())
    message.put(KEY_TYPE_MESSAGE, MESSAGE_TYPE_IMAGE)
    database.collection(KEY_COLLECTION_USERS)
        .whereEqualTo(KEY_UID, preferenceManager.getString(KEY_UID))
        .get().addOnCompleteListener {
            it.getResult().documents.get(0).reference.collection(KEY_COLLECTION_CHAT)
                .add(message)
        }
    mBinding.frameTypeField.visibility = View.VISIBLE
    mBinding.sendBtn.visibility = View.VISIBLE
    mBinding.frameAttach.visibility = View.VISIBLE
    mBinding.cancelBtn.visibility = View.GONE
    mBinding.uploadBtn.visibility = View.GONE
    mBinding.iconImage.visibility = View.GONE
    mBinding.textImages.visibility = View.GONE
    mBinding.typeMessageField.setText(null)
}

图片消息预处理方法

private fun sendMessageAsImage(image: Bitmap) {
    mBinding.frameTypeField.visibility = View.GONE
    mBinding.sendBtn.visibility = View.GONE
    mBinding.frameAttach.visibility = View.GONE
    mBinding.cancelBtn.visibility = View.VISIBLE
    mBinding.uploadBtn.visibility = View.VISIBLE
    mBinding.iconImage.visibility = View.VISIBLE
    mBinding.textImages.visibility = View.VISIBLE
    mBinding.textImages.text = encodedImage(image).toString()
}

问题核心

开发聊天功能发送图片时遇到两个关键问题:

  1. 最初用URI发送消息,数据库仅写入1次,但RecyclerView重复显示3次,同时抛出TransactionTooLargeException: data parcel size 896372 bytes错误。
  2. 改用Bitmap发送后,错误未消失,且刚启动图片选择器就触发错误,尚未选择照片。
    此前将发送逻辑从sendMessageAsImage拆分到uploadBtn点击事件中,问题依旧。

问题分析与修复方案

1. TransactionTooLargeException 根源与修复

这个错误是因为传递/存储的数据体积超过Binder事务限制(通常约1MB)。你当前把Bitmap转成Base64字符串存到TextView或数据库,Base64会让图片体积增大30%左右,直接触发阈值。

修复步骤:

  • 不要直接存储Base64或Bitmap,改为将图片上传到云存储(如Firebase Storage、阿里云OSS),仅把图片的下载URL存入数据库。示例修改sendMessageAsImage:
private fun sendMessageAsImage(image: Bitmap) {
    // 将Bitmap转为临时文件
    val tempFile = File(context.cacheDir, "chat_temp_img_${System.currentTimeMillis()}.jpg")
    tempFile.outputStream().use {
        image.compress(Bitmap.CompressFormat.JPEG, 80, it)
    }
    // 上传到云存储(以Firebase Storage为例)
    val storageRef = FirebaseStorage.getInstance().reference.child("chat_images/${UUID.randomUUID()}.jpg")
    storageRef.putFile(Uri.fromFile(tempFile))
        .addOnSuccessListener {
            storageRef.downloadUrl.addOnSuccessListener { downloadUrl ->
                // 将URL存入TextView,等待上传
                mBinding.textImages.text = downloadUrl.toString()
                // 原有UI调整逻辑保留
                mBinding.frameTypeField.visibility = View.GONE
                mBinding.sendBtn.visibility = View.GONE
                // ... 其余UI设置
            }
        }
}

2. RecyclerView重复显示修复

  • 检查消息监听逻辑:确保Firestore的SnapshotListener只注册一次,避免页面重复注册导致多次回调。示例:
private var chatListener: ListenerRegistration? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    // 仅在View创建时注册监听
    chatListener = database.collection(KEY_COLLECTION_USERS)
        .document(preferenceManager.getString(KEY_UID)!!)
        .collection(KEY_COLLECTION_CHAT)
        .orderBy(KEY_TIMESTAMP)
        .addSnapshotListener { snapshot, e ->
            // 处理消息更新逻辑
        }
}

override fun onDestroyView() {
    super.onDestroyView()
    // 页面销毁时移除监听
    chatListener?.remove()
}
  • 给RecyclerView Adapter设置稳定ID,避免重复渲染:
class ChatAdapter(private val messages: List<ChatMessage>) : RecyclerView.Adapter<ChatViewHolder>() {
    override fun getItemId(position: Int): Long {
        // 用消息文档的唯一ID作为条目ID
        return messages[position].documentId.hashCode().toLong()
    }

    init {
        setHasStableIds(true)
    }
    // ... 其余Adapter逻辑
}

3. 启动选择器即触发错误修复

  • 避免混用新旧ActivityResult API:你已经使用registerForActivityResult,但又调用了旧的startActivityForResult,统一改用新API处理裁剪页面返回:
// 注册裁剪页面的ActivityResult
private val cropLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val resultUriStr = result.data?.getStringExtra("RESULT") ?: return@registerForActivityResult
        val resultUri = Uri.parse(resultUriStr)
        val bitmap = MediaStore.Images.Media.getBitmap(APP_ACTIVITY.contentResolver, resultUri)
        sendMessageAsImage(bitmap)
    }
}

// 初始化图片选择器时调用新Launcher
mGetContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
    val intent = Intent(APP_ACTIVITY, CropperAcrtivity::class.java)
    intent.putExtra("DATA", uri.toString())
    cropLauncher.launch(intent) // 替换原startActivityForResult
}
  • 检查onSaveInstanceState:不要保存Bitmap、Base64这类大对象,避免页面重建时传递过大数据。

内容的提问来源于stack exchange,提问作者Danil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 18:48:24