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) } }
根因分析
- 核心崩溃原因:用
@ApplicationContext即应用级Context创建ProgressDialog,应用Context没有对应Activity的窗口Token,不具备显示UI弹窗的能力,直接调用show()必然触发BadToken异常。 - 之前尝试的方案无效原因:
FirebaseManager的提供方法被安装在SingletonComponent即单例组件中,单例组件生命周期和应用一致,根本无法获取Activity级别的Context依赖;@ActivityScoped作用域和SingletonComponent不匹配,直接导致Hilt注入失败。 - 架构设计问题:
FirebaseManager属于数据层单例组件,不应该持有UI组件、处理UI弹窗逻辑,单例类持有Activity/Dialog这类UI引用还会造成内存泄漏。
修复方案
- 拆分职责,将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() } }
- 修正Hilt配置
删除之前添加的错误@ActivityScoped注解,FirebaseManager保持单例即可,和SingletonComponent的作用域匹配,不需要修改组件安装位置。
额外注意:从拍照Intent返回时,先判断Fragment是否已完成和Activity的附着、Activity是否处于RESUMED状态,再执行弹窗show操作,避免Activity未完全恢复时调用show触发同类异常。
内容的提问来源于stack exchange,提问作者chrisChris
相关产品推荐
相关产品推荐

