上篇文章我们讲解了Dagger2这把“匕首”,下面我们继续了解如何给“匕首”装上“刀柄”。如果还没有看过上篇对Dagger2的分析,建议先看对Dagger2的分析,再来看这篇效果更佳。揭开Dagger2的神秘面纱
Hilt是什么
Hilt和Dagger2一样,是一个依赖注入库,但是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一样,该注解可以使用在类的成员变量或类的构造方法上。
- 在成员变量使用该注解,表示该对象需要Hilt帮我们注入。如下代码,是告诉Hilt这里的User对象需要你帮我注入进来
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@JvmField
@Inject
var user:User?=null
...
}
- 在类的构造方法中使用
@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 组件 | 注入器面向的对象 |
|---|---|
SingletonComponent | Application |
ActivityRetainedComponent | 不适用 |
ViewModelComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注解的 View |
ServiceComponent | Service |
组件的生命周期
| 生成的组件 | 创建时机 | 销毁时机 |
|---|---|---|
SingletonComponent | Application#onCreate() | Application 已销毁 |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ViewModelComponent | ViewModel 已创建 | ViewModel 已销毁 |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | View 已销毁 |
ViewWithFragmentComponent | View#super() | View 已销毁 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
组件的作用域
| Android 类 | 生成的组件 | 作用域 |
|---|---|---|
Application | SingletonComponent | @Singleton |
Activity | ActivityRetainedComponent | @ActivityRetainedScoped |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
带有 @WithFragmentBindings 注解的 View | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @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方法中使用调用匿名内部类ApplicationComponentManager的get方法去创建实例。关键代码如下:
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_Application和Hilt_MainActivity。Hilt生成的Hilt_Application和Hilt_MainActivity怎么与我们的MyApplication和MainActivity产生联系。
其实是Hilt把我们的MyApplication的父类替换成了Hilt_Application,MainActivity父类替换成了Hilt_MainActivity。那是如何替换的呢?
这个是使用gradle插件,在编译时利用Android Transform+Javassit动态修改字节码实现的,也可以使用Android Transform+ASM实现,对于这种技术不了解的自己去查询资料。
总结
Hilt实现是和Dagger2一样生成一些样板代码,然后利用字节码动态修改技术修改我们的父类,从而实现自动注入。