理解Jetpack--Hilt

888 阅读9分钟

上篇文章我们讲解了Dagger2这把“匕首”,下面我们继续了解如何给“匕首”装上“刀柄”。如果还没有看过上篇对Dagger2的分析,建议先看对Dagger2的分析,再来看这篇效果更佳。揭开Dagger2的神秘面纱

Hilt是什么

HiltDagger2一样,是一个依赖注入库,但是Hilt 是专为 Android 设计的依赖项注入库。

Hilt 提供一种在应用中进行 DI 注入的标准方法,其会为项目中的每个 Android 组件提供容器,并为您自动管理容器的生命周期,可减少在项目中使用手动 DI 的样板。

Hilt使用

HiltAndroidApp

新建我们自己的Application类并添加 @HiltAndroidApp 注解。@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。代码如下:

@HiltAndroidApp
class MyApplication:Application() {
    ...
}

这里是不是特别简单,相比Dagger2我们不需自己去创建ApplicationComponent,只要添加HiltAndroidApp就可以。这是为什么呢?后面我们源码再作分析。

AndroidEntryPoint

Hilt可以为带有@AndroidEntryPoint注解的其他Android类提供依赖项。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...
}

如果您使用 @AndroidEntryPoint 为某个 Android 类添加注解,则还必须为依赖于该类的 Android 类添加注解。例如,如果您为某个 fragment 添加注解,则还必须为使用该 fragment 的所有 activity 添加注解。

Inject

这里的@Inject与Dagger2一样,该注解可以使用在类的成员变量或类的构造方法上。

  1. 在成员变量使用该注解,表示该对象需要Hilt帮我们注入。如下代码,是告诉Hilt这里的User对象需要你帮我注入进来
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @JvmField
    @Inject
    var user:User?=null

    ...
}
  1. 在类的构造方法中使用@Inject注解,告知Hilt使用该构造方法提供该类的实例。如下代码,我们在User类的构造方法使用@Inject,告诉Hilt可以使用该构造方法创建User实例。
class User @Inject constructor() {
    var name: String? = null
}

Module

有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以@Module注解的类向 Hilt 提供绑定信息

Hilt 模块是一个带有 @Module 注解的类。与Dagger2一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是,您必须使用 @InstallIn 为 Hilt 模块添加注解,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中。

@Binds

@Binds 注解会告知 Hilt 在需要提供接口的实例时要使用哪种实现。

interface UserRepository {
    fun getUser():User
}

class UserDataRepository @Inject constructor():UserRepository{
    override fun getUser(): User=User().apply { name="王明" }
}

@InstallIn(ActivityComponent::class)
@Module
abstract class UserRepositoryModule {
   
    @Binds
    abstract fun bindUserRepository(repository: UserDataRepository): UserRepository
}

这里UserRepository是一个接口,无法通过构造方法注入,我们只能使用@Module注解的类提供注入信息。

我们建立抽象类UserRepositoryModule并使用@Module@InstallIn注解,@InstallIn注解表明该模块安装到ActivityComponent上。

UserRepositoryModule类内部创建带有@Bind注解的抽象方法。带有@Bind注解的抽象方法告诉HiltUserRepository接口的实例使用参数UserRepositoryModule类实现。

@Provides

如果某个类为第三方库所有,我们无法使用无法通过构造函数注入,那该如何提供实例呢?Hilt给我们提供了@Provides,他的使用和Dagger2一样,这里就不详细介绍了。

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

@Qualifier

如果一个类提供了多种实例的绑定,我们可以使用@Qualifier建立别名,和Dagger2一样,这里就不详细介绍了。

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

预定义限定符

Hilt 提供了一些预定义的限定符。来自Application级别的Context @ApplicationContext,来自Activity级别的Context @ActivityContext。我们在Dagger2是需要自己实现,而Hilt帮我们定义好了,是不是简单很多。

Hilt预定义的组件(Component)

Hilt还给我们预定义了一些Component。

Hilt 组件注入器面向的对象
SingletonComponentApplication
ActivityRetainedComponent不适用
ViewModelComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponent带有 @WithFragmentBindings 注解的 View
ServiceComponentService

组件的生命周期

生成的组件创建时机销毁时机
SingletonComponentApplication#onCreate()Application 已销毁
ActivityRetainedComponentActivity#onCreate()Activity#onDestroy()
ViewModelComponentViewModel 已创建ViewModel 已销毁
ActivityComponentActivity#onCreate()Activity#onDestroy()
FragmentComponentFragment#onAttach()Fragment#onDestroy()
ViewComponentView#super()View 已销毁
ViewWithFragmentComponentView#super()View 已销毁
ServiceComponentService#onCreate()Service#onDestroy()

组件的作用域

Android 类生成的组件作用域
ApplicationSingletonComponent@Singleton
ActivityActivityRetainedComponent@ActivityRetainedScoped
ViewModelViewModelComponent@ViewModelScoped
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
带有 @WithFragmentBindings 注解的 ViewViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@ServiceScoped

在 Hilt 不支持的类中注入依赖项

在这些情况下,您可以使用 @EntryPoint 注解创建入口点。@InstallIn 以指定要在其中安装入口点的组件。使用如下:

class Student {

    @InstallIn(ActivityComponent::class)
    @EntryPoint
    interface UserRepositoryEntryPoint {
        fun getUserRepository(): UserRepository
    }

    private fun getUser(context: Activity): User {
        val userRepositoryEntryPoint =
            EntryPointAccessors.fromActivity(context, UserRepositoryEntryPoint::class.java)
        return userRepositoryEntryPoint.getUserRepository().getUser()
    }
}

在Student中无法直接注入UserRepository,所以我们创建了一个UserRepositoryEntryPoint接口使用@EntryPoint注解创建入口点,使用@InstallIn指明器转载的组件。

我们使用EntryPointAccessors提供的静态方法访问入口点,注意这里的fromActivity方法,我们需要根据装载的组件不同,做适当调整。

源码分析

上面我们知道了Hilt帮我们创建了一些Component,这就是不需要我们创建的原因。我们看一下Hilt都做了什么。

Comnponent+Hilt_Application等生成

我们在自定义的MyApplication上使用了@HiltAndroidApp注解,Hilt生成了MyApplication_GeneratedInjector接口,这个接口可以理解成我们在Dagger2中自己定义的ApplicationComponent接口,不过这个地方的接口没有装载Module也没有绑定作用域。代码如下:

public interface MyApplication_GeneratedInjector {
  void injectMyApplication(MyApplication myApplication);
}

还给我们生成一个MyApplication_HiltComponents类,其内部生成了SingletonC抽象类,该抽象类实现了MyApplication_GeneratedInjector接口,这里装载了Module同时添加了作用域,这里才是一个真正的Component。代码如下:

 @Component(...)
 @Singleton
public abstract static class SingletonC implements MyApplication_GeneratedInjector,
    FragmentGetContextFix.FragmentGetContextFixEntryPoint,
    HiltWrapper_ActivityRetainedComponentManager_ActivityRetainedComponentBuilderEntryPoint,
    ServiceComponentManager.ServiceComponentBuilderEntryPoint,
    SingletonComponent,
    GeneratedComponent {
}

SingletonC抽象类的具体实现是SingletonCImpl,我们看一下该类的实例在什么地方被创建。

private static final class SingletonCImpl extends MyApplication_HiltComponents.SingletonC {
  private final SingletonCImpl singletonCImpl = this;

  private SingletonCImpl() {
  }

  @Override
  public void injectMyApplication(MyApplication myApplication) {
  }

  @Override
  public Set<Boolean> getDisableFragmentGetContextFix() {
    return Collections.<Boolean>emptySet();
  }

  @Override
  public ActivityRetainedComponentBuilder retainedComponentBuilder() {
    return new ActivityRetainedCBuilder(singletonCImpl);
  }

  @Override
  public ServiceComponentBuilder serviceComponentBuilder() {
    return new ServiceCBuilder(singletonCImpl);
  }
}

Hilt为我们生成了DaggerMyApplication_HiltComponents_SingletonC类,该类的内部类中build()方法实例化了SingletonCImpl对象。这个类和Dagger2给我们生成的DaggerApplicationComponent类功能一样。其代码如下:

public final class DaggerMyApplication_HiltComponents_SingletonC {

  public static final class Builder {
    private Builder() {
    }

    /**
     * @deprecated This module is declared, but an instance is not used in the component. This method is a no-op. For more, see https://dagger.dev/unused-modules.
     */
    @Deprecated
    public Builder applicationContextModule(ApplicationContextModule applicationContextModule) {
      Preconditions.checkNotNull(applicationContextModule);
      return this;
    }

    public MyApplication_HiltComponents.SingletonC build() {
      return new SingletonCImpl();
    }
  }
  }

我们通过上面的代码可以看出,Hilt给我生成的代码和Dagger2相似,唯一的区别是Dagger2需要我们自己去创建ApplicationComponent和Hilt帮我们自动生成了。

我们回顾一下Dagger2是怎么在Application中使用DaggerApplicationComponent的。Dagger2使用时,在自己创建的Applicaton中通过DaggerApplicationComponent创建ApplicationComponent。Dagger2代码如下:

public class MyApplication extends Application {

    private ApplicationComponent applicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        ApplicationModule module = new ApplicationModule(this);
        applicationComponent = DaggerApplicationComponent.builder().applicationModule(module).build();
    }

    public ApplicationComponent getApplicationComponent(){
        return applicationComponent;
    }
}

我们在看一下Hilt怎么使用的,Hilt同样创建了一个Hilt_MyApplication并且在onCreate方法中使用调用匿名内部类ApplicationComponentManagerget方法去创建实例。关键代码如下:

private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
  @Override
  public Object get() {
    return DaggerMyApplication_HiltComponents_SingletonC.builder()
        .applicationContextModule(new ApplicationContextModule(Hilt_MyApplication.this)).build();
  }
});

其具体代码如下:

public abstract class Hilt_MyApplication extends Application implements GeneratedComponentManagerHolder {
  private boolean injected = false;

  private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
    @Override
    public Object get() {
      return DaggerMyApplication_HiltComponents_SingletonC.builder()
          .applicationContextModule(new ApplicationContextModule(Hilt_MyApplication.this)).build();
    }
  });

  @Override
  public final ApplicationComponentManager componentManager() {
    return componentManager;
  }

  @Override
  public final Object generatedComponent() {
    return this.componentManager().generatedComponent();
  }

  @CallSuper
  @Override
  public void onCreate() {
    hiltInternalInject();
    super.onCreate();
  }

  protected void hiltInternalInject() {
    if (!injected) {
      injected = true;
      // This is a known unsafe cast, but is safe in the only correct use case:
      // MyApplication extends Hilt_MyApplication
      ((MyApplication_GeneratedInjector) generatedComponent()).injectMyApplication(UnsafeCasts.<MyApplication>unsafeCast(this));
    }
  }
}

Dagger2是在我们自己的创建的Application类中使用,而Hilt是在APT生成的Hilt_MyApplication类中使用,那Hilt_MyApplication又怎么能给我们的MyApplication类建立联系呢?

在Activity中使用了@AndroidEntryPoint注解生成了MainActivity_GeneratedInjector接口,该接口相当于Dagger2的ActivityComponent接口。Hilt这里的接口没有装载Module也没有绑定作用域,而Dagger2的Component我们通常会直接装载Module和绑定作用域,Hilt代码如下:

public interface MainActivity_GeneratedInjector {
  void injectMainActivity(MainActivity mainActivity);
}

这里创建了一个ActivityC类实现了MainActivity_GeneratedInjector接口,添加Subcomponent注解并装载了Module,同时给该Component绑定作用域ActivityScoped,到这里才给我们Dagger2中创建的Component完全一致。

@Subcomponent(...)
@ActivityScoped
public abstract static class ActivityC implements MainActivity_GeneratedInjector,
    Student.UserRepositoryEntryPoint,
    ActivityComponent,
    DefaultViewModelFactories.ActivityEntryPoint,
    HiltWrapper_HiltViewModelFactory_ActivityCreatorEntryPoint,
    FragmentComponentManager.FragmentComponentBuilderEntryPoint,
    ViewComponentManager.ViewComponentBuilderEntryPoint,
    GeneratedComponent {
  @Subcomponent.Builder
  abstract interface Builder extends ActivityComponentBuilder {
  }
}

我们看一下该Component是怎么在注入的,这里和Dagger2一样,就不详细介绍了,具体代码如下:

private static final class ActivityCImpl extends MyApplication_HiltComponents.ActivityC {
     
  ...
  
  @Override
  public void injectMainActivity(MainActivity arg0) {
    injectMainActivity2(arg0);
  }

  @CanIgnoreReturnValue
  private MainActivity injectMainActivity2(MainActivity instance) {
    MainActivity_MembersInjector.injectUser(instance, new User());
    MainActivity_MembersInjector.injectUserRepository(instance, new UserDataRepository());
    return instance;
  }
}

Dagger2在MaintActivity注入依赖,需要在MainActivity中手动的注入,那Hilt怎么实现不用手动注入的呢?Hilt通过APT为我们生成了Hilt_MainActivity,我们看一下Hilt_MainActivity代码在inject方法中注入。现在我们关键是需要知道Hilt_MainActivity与我们自己的MainActivity有什么关系?

public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder {

  Hilt_MainActivity() {
    super();
    _initHiltInternal();
  }

  Hilt_MainActivity(int contentLayoutId) {
    super(contentLayoutId);
    _initHiltInternal();
  }

  private void _initHiltInternal() {
    addOnContextAvailableListener(new OnContextAvailableListener() {
      @Override
      public void onContextAvailable(Context context) {
        inject();
      }
    });
  }

  protected void inject() {
    if (!injected) {
      injected = true;
      ((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
    }
  }
  
}

字节码修改

通过对上面的Application和Activity的分析,我们可以看出Hilt与Dagger2一样,区别在于Hilt可以自动生成Component、Hilt_ApplicationHilt_MainActivity。Hilt生成的Hilt_ApplicationHilt_MainActivity怎么与我们的MyApplicationMainActivity产生联系。

其实是Hilt把我们的MyApplication的父类替换成了Hilt_ApplicationMainActivity父类替换成了Hilt_MainActivity。那是如何替换的呢?

这个是使用gradle插件,在编译时利用Android Transform+Javassit动态修改字节码实现的,也可以使用Android Transform+ASM实现,对于这种技术不了解的自己去查询资料。

总结

Hilt实现是和Dagger2一样生成一些样板代码,然后利用字节码动态修改技术修改我们的父类,从而实现自动注入。