疑问
为什么用了 Hilt 就可以在 XxxViewModel 的构造方法中肆无忌惮的添加参数?
@Singleton
class MyRepo
@HiltViewModel
class MyViewModel(val repo: MyRepo) : ViewModel
@AndroidEntryPoint
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
}
因为正常情况下,不提供自定义的 ViewModelFactory 是不可能生成带参数的 ViewModel 的。
诶,说到点子上了,那么 Hilt 肯定提供了自定义的 ViewModelFactory。
原因
查看生成的 Hilt_MyFragment 源码,可以发现其复写了 Fragment#getDefaultViewModelProviderFactory()
方法:
public abstract class Hilt_GardenFragment extends Fragment {
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
return DefaultViewModelFactories.getFragmentFactory(this, super.getDefaultViewModelProviderFactory());
}
}
再看 DefaultViewModelFactories.java
:
public final class DefaultViewModelFactories {
/**
* Retrieves the default view model factory for the activity.
*
* <p>Do not use except in Hilt generated code!
*/
public static ViewModelProvider.Factory getActivityFactory(ComponentActivity activity,
ViewModelProvider.Factory delegateFactory) {
return EntryPoints.get(activity, ActivityEntryPoint.class)
.getHiltInternalFactoryFactory()
.fromActivity(activity, delegateFactory);
}
/**
* Retrieves the default view model factory for the activity.
*
* <p>Do not use except in Hilt generated code!
*/
public static ViewModelProvider.Factory getFragmentFactory(
Fragment fragment, ViewModelProvider.Factory delegateFactory) {
return EntryPoints.get(fragment, FragmentEntryPoint.class)
.getHiltInternalFactoryFactory()
.fromFragment(fragment, delegateFactory);
}
/** Internal factory for the Hilt ViewModel Factory. */
public static final class InternalFactoryFactory {
private final Application application;
private final Set<String> keySet;
private final ViewModelComponentBuilder viewModelComponentBuilder;
@Inject
InternalFactoryFactory(
Application application,
@HiltViewModelMap.KeySet Set<String> keySet,
ViewModelComponentBuilder viewModelComponentBuilder) {
this.application = application;
this.keySet = keySet;
this.viewModelComponentBuilder = viewModelComponentBuilder;
}
ViewModelProvider.Factory fromActivity(
ComponentActivity activity, ViewModelProvider.Factory delegateFactory) {
return getHiltViewModelFactory(
activity,
activity.getIntent() != null ? activity.getIntent().getExtras() : null,
delegateFactory);
}
ViewModelProvider.Factory fromFragment(
Fragment fragment, ViewModelProvider.Factory delegateFactory) {
return getHiltViewModelFactory(fragment, fragment.getArguments(), delegateFactory);
}
private ViewModelProvider.Factory getHiltViewModelFactory(
SavedStateRegistryOwner owner,
@Nullable Bundle defaultArgs,
@Nullable ViewModelProvider.Factory extensionDelegate) {
ViewModelProvider.Factory delegate = extensionDelegate == null
? new SavedStateViewModelFactory(application, owner, defaultArgs)
: extensionDelegate;
return new HiltViewModelFactory(
owner, defaultArgs, keySet, delegate, viewModelComponentBuilder);
}
}
/** The activity module to declare the optional factories. */
@Module
@InstallIn(ActivityComponent.class)
interface ActivityModule {
@Multibinds
@HiltViewModelMap.KeySet
abstract Set<String> viewModelKeys();
}
/** The activity entry point to retrieve the factory. */
@EntryPoint
@InstallIn(ActivityComponent.class)
public interface ActivityEntryPoint {
InternalFactoryFactory getHiltInternalFactoryFactory();
}
/** The fragment entry point to retrieve the factory. */
@EntryPoint
@InstallIn(FragmentComponent.class)
public interface FragmentEntryPoint {
InternalFactoryFactory getHiltInternalFactoryFactory();
}
private DefaultViewModelFactories() {}
}
可以看到在 getHiltViewModelFactory()
方法中生成并返回了 HiltViewModelFactory
实例。
再看 HiltViewModelFactory.java
:
public final class HiltViewModelFactory implements ViewModelProvider.Factory {
/** Hilt entry point for getting the multi-binding map of ViewModels. */
@EntryPoint
@InstallIn(ViewModelComponent.class)
public interface ViewModelFactoriesEntryPoint {
@HiltViewModelMap
Map<String, Provider<ViewModel>> getHiltViewModelMap();
}
/** Hilt module for providing the empty multi-binding map of ViewModels. */
@Module
@InstallIn(ViewModelComponent.class)
interface ViewModelModule {
@Multibinds
@HiltViewModelMap
Map<String, ViewModel> hiltViewModelMap();
}
private final Set<String> hiltViewModelKeys;
private final ViewModelProvider.Factory delegateFactory;
private final AbstractSavedStateViewModelFactory hiltViewModelFactory;
public HiltViewModelFactory(
@NonNull SavedStateRegistryOwner owner,
@Nullable Bundle defaultArgs,
@NonNull Set<String> hiltViewModelKeys,
@NonNull ViewModelProvider.Factory delegateFactory,
@NonNull ViewModelComponentBuilder viewModelComponentBuilder) {
this.hiltViewModelKeys = hiltViewModelKeys;
this.delegateFactory = delegateFactory;
this.hiltViewModelFactory =
new AbstractSavedStateViewModelFactory(owner, defaultArgs) {
@NonNull
@Override
@SuppressWarnings("unchecked")
protected <T extends ViewModel> T create(
@NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle) {
ViewModelComponent component =
viewModelComponentBuilder.savedStateHandle(handle).build();
Provider<? extends ViewModel> provider =
EntryPoints.get(component, ViewModelFactoriesEntryPoint.class)
.getHiltViewModelMap()
.get(modelClass.getName());
if (provider == null) {
throw new IllegalStateException(
"Expected the @HiltViewModel-annotated class '"
+ modelClass.getName()
+ "' to be available in the multi-binding of "
+ "@HiltViewModelMap but none was found.");
}
return (T) provider.get();
}
};
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (hiltViewModelKeys.contains(modelClass.getName())) {
return hiltViewModelFactory.create(modelClass);
} else {
return delegateFactory.create(modelClass);
}
}
@EntryPoint
@InstallIn(ActivityComponent.class)
interface ActivityCreatorEntryPoint {
@HiltViewModelMap.KeySet
Set<String> getViewModelKeys();
ViewModelComponentBuilder getViewModelComponentBuilder();
}
public static ViewModelProvider.Factory createInternal(
@NonNull Activity activity,
@NonNull SavedStateRegistryOwner owner,
@Nullable Bundle defaultArgs,
@NonNull ViewModelProvider.Factory delegateFactory) {
ActivityCreatorEntryPoint entryPoint =
EntryPoints.get(activity, ActivityCreatorEntryPoint.class);
return new HiltViewModelFactory(
owner,
defaultArgs,
entryPoint.getViewModelKeys(),
delegateFactory,
entryPoint.getViewModelComponentBuilder()
);
}
}
具体创建 ViewModel 实例的地方在 AbstractSavedStateViewModelFactory#create()
方法中。其 provider 实例是动态生成的,跟进去可以看到其实就是调用 new 生成的具体的 ViewModel 并填充了具体的参数:
private MyViewModel viewModel() {
return new MyViewModel(singletonC.myRepoProvider.get());
}
最后
至此,解释清楚了 Hilt 是如何生成带参数的 ViewModel 实例的。
另外,不用 Hilt 也想带参数,可以用如下代码实现:
/**
* 提供自定义 Factory 和 viewModel 扩展,使得定义 ViewModel 时可以传参
*
* <code>
* private val model by viewModel { XxxViewModel(1, 2, 3) }
* </code>
*/
class ParamViewModelFactory<VM : ViewModel>(
private val block: () -> VM,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = block() as T
}
inline fun <reified VM : ViewModel> AppCompatActivity.viewModel(
noinline block: () -> VM,
): Lazy<VM> = viewModels { ParamViewModelFactory(block) }
inline fun <reified VM : ViewModel> Fragment.viewModel(
noinline block: () -> VM,
): Lazy<VM> = viewModels { ParamViewModelFactory(block) }
网友评论