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

Hilt为FirebaseManager提供Context时ProgressDialog崩溃解决方案

FirebaseManager传Context显示ProgressDialog崩溃问题

问题现状

需要为FirebaseManager.kt传入合适的Context,当前类实现代码如下:

class FirebaseManager @Inject constructor(
    @ApplicationContext val context: Context,
    firebaseFirestore: FirebaseFirestore,
    firebaseStorage: FirebaseStorage
) {
    fun uploadImage(imageFileUri: Uri) {
        mProgressDialog = ProgressDialog(context)
        mProgressDialog.setMessage("Please wait, image being upload")

        mProgressDialog.show()  // 崩溃触发点
    }
}

现有依赖注入相关配置:

  • NetworkModule定义:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideFirestore() = FirebaseFirestore.getInstance()

    @Provides
    @Singleton
    fun provideFirebaseStorage() = FirebaseStorage.getInstance()
}
  • MyInspirationModule定义:
@Module
@InstallIn(SingletonComponent::class)
abstract class MyInspirationModule {

    companion object {
        @Provides
        fun provideInspirationFirebaseStore(@ApplicationContext context: Context, firebaseFirestore: FirebaseFirestore, firebaseFirebaseStorage: FirebaseStorage) : FirebaseManager =
            FirebaseManager(context, firebaseFirestore, firebaseFirebaseStorage)
    }
}

已尝试的无效方案

  • 将@ApplicationContext替换为@ActivityContext,Hilt注入失败,应用无法正常运行
  • 给依赖提供方添加@ActivityScoped注解,代码如下,仍无法解决问题:
companion object {
    @Provides
    @ActivityScoped
    fun provideInspirationFirebaseStore(@ApplicationContext context: Context, firebaseFirestore: FirebaseFirestore, firebaseFirebaseStorage: FirebaseStorage) : FirebaseManager =
        FirebaseManager(context, firebaseFirestore, firebaseFirebaseStorage)
}

业务场景与崩溃日志

业务流程:应用跳转拍照Intent后返回Fragment,此时本应展示图片上传进度弹窗,但实际触发崩溃。Firebase本身的数据读写加载功能运行正常,问题出在Context的使用环节。
崩溃为ProgressDialog调用show()方法时触发,报错日志:

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:907)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:95)
    at android.app.Dialog.show(Dialog.java:342)
    at com.example.learningmanager.fragments.myinspiration.FirebaseManager.uploadImage(FirebaseManager.kt:31)

关联的FirebaseStorage业务用例实现:

class AddImageMyInspirationFirebaseStorageUseCase @Inject constructor(private val firebaseManager: FirebaseManager) {
    suspend fun create(imgUrl: Uri) {
        firebaseManager.uploadImage(imgUrl)
    }
}

根因分析

  1. 核心崩溃原因:用@ApplicationContext即应用级Context创建ProgressDialog,应用Context没有对应Activity的窗口Token,不具备显示UI弹窗的能力,直接调用show()必然触发BadToken异常。
  2. 之前尝试的方案无效原因:FirebaseManager的提供方法被安装在SingletonComponent即单例组件中,单例组件生命周期和应用一致,根本无法获取Activity级别的Context依赖;@ActivityScoped作用域和SingletonComponent不匹配,直接导致Hilt注入失败。
  3. 架构设计问题:FirebaseManager属于数据层单例组件,不应该持有UI组件、处理UI弹窗逻辑,单例类持有Activity/Dialog这类UI引用还会造成内存泄漏。

修复方案

  1. 拆分职责,将UI逻辑从数据层移出
    修改FirebaseManager,只保留Firebase上传相关的核心逻辑,移除所有ProgressDialog相关代码,通过回调/Flow向上层传上传状态:
class FirebaseManager @Inject constructor(
    @ApplicationContext val context: Context,
    private val firebaseFirestore: FirebaseFirestore,
    private val firebaseStorage: FirebaseStorage
) {
    suspend fun uploadImage(imageFileUri: Uri, onProgressChange: ((progress: Int) -> Unit)? = null): Result<Uri> {
        // 此处实现Firebase上传逻辑,上传过程中通过onProgressChange回传进度
        // 上传完成后返回封装的成功/失败结果,不处理任何UI操作
    }
}

同步修改UseCase层适配新的方法签名即可。
2. 将弹窗逻辑下沉到页面层(Fragment/Activity)
只有页面层持有当前活跃的Activity Context,具备显示弹窗的合法权限。在对应Fragment中处理Dialog的创建、显示、销毁:

class MyInspirationFragment : Fragment() {
    private var progressDialog: ProgressDialog? = null
    // 注入上传用例
    @Inject
    lateinit var uploadUseCase: AddImageMyInspirationFirebaseStorageUseCase

    // 拍照返回、拿到图片Uri后调用上传逻辑
    private fun startImageUpload(imageUri: Uri) {
        // 必须用当前Fragment附着的Activity Context创建Dialog
        progressDialog = ProgressDialog(requireContext())
        progressDialog?.setMessage("Please wait, image being upload")
        progressDialog?.show()

        viewLifecycleOwner.lifecycleScope.launch {
            val uploadResult = uploadUseCase.create(imageUri)
            progressDialog?.dismiss()
            // 处理上传成功/失败的业务逻辑
        }
    }

    override fun onDestroyView() {
        // 页面销毁时及时关闭弹窗,避免内存泄漏
        progressDialog?.dismiss()
        progressDialog = null
        super.onDestroyView()
    }
}
  1. 修正Hilt配置
    删除之前添加的错误@ActivityScoped注解,FirebaseManager保持单例即可,和SingletonComponent的作用域匹配,不需要修改组件安装位置。

额外注意:从拍照Intent返回时,先判断Fragment是否已完成和Activity的附着、Activity是否处于RESUMED状态,再执行弹窗show操作,避免Activity未完全恢复时调用show触发同类异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:33:20