共计 16492 个字符,预计需要花费 42 分钟才能阅读完成。
介绍
Dagger Hilt (这名字起的溜 ………..)
官方描述其设计目的:
- To simplify Dagger-related infrastructure for Android apps.
- To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.
- To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).
简单说就是 Dagger Android 的瘦身包,使依赖注入在 Android 开发中标准化、简单化。
集成
首先在项目级别的 build.gradle 文件中添加以下内容,这将使我们能够访问 hilt gradle 插件:
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
然后在应用程序级别的 build.gradle 文件并应用此插件:
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
最后,在应用程序级别 build.gradle 文件中添加所需的 hilt 依赖项:
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
这样 Hilt 就可以使用了。
Hilt Application
按照官方要求,首先需要在自定义的 Application 类中添加 @HiltAndroidApp
注解:
@HiltAndroidApp
class APP:Application()
这有什么作用?以下来自官方描述:
All apps using Hilt must contain an Application
class annotated with @HiltAndroidApp
. @HiltAndroidApp
kicks off the code generation of the Hilt components and also generates a base class for your application that uses those generated components. Because the code generation needs access to all of your modules, the target that compiles your Application
class also needs to have all of your Dagger modules in its transitive dependencies.
Just like other Hilt Android entry points, Applications are members injected as well. This means you can use injected fields in the Application after super.onCreate()
has been called.
在 Daager2
中,需要 Application 继承 DaggerApplication
,并且还需要创建 Application 的Module
。
这里只需要使用@HiltAndroidApp
的注解就可以完成对 Application 的依赖注入,由 Hilt gradle 插件生成对应的文件
构建后我们看到在 app/build/generated/source/kapt/debug/
目录下生成了一个 Hilt_APP
的抽象类:
/**
* A generated base class to be extended by the @dagger.hilt.android.HiltAndroidApp annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation. */
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator")
public abstract class Hilt_APP extends Application implements GeneratedComponentManager<Object> {private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
@Override
public Object get() {return DaggerAPP_HiltComponents_ApplicationC.builder()
.applicationContextModule(new ApplicationContextModule(Hilt_APP.this))
.build();}
});
protected final ApplicationComponentManager componentManager() {return componentManager;}
@Override
public final Object generatedComponent() {return componentManager().generatedComponent();}
@CallSuper
@Override
public void onCreate() {
// This is a known unsafe cast, but is safe in the only correct use case:
// APP extends Hilt_APP
((APP_GeneratedInjector) generatedComponent()).injectAPP(UnsafeCasts.<APP>unsafeCast(this));
super.onCreate();}
}
- ApplicationComponentManager的声明
- 在 onCreate 函数中注入 Application 类
看下里面涉及到的~
ApplicationComponentManager
public final class ApplicationComponentManager implements GeneratedComponentManager<Object> {
private volatile Object component;
private final Object componentLock = new Object();
private final ComponentSupplier componentCreator;
public ApplicationComponentManager(ComponentSupplier componentCreator) {this.componentCreator = componentCreator;}
@Override
public Object generatedComponent() {if (component == null) {synchronized (componentLock) {if (component == null) {component = componentCreator.get();
}
}
}
return component;
}
}
主要用于管理应用程序中的 Hilt Component 的创建
- 构造中创建 ComponentSupplier 实例。
-
generatedComponent(): 通过对象锁获取
ComponentSupplier
类中的 Object,并负责在 onCreate 函数中将依赖项注入到我们的应用程序类中。
ComponentSupplier
提供 component
的接口
/**
* Interface for supplying a component. This is separate from the Supplier interface so that
* optimizers can strip this method (and therefore all the Dagger code) from the main dex even if a
* Supplier is referenced in code kept in the main dex.
*/
public interface ComponentSupplier {Object get();
}
ApplicationC
代码比较长就不贴出来了,看下 builder:
import dagger.hilt.android.internal.builders.ActivityComponentBuilder;
import dagger.hilt.android.internal.builders.ActivityRetainedComponentBuilder;
import dagger.hilt.android.internal.builders.FragmentComponentBuilder;
import dagger.hilt.android.internal.builders.ServiceComponentBuilder;
import dagger.hilt.android.internal.builders.ViewComponentBuilder;
import dagger.hilt.android.internal.builders.ViewWithFragmentComponentBuilder;
在 ComponentSupplier 实现的内部,我们可以看到对 ApplicationC(Application 组件)类的引用。DaggerAPP_HiltComponents_ApplicationC是生成的应用程序组件,它充当 Hilt 在我们的应用程序中使用的(Activity,Fragment,Service,View 等组件)的全局容器。
GeneratedInjector
@OriginatingElement(topLevelClass = APP.class)
@GeneratedEntryPoint
@InstallIn(ApplicationComponent.class)
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.InjectorEntryPointGenerator")
public interface APP_GeneratedInjector {void injectAPP(APP aPP);
}
接口类提供 injectAPP
方法为外部类提供了一个访问点,以触发应用程序 Component 的注入。
ApplicationContextModule
@Module
@InstallIn(ApplicationComponent.class)
public final class ApplicationContextModule {
private final Context applicationContext;
public ApplicationContextModule(Context applicationContext) {this.applicationContext = applicationContext;}
@Provides
@ApplicationContext
Context provideContext() {return applicationContext;}
@Provides
Application provideApplication() {return (Application) applicationContext.getApplicationContext();}
}
主要是提供 ApplicationContext
,通过@InstalIIn 注入到 ApplicationComponent
便于后续使用
这里有个@ApplicationContext
这是个qualifers 限定符,Hilt 还提供了一个@ActivityContext
,
例如:
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) {...}
@Singleton
class NetWorkUtils @Inject constructor(@ApplicationContext private val context: Context) {fun isNetworkConnected(): Boolean {.....}
}
可以直接作为 @Provides
方法或 @Inject
构造的参数使用。
流程
Hilt Components
介绍
在之前 Dagger-Android 中,我们必须创建诸如 ActivityScope,FragmentScope 之类的范围注释,以管理对象的生命周期,
而这里只要使用 @InstallIn
的注解,就可以委托 Hilt
帮我们管理
组件的生存期:
组件 | 范围 | 创建 | 销毁 |
---|---|---|---|
ApplicationComponent |
@Singleton |
Application#onCreate() |
Application#onDestroy() |
ActivityRetainedComponent |
@ActivityRetainedScope |
Activity#onCreate() 链接
|
Activity#onDestroy() 链接
|
ActivityComponent |
@ActivityScoped |
Activity#onCreate() |
Activity#onDestroy() |
FragmentComponent |
@FragmentScoped |
Fragment#onAttach() |
Fragment#onDestroy() |
ViewComponent |
@ViewScoped |
View#super() |
View 销毁 |
ViewWithFragmentComponent |
@ViewScoped |
View#super() |
View 销毁 |
ServiceComponent |
@ServiceScoped |
Service#onCreate() |
Service#onDestroy() |
在 @InstallIn
模块中确定绑定范围时,绑定上的范围必须与 component 的范围匹配。例如,@InstallIn(ActivityComponent.class)
模块内的绑定只能用限制范围@ActivityScoped
。
例如我们需要在 App 中共享 OkHttp 的配置:
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {
@Provides
fun provideBaseUrl() = "...."
@Provides
@Singleton
fun provideOkHttpClient() = if (BuildConfig.DEBUG) {val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()} else OkHttpClient
.Builder()
.build()
@Provides
@Singleton
fun provideRetrofit(
okHttpClient: OkHttpClient,
BASE_URL: String
): Retrofit =
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java)
}
类似 Dagger 中:
@Module
class NetworkModule {
// Hypothetical dependency on LoginRetrofitService
@Provides
fun provideLoginRetrofitService(okHttpClient: OkHttpClient): LoginRetrofitService {...}
}
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {...}
这里通过 @InstallIn(ApplicationComponent::class)
让 Hilt 帮我们管理 ApplicationModule 的生命周期
Module
module 的使用基本和 dagger 中一样, 用来提供一些无法用构造 @Inject
的依赖, 比如接口, 第三方库类型, Builder 模式构造的对象等.
-
@Module
: 标记一个 module, 可以是一个object
. -
@Provides
: 标记方法, 提供返回值类型的依赖. 这里就不需要手动添加到@Component(modules = ...)
-
@Binds
: 标记抽象方法, 返回接口类型, 接口实现是方法的唯一参数.
interface AnalyticsService {fun analyticsMethods()
}
// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(...) : AnalyticsService {...}
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(analyticsServiceImpl: AnalyticsServiceImpl): AnalyticsService
}
@Provides
与 @Binds
的区别:
按照官方说 @Binds
需要 module 是一个 abstract class,@Provides
需要 module 是一个 object. 而且 @Binds
需要在方法参数里面明确指明接口的实现类
但是 @Provides
这么用也是可以的。
Qualifier
如果要提供同一个接口的不同实现, 可以用不同的注解来标记. (类似于 dagger
中是@Named
).
什么意思? 比如我们缓存接口有内存和磁盘两种实现:
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheInMemory
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheInDisk
module 中提供的时候用来标记相应的依赖:
@InstallIn(ApplicationComponent::class)
@Module
object CacheModule {
@CacheInMemory
@Singleton
@Provides
fun getCacheInMemory(memoryImpl: CacheSourceMemoryImpl): CacheSource = memoryImpl
@CacheInDisk
@Singleton
@Provides
fun getCacheInDisk(diskImpl: CacheSourceDiskImpl): CacheSource = diskImpl
}
Android types
@AndroidEntryPoint
在 Dagger2
中,对 Activity 和 Fragment 的注入依赖的使用比较麻烦。
@Module
abstract class ActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = [MainActivityFragmentModule::class])
internal abstract fun contributeMainActivity(): MainActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributeMovieDetailActivity(): MovieDetailActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributeTvDetailActivity(): TvDetailActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributePersonDetailActivity(): PersonDetailActivity}
在 Hilt
中就比较简单了,只需要 @AndroidEntryPoint
的注解。相当于上面的@ContributesAndroidInjector
。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//
private val mJokesViewModel: JokesViewModel by viewModels()
private val mBinding: ActivityMainBinding by viewBinding {ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mJokesViewModel.jokes.observe(this, Observer {........}
}
}
我们不需要编写 AndroidInjection.inject(this)
或扩展 DaggerAppCompatActivity
类。
这里官方给了限定范围:
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
Hilt currently only supports activities that extend ComponentActivity
and fragments that extend androidx library Fragment
, not the (now deprecated) Fragment
in the Android platform.
Hilt
目前不直接支持 content providers.
@EntryPoint
Hilt 支持最常用的 Android 组件, 对于默认不支持的类型, 如果要做字段注入, 需要用@EntryPoint
.
这里只是限制了 字段注入 的情况, 对于自定义类型我们一般习惯于用构造注入。
必须与 @InstallIn
搭配使用,将 interface
标记为入口点,这样就可以使用 Hilt
容器提供的依赖对象们.
如果要 content provider
使用Hilt
:
class ExampleContentProvider : ContentProvider() {
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface ExampleContentProviderEntryPoint {fun analyticsService(): AnalyticsService
}
...
}
要访问@EntryPoint
,使用静态方法 EntryPointAccessors
:
class ExampleContentProvider: ContentProvider() {
...
override fun query(...): Cursor {val appContext = context?.applicationContext ?: throw IllegalStateException()
val hiltEntryPoint =
EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)
val analyticsService = hiltEntryPoint.analyticsService()
...
}
}
appContext
参数要与 @InstallIn(ApplicationComponent::class)
保持一致。
@EntryPoint
除了解决上述字段注入的 问题,还有什么场景可以发挥用处?
生命周期匹配
我们利用 FragmentFactory 在Activity
与 Fragment
之间用构造函数传递数据:
class ContainerActivity : AppCompatActivity() {private var fragmentDataTest = FragmentDataTest()
private val mBinding: ActivityContainerBinding by viewBinding {ActivityContainerBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {supportFragmentManager.fragmentFactory = EntryPointFragmentFactory(fragmentDataTest)
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
setSupportActionBar(mBinding.toolbar)
}
}
看上述代码,在没有用 Hilt 之前 fragmentFactory 的设置应该是在 super.onCreate()
之前,但是如果用 Hilt 就不能这么写了,因为在
之前 Hilt Application 中已经说过,Hilt 是在 super.onCreate()
中进行依赖注入的,在 Hilt_ContainerActivity
类中:
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {inject();
super.onCreate(savedInstanceState);
}
<u>Injection happens in super.onCreate().</u>
所以,如果我们要在 ContainerActivity 中使用 FragmentFactory 就该在 super.onCreate()之后,那么问题来了 … 我们知道FragmentFactory
负责在 Activity 和 parent Fragment 初始化 Fragment,应该在 super.onCreate()
之前关联 FragmentFactory 和 FragmentManager 设置。如果在使用 Hilt 注入之后还是放在编译会报错:
UninitializedPropertyAccessException: lateinit property mFragmentFactory has not been initialized
所以我们将 FragmentManager
绑定 FragmentFactory
的动作放在 super.onCreate()
之后:
@AndroidEntryPoint
class ContainerActivity : AppCompatActivity() {
@Inject lateinit var mFragmentFactory: EntryPointFragmentFactory
private val mBinding: ActivityContainerBinding by viewBinding {ActivityContainerBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
supportFragmentManager.fragmentFactory = mFragmentFactory
setContentView(mBinding.root)
mFragmentFactory.fragmentDataTest.setData("xxx")
}
}
这样就可以了,但是要注意了:
<u> 如果上面的 ContainerActivity
被意外终止而触发重建的话是会报错的:</u>
java.lang.RuntimeException: Unable to start activity ComponentInfo{tt.reducto.daggerhiltsample/tt.reducto.daggerhiltsample.ui.entry.ContainerActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment tt.reducto.daggerhiltsample.ui.entry.EntryPointFragment: could not find Fragment constructor
...
Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment tt.reducto.daggerhiltsample.ui.entry.EntryPointFragment: could not find Fragment constructor
可以定位 Hilt_ContainerActivity
中的 super.onCreate(savedInstanceState);
这里就涉及到FragmentManager 的状态保存与恢复
Fragment 依附于 Activity,而 Fragment 的状态保存与恢复机制也是由 Activity 的相关方法触发。Activity 的方法 onSaveInstanceState(Bundle outState)
的参数 outState 是系统在状态需要保存时用来提供存放持久化状态的容器,当系统触发状态保存时,Activity 下的 Fragment 的所有状态便通过 mFragments 的 saveAllState
方法保存在了 FRAGMENTS_TAG
键中,在 Activity 重建 的 时候通过 mFragments.restoreAllState 入口将状态恢复
在此期间 Fragment 的都会交由 FragmentManager 管理,包括我们需要注意的如何新建一个 Fragment 对象:
Fragment.instantiate(…)方法会根据所给的 class name 加载对应的 Class 类,调用 clazz.newInstance()
新建一个全新的 Fragment 对象:
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
...
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
...
}
综上,Activity 重建后无法利用 FragmentFactory 重新创建 Fragment,所以官方 FragmentFactory
文档才有这么一句话:
- Before the system restores the Fragment, if the Fragment is being recreated after a configuration change or the app’s process restart.
了解了 FragmentManager
绑定 FragmentFactory
的动作在 super.onCreate()
之前执行的必要性后,我们再来利用 Hilt 提供的特性解决声明周期不匹配的问题
@EntryPoint
@InstallIn(ActivityComponent::class)
interface ContainerActivityEntryPoint {fun getFragmentManager(): FragmentManager
fun getFragmentFactory(): EntryPointFragmentFactory}
我们利用 @EntryPoint
从ContainerActivityEntryPoint
对象中获取 FragmentManager 和 EntryPointFragmentFactory 的引用:
@AndroidEntryPoint
class ContainerActivity : AppCompatActivity() {
private val mBinding: ActivityContainerBinding by viewBinding {ActivityContainerBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {val entryPoint = EntryPointAccessors.fromActivity(this,ContainerActivityEntryPoint::class.java)
val mFragmentFactory = entryPoint.getFragmentFactory()
entryPoint.getFragmentManager().fragmentFactory = mFragmentFactory
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mFragmentFactory.fragmentDataTest.setData("xxxxxxxxxxx")
}
......
}
从静态类 EntryPointAccessors
中获取定义的实例,有点 SOLID 中接口隔离原则的意思。
再次模拟 Activity 重建状态,一切正常。
ViewModel
之前 Dagger 注入 ViewModel 时比较麻烦,在构造函数中创建带有参数的 ViewModel 实例,每个 ViewModel
必须实现一个全局或每个 ViewModelFactory
,并实现ViewModelModule
来绑定ViewModel
。
@Module
internal abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModels(mainActivityViewModel: MainActivityViewModel): ViewModel
......
}
而 Hilt
更简单:
class JokesViewModel @ViewModelInject constructor(
private val jokesRepository: JokesRepository,
private val netWorkUtils: NetWorkUtils,
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {........}
使用 @ViewModelInject
即可,JokesRepository
、NetWorkUtils
都是由 Hilt 注入的。
Hilt
将在后台生成相应的工厂类和东西。
这里有个 @Assisted
需要注意下:
因为在这之前 ViewModel
中注入 SavedStateHandle
是比较麻烦的,由于 @AssistedInject.Factory 修饰接口再通过
@AssistedInject 注入
ViewModel,最后还要通过
@AssistedModule` 中添加 ….. 太太太麻烦了
看下 Hilt
的@Assisted
描述:
/**
* Marks a parameter in a {@link androidx.hilt.lifecycle.ViewModelInject}-annotated constructor
* or a {@link androidx.hilt.work.WorkerInject}-annotated constructor to be assisted
* injected at runtime via a factory.
*/
// TODO(danysantiago): Remove and replace with dagger.assisted.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Assisted {
}
意思是 Worker
通过 @WorkerInject
构造函数注入时要通过 @Assisted
修饰 Context
和WorkerParameters
类似:
class ExampleWorker @WorkerInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
workerDependency: WorkerDependency
) : Worker(appContext, workerParams) {...}
简单例子
利用 Hilt 写个超简单的请求列表的小例子:Github 传送
总结
- 不用手动创建 Component.
- 不用手动调用 inject()进行 字段注入.
- 不用在 Application 中保存 component.
- 提供一些 Scope 管理他们的生命周期,只能在对应的范围内进行使用。
- 提供了一些默认依赖, 比如 Context.
以上就是 Dagger Hilt 简单上手,
目前 Hilt 还处于 alpha 状态,依赖 kapt,等 KSP 成熟之后预计效率会有进一步提升。
当然 koin 玩起来更舒心。
参考
https://developer.android.com…
https://developer.android.com…
http://joebirch.co/android/ex…
https://www.techyourchance.co…