Android配置变更时ViewModel重复调用构造函数原因排查
Let's break down exactly what's happening here, and yes—your dependency injection setup is the root cause of this behavior.
The Core Issue: Your DI is bypassing ViewModel's lifecycle management
By default, ViewModelProvider is designed to retain ViewModels across configuration changes (like screen rotations). It only creates a new ViewModel instance if one doesn't already exist for the given lifecycle owner (your Activity/Fragment). But your current DI setup forces a new ViewModel instance to be created every time you retrieve the ViewModelProvider.Factory, which breaks this retention.
Let's look at your DI code specifically:
@Provides ViewModelProvider.Factory mainViewModelProvider(MainViewModel mainViewModel) { return new ViewModelProviderFactory<>(mainViewModel); }
When your DI framework resolves this ViewModelProvider.Factory, it first has to create a new MainViewModel instance to pass into the factory. This happens every time your Activity is recreated (on rotation), because performInjection() runs in onCreate().
So instead of letting ViewModelProvider decide when to create the ViewModel, your DI is pre-creating a new instance on every Activity/Fragment rebuild—and the factory just hands that pre-made instance over. This completely skips the ViewModel's retention mechanism.
Why your Fragment's BalanceViewModel is also recreating
Your Fragment's code uses the same flawed pattern:
@Override public BalanceViewModel setViewModel() { return ViewModelProviders.of(this, viewModelFactory).get(BalanceViewModel.class); }
If your viewModelFactory for BalanceViewModel is set up the same way as your MainViewModel's factory (pre-creating the ViewModel instance), then every time the Fragment is recreated (on rotation), DI will spin up a new BalanceViewModel, and the old one will be cleared (hence the onCleared() log entry).
How to fix this
You need to adjust your DI setup to let ViewModelProvider control when ViewModels are created, instead of pre-instantiating them. Here are two common approaches:
1. Use a ViewModel-specific Factory class
Create a nested Factory class inside your ViewModel that handles instantiation, and inject its dependencies:
// Inside MainViewModel.java public class MainViewModel extends ViewModel { private final SomeDependency dependency; public MainViewModel(SomeDependency dependency) { this.dependency = dependency; } @Inject public static class Factory extends ViewModelProvider.NewInstanceFactory { private final SomeDependency dependency; @Inject public Factory(SomeDependency dependency) { this.dependency = dependency; } @Override public <T extends ViewModel> T create(Class<T> modelClass) { if (modelClass.isAssignableFrom(MainViewModel.class)) { return (T) new MainViewModel(dependency); } throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass); } } }
Then update your DI module to provide this Factory instead of pre-creating the ViewModel:
@Provides ViewModelProvider.Factory mainViewModelProvider(MainViewModel.Factory factory) { return factory; }
This way, the Factory only creates a new MainViewModel when ViewModelProvider calls create()—which only happens if there's no existing instance for the lifecycle owner.
2. Use a generic ViewModel Factory (scalable for multiple ViewModels)
For apps with many ViewModels, a generic factory that uses a map of ViewModel classes to their DI providers is more efficient:
public class GenericViewModelFactory implements ViewModelProvider.Factory { private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModelProviders; @Inject public GenericViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModelProviders) { this.viewModelProviders = viewModelProviders; } @Override @SuppressWarnings("unchecked") public <T extends ViewModel> T create(Class<T> modelClass) { Provider<? extends ViewModel> provider = viewModelProviders.get(modelClass); if (provider == null) { // Fallback for subclass ViewModels for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : viewModelProviders.entrySet()) { if (modelClass.isAssignableFrom(entry.getKey())) { provider = entry.getValue(); break; } } } if (provider == null) { throw new IllegalArgumentException("No provider found for ViewModel class: " + modelClass); } try { return (T) provider.get(); } catch (Exception e) { throw new RuntimeException("Failed to create ViewModel", e); } } }
Then bind your ViewModels to this factory using DI's @IntoMap and a custom @ViewModelKey:
// Define a ViewModelKey annotation @MapKey @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ViewModelKey { Class<? extends ViewModel> value(); } // In your DI module @Binds @IntoMap @ViewModelKey(MainViewModel.class) abstract ViewModel bindMainViewModel(MainViewModel viewModel); @Binds @IntoMap @ViewModelKey(BalanceViewModel.class) abstract ViewModel bindBalanceViewModel(BalanceViewModel viewModel); @Binds abstract ViewModelProvider.Factory bindViewModelFactory(GenericViewModelFactory factory);
This setup lets your DI framework handle mapping ViewModel classes to their providers, and the generic factory only retrieves a new instance when ViewModelProvider requests it—preserving the ViewModel's lifecycle retention.
Summary
Your ViewModels are recreating on rotation because your DI configuration is pre-instantiating ViewModels and passing them into the factory, instead of letting ViewModelProvider manage when instances are created. Fixing your DI setup to defer ViewModel creation to the factory will resolve the issue and restore the expected ViewModel lifecycle behavior.
内容的提问来源于stack exchange,提问作者Ivan Sablin

