美文网首页
Android 关于 Hilt 的 HiltViewModelF

Android 关于 Hilt 的 HiltViewModelF

作者: 雁过留声_泪落无痕 | 来源:发表于2022-04-19 15:13 被阅读0次

疑问

为什么用了 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) }

相关文章

网友评论

      本文标题:Android 关于 Hilt 的 HiltViewModelF

      本文链接:https://www.haomeiwen.com/subject/knvsertx.html