如何用Dagger2在App作用域ViewModel中注入依赖Activity的Navigator
看起来你遇到了Dagger作用域不匹配的典型问题:你的HomeViewModel绑定在**@Singleton**的VMAppComponent中(属于App全局作用域),但HomeNavigator依赖于Activity(属于Activity作用域),直接构造注入会导致Dagger报错,同时还可能引发内存泄漏风险(App作用域的ViewModel持有Activity引用,Activity销毁后无法被回收)。
下面给你两个最合理的解决方案:
方案一:将ViewModel移至Activity作用域(推荐)
既然HomeViewModel是和HomeActivity强关联的,它根本不需要放在App全局作用域。我们可以把ViewModel的绑定移到Activity级别的Component中,这样就能直接注入依赖Activity的HomeNavigator了。
步骤1:定义Activity作用域
首先创建一个Activity级别的作用域注解:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope {}
步骤2:调整ViewModel绑定到Activity模块
创建HomeActivityModule,将HomeViewModel的绑定和HomeNavigator的实现绑定放在这里:
@Module public abstract class HomeActivityModule { // 绑定HomeViewModel到ViewModel映射 @Binds @IntoMap @ViewModelKey(HomeViewModel.class) abstract ViewModel bindHomeViewModel(HomeViewModel homeViewModel); // 绑定HomeNavigator的实现(HomeActivity本身实现了HomeNavigator) @Binds abstract HomeNavigator bindHomeNavigator(HomeActivity homeActivity); }
步骤3:修改HomeActivityComponent
让HomeActivityComponent依赖VMAppComponent(获取App级别的依赖如BaseSchedulerProvider),并标记为@ActivityScope:
@ActivityScope @Component(dependencies = VMAppComponent.class, modules = {BaseActivityModule.class, HomeActivityModule.class}) public interface HomeActivityComponent { void inject(HomeActivity homeActivity); }
步骤4:修改HomeViewModel的构造注入
现在可以直接在构造函数中注入HomeNavigator了:
public class HomeViewModel extends ViewModel { @NonNull private final HomeNavigator navigationProvider; @NonNull private final BaseSchedulerProvider schedulerProvider; @Inject public HomeViewModel( @NonNull BaseSchedulerProvider schedulerProvider, @NonNull HomeNavigator navigationProvider ) { this.schedulerProvider = schedulerProvider; this.navigationProvider = navigationProvider; } void handleAddButtonClick() { navigationProvider.addNewVehicle(); } }
步骤5:在HomeActivity中初始化Component和ViewModel
public class HomeActivity extends AppCompatActivity implements HomeNavigator { @Inject ViewModelProvider.Factory viewModelFactory; private HomeViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化Activity Component HomeActivityComponent component = ((VMApp) getApplication()) .getAppComponent() .injectHomeActivity(new BaseActivityModule(this)); component.inject(this); // 通过Dagger的Factory获取ViewModel viewModel = new ViewModelProvider(this, viewModelFactory).get(HomeViewModel.class); } @Override public void addNewVehicle() { // 实现跳转逻辑,比如启动AddEditVehicleActivity startActivity(new Intent(this, AddEditVehicleActivity.class)); } }
方案二:使用Assisted Injection(适合跨作用域参数的场景)
如果你的ViewModel确实需要保留在App作用域,同时要注入Activity级别的依赖,可以用Dagger的Assisted Inject来处理混合作用域的构造参数。
步骤1:修改HomeViewModel使用@AssistedInject
public class HomeViewModel extends ViewModel { @NonNull private final HomeNavigator navigationProvider; @NonNull private final BaseSchedulerProvider schedulerProvider; // 用@AssistedInject标记构造函数,Activity级别的参数用@Assisted标记 @AssistedInject public HomeViewModel( @NonNull BaseSchedulerProvider schedulerProvider, @Assisted @NonNull HomeNavigator navigationProvider ) { this.schedulerProvider = schedulerProvider; this.navigationProvider = navigationProvider; } // 定义Assisted Factory,用于创建ViewModel时传入Activity级参数 @AssistedFactory public interface Factory { HomeViewModel create(HomeNavigator navigationProvider); } void handleAddButtonClick() { navigationProvider.addNewVehicle(); } }
步骤2:调整ViewModelFactory适配AssistedInject
你需要修改ViewModelFactory来支持Assisted Factory,或者使用Dagger提供的AssistedViewModelFactory(需要添加对应的依赖)。
步骤3:在Activity中创建ViewModel并传入Navigator
public class HomeActivity extends AppCompatActivity implements HomeNavigator { @Inject HomeViewModel.Factory viewModelFactory; private HomeViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化Component ((VMApp) getApplication()).getAppComponent().injectHomeActivity(new BaseActivityModule(this)).inject(this); // 通过Assisted Factory创建ViewModel,传入当前Activity作为Navigator viewModel = new ViewModelProvider(this, new ViewModelProvider.Factory() { @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { if (modelClass.isAssignableFrom(HomeViewModel.class)) { return (T) viewModelFactory.create(HomeActivity.this); } throw new IllegalArgumentException("Unknown ViewModel class"); } }).get(HomeViewModel.class); } }
为什么原方案的Setter注入有风险?
你当前用setNavigationProvider的方式虽然能工作,但存在两个问题:
- 空指针风险:如果忘记调用setter,
handleAddButtonClick会抛出NPE; - 内存泄漏风险:如果ViewModel是App作用域的,它会持有Activity的引用,当Activity销毁后,ViewModel仍存在于App内存中,导致Activity无法被GC回收。
所以更推荐方案一,让ViewModel的作用域和它关联的Activity保持一致,既符合Dagger的作用域规则,也能避免内存泄漏。
内容的提问来源于stack exchange,提问作者reinaldo

