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

RecyclerView点击弹出BottomSheetDialog报BadTokenException异常

异常产生原因

这个WindowManager$BadTokenException的核心触发原因有两个:

  • 你传入Dialog的mContext不具备有效的窗口Token:如果mContext是Application级别的上下文,或者是Adapter持有的已经被销毁的旧Activity实例,本身没有可挂载弹窗的活跃窗口,调用show()时就会报token无效的错误。
  • 缺少生命周期校验:点击事件触发、Dialog执行show方法时,宿主Activity可能已经被销毁(比如用户切后台、退出页面、屏幕旋转重建),此时窗口已经被系统回收,不允许再添加新的弹窗。

另外你当前用原生Dialog手动设置Gravity模拟底部弹窗的写法不是官方推荐方案,存在样式不统一、手势交互缺失、兼容性差的问题,同时Firebase异步请求没有绑定生命周期,回调时如果页面销毁还会触发View更新的内存泄漏/崩溃问题。

正确实现方案

前置准备

确保项目已经引入Material组件依赖,直接使用官方封装好的BottomSheetDialog,不需要手动修改Window属性实现底部弹出效果,默认自带底部对齐、手势拖拽关闭、圆角等标准交互。

实现步骤

  1. 不要在Adapter中直接处理弹窗逻辑:Adapter只负责绑定列表数据,点击事件通过回调抛给宿主Activity/Fragment处理,避免Adapter长期持有Context导致的内存泄漏、生命周期不一致问题。
    Adapter中点击逻辑修改参考:
    class PostAdapter(...) : RecyclerView.Adapter<PostAdapter.ViewHolder>() {
        // 定义点击回调
        private var onPublisherClick: ((publisherId: String) -> Unit)? = null
    
        fun setPublisherClickListener(listener: (String) -> Unit) {
            onPublisherClick = listener
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val post = currentList[position]
            // 其他绑定逻辑...
            holder.publisher.setOnClickListener {
                onPublisherClick?.invoke(post.publisher)
            }
        }
        // 其他Adapter逻辑...
    }
    
  2. 在宿主Activity/Fragment中设置Adapter点击监听,实现弹窗逻辑,增加生命周期校验,避免在页面销毁时操作UI:
    // 在Activity中设置监听
    postAdapter.setPublisherClickListener { publisherId ->
        openBottomSheet(publisherId)
    }
    
    正确的openBottomSheet实现:
    private fun openBottomSheet(publisher: String) {
        // 第一步先校验Activity状态,已销毁直接返回不弹弹窗
        if (isFinishing || isDestroyed) return
    
        // 初始化官方BottomSheetDialog,传入当前Activity作为Context
        val bottomSheetDialog = BottomSheetDialog(this)
        val contentView = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_dialog, null)
        
        // 初始化弹窗内的View
        val uImage = contentView.findViewById<ImageView>(R.id.sheetIvProfileImage)
        val uNumber = contentView.findViewById<TextView>(R.id.sheetTvOwnerNumber)
        val uEmail = contentView.findViewById<TextView>(R.id.tvOwnerEmail)
        val uAbout = contentView.findViewById<TextView>(R.id.tvOwnerAbout)
        val uTitle = contentView.findViewById<TextView>(R.id.sheetTvOwnerTitle)
    
        // 配置弹窗属性并显示
        bottomSheetDialog.apply {
            setContentView(contentView)
            setCancelable(true)
            // 弹窗消失时清理图片加载请求,避免内存泄漏
            setOnDismissListener {
                Glide.with(this@YourActivityName).clear(uImage)
            }
            show()
        }
    
        // 发起Firebase数据请求
        val ownersRef = FirebaseFirestore.getInstance().collection("owners").document(publisher)
        ownersRef.get()
            .addOnSuccessListener { document ->
                // 异步回调先校验页面、弹窗状态,已销毁就不更新UI
                if (isFinishing || isDestroyed || !bottomSheetDialog.isShowing) return@addOnSuccessListener
                val ownerModel = document.toObject(Owner::class.java) ?: return@addOnSuccessListener
                
                Glide.with(this)
                    .load(ownerModel.profileImage)
                    .into(uImage)
                uTitle.text = ownerModel.username
                uNumber.text = ownerModel.phoneNumber
                uEmail.text = ownerModel.email
                uAbout.text = ownerModel.about
            }
            .addOnFailureListener {
                // 请求失败关闭弹窗给出提示
                if (bottomSheetDialog.isShowing) bottomSheetDialog.dismiss()
                Toast.makeText(this, "获取用户信息失败,请重试", Toast.LENGTH_SHORT).show()
            }
    }
    

关键注意点

  • 永远不要用Application级别的Context弹Dialog/弹窗,必须传入当前活跃的Activity实例,只有Activity持有有效的窗口Token。
  • 所有异步回调(网络请求、数据库查询、延迟任务)执行UI操作前,必须校验页面/组件的生命周期状态,避免操作已销毁的组件。
  • 优先使用官方封装的BottomSheetDialog实现底部弹窗,不要手动修改原生Dialog的Window属性模拟,减少兼容性问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:03:23