使用Dagger2在Module中实现ProgressDialog触发IllegalStateException求助
兄弟,我一眼就看出你的问题出在哪了——你用Application级别的Context创建了ProgressDialog,但Dialog是必须依附于Activity窗口的组件,Application Context没有对应的窗口token,所以调用show()的时候直接炸了,就是你看到的WindowManager$BadTokenException。
问题根源拆解
你在ApplicationModule里提供的是ApplicationContext,并用来初始化ProgressDialog:
@Provides @Singleton public ProgressDialog provideProgressDialog(Context context) { return new ProgressDialog(context); // 这里的context是Application的! }
Dialog需要绑定到一个活跃的Activity窗口,Application Context没有窗口,自然无法添加Dialog,就会抛出token无效的异常。
解决方案:给ProgressDialog绑定Activity生命周期
我们需要把ProgressDialog的创建逻辑移到Activity级别的Module里,让它依赖Activity的Context,并且和Activity生命周期绑定。
步骤1:创建自定义的Activity作用域
因为Singleton是全局生命周期,我们需要一个和Activity生命周期匹配的作用域:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { }
步骤2:创建ActivityModule,提供Activity级依赖
@Module public class ActivityModule { private AppCompatActivity mActivity; public ActivityModule(AppCompatActivity activity) { this.mActivity = activity; } @Provides @ActivityScope public ProgressDialog provideProgressDialog() { ProgressDialog progressDialog = new ProgressDialog(mActivity); progressDialog.setTitle(mActivity.getString(R.string.str_progress_dialog_title)); progressDialog.setMessage(mActivity.getString(R.string.str_progress_dialog_desc)); progressDialog.setCancelable(false); return progressDialog; } @Provides @ActivityScope public Context provideActivityContext() { return mActivity; } }
步骤3:创建ActivityComponent,依赖ApplicationComponent
这样既能复用Application级的单例(比如你的APIEndPoints),又能提供Activity专属的依赖:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(BaseActivity baseActivity); void inject(HomeActivity homeActivity); }
步骤4:修改BaseActivity的注入逻辑
把原来的ApplicationComponent注入改成ActivityComponent:
public class BaseActivity extends AppCompatActivity { @Inject protected ProgressDialog mProgressDialog; @Inject protected HomePresenter homePresenter; private ActivityComponent mActivityComponent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 构建ActivityComponent并注入 mActivityComponent = DaggerActivityComponent.builder() .applicationComponent(((Dagger2DemoApplication) getApplication()).getmApplicationComponent()) .activityModule(new ActivityModule(this)) .build(); mActivityComponent.inject(this); } public ActivityComponent getActivityComponent() { return mActivityComponent; } }
步骤5:清理ApplicationModule
把原来在ApplicationModule里的provideProgressDialog方法删掉,避免冲突,只保留全局单例的提供:
@Module public class ApplicationModule { private Dagger2DemoApplication mDagger2DemoApplication; public ApplicationModule(Dagger2DemoApplication dagger2DemoApplication) { this.mDagger2DemoApplication = dagger2DemoApplication; } @Provides @Singleton public APIEndPoints provideAPIEndPoints() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://reqres.in/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); return retrofit.create(APIEndPoints.class); } @Provides @Singleton public Context provideApplicationContext() { return mDagger2DemoApplication.getApplicationContext(); } }
为什么这样改就没问题?
- 用Activity的Context创建Dialog,有了有效的窗口token,
show()方法就能正常添加窗口了。 ActivityScope保证ProgressDialog的生命周期和Activity一致,不会出现内存泄漏,也符合Dialog的使用场景(一个Activity对应一个Dialog实例)。- ActivityComponent依赖ApplicationComponent,之前的全局单例(比如APIEndPoints)仍然可以正常注入,不影响原有功能。
额外小建议
现在官方其实已经不推荐用ProgressDialog了,更建议用Material Design风格的ProgressBar或者自定义的加载弹窗,但不管用哪种,核心原则都是:弹窗类组件必须使用Activity的Context。
内容的提问来源于stack exchange,提问作者Maulik Dodia

