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() }
问题核心
开发聊天功能发送图片时遇到两个关键问题:
- 最初用URI发送消息,数据库仅写入1次,但RecyclerView重复显示3次,同时抛出
TransactionTooLargeException: data parcel size 896372 bytes错误。 - 改用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
相关产品推荐
相关产品推荐

