官方文章参考文章一、概念依赖管理不仅可以很好地解决对象繁琐的初始化逻辑还可以很好的实施控制反转的编码思想。类中使用的某个对象不是在这个类中实例化的如汽车类内部创建轮子对象而是通过外部注入在外部初始化后传入后使用汽车类不再自己造轮子实现解耦可以很容易更换不同类型的轮子或测试时模拟一个轮子而不用修改汽车类这种实现方式就称为依赖注入 Dependency Injection简称DI也叫控制反转 Inversion of Control简称 IoC对象的创建交给外部。不只是单纯的传参作用使得我们的代码更加模块化每个类都只关注自己的职责不需要关心其依赖对象的创建和管理使得代码更容易重用对于 MVVM 架构UI层的 Activity/Fragment 实例不是由我们操心创建的VM层的 ViewModel 实例 Jetpack 提供了专门的 API 来获取 但是 Respository 层的仓库实例创建就成了问题VM层依赖 Repository 层但不应该负责创建实例如果设置成单例谁都拥有了对它的依赖关系违背了不能夸层级的规则如UI跨过VM直接通信Repository借助依赖注入框架就能很好的解决。不然UI中要依次创建并传递ApiService→DataResource→Repository→ViewModel对于 Android 经常需要访问一些共享资源或服务如网络请求、数据库访问、ViewModel等没有 DI 就需要手动创建这些对象导致代码重复和耦合难以单元测试使用 DI 就可以统一配置然后在需要的地方自动注入。对于 APP/Moudle 不同模块需要访问一些共享的服务没有 DI 就需要通过复杂的方式获取这些服务导致代码复杂难管理使用 DI 可以在 APP 中配置后在 Moudle 中自动注入。对于单例如数据库、网络客户端使用 DI 可以轻松管理避免了手动管理的复杂性。构造注入将对象B通过构造传参给classA。有些对象无法通过实例化使用如Activity。字段注入将对象C通过函数设置给classA的字段也叫setter注入、属性注入。如果类的依赖类型非常多而且要严格执行顺序如造车前要造好轮子造轮子又需要先造好螺丝和轮胎随着项目越发复杂需要编写很多模板代码耦合度也更高手动注入就容易出错。方法注入将对象D传入到classA的方法中仅在该方法中使用。工厂注入ClassA调用工厂类生产对象调用和生产不在同一个地方不利于修改测试。单例注入ClassA调用单例类获取其持有的对象对象的生命周期难以管理通常并不需要存在于整个APP生命周期指定在特定的生命周期又需要添加很多判断。自动注入基于反射的解决方案可以在运行时连接依赖类型过多使用反射方法会影响程序的运行效率而且反射方法在编译阶段是不会产生错误的导致只有在程序运行时才可以验证使用方式是否正确。Square开发的Dagger。静态解决方案通过注解解决了反射的弊端还在编译时就自动生成依赖注入的代码不会增加运行耗时。在编译时就可以发现依赖注入使用的问题。谷歌基于Dagger开发出Dagger2和HiltDagger2使用起来复杂繁琐而Hilt专门面向Android开发提供更简单的实现方式和其它Jetpack组件能更好的协同工作。Dagger是“匕首”Hilt是“把柄”给你安全的把柄避免用不好锋利的匕首误伤自己。二、添加依赖最新版本2.1 Project.gradleplugins { id com.google.dagger.hilt.android version 2.44 apply false }2.2 app.gradleplugins { id kotlin-kapt id com.google.dagger.hilt.android } dependencies { implementation com.google.dagger:hilt-android:2.44 kapt com.google.dagger:hilt-compiler:2.44 } // Allow references to generated code kapt { correctErrorTypes true }三、初始化 HiltAndroidApp必须自定义一个Application为其添加 HiltAndroidApp 注解会触发 Hilt 的代码生成生成的这一 Hilt 组件会附加到 Application 对象的生命周期作为应用的父组件可为其它组件提供依赖类型。注解使用位置说明HiltAndroidAppApplication初始化Hilt容器作为全局依赖入口。HiltAndroidApp class APP : Application() {}四、将依赖项注入到Android类中 AndroidEntryPoint使用 AndroidEntryPoint 对 Android 类添加注解后就可以向它里面的字段注入依赖了。声明一个延迟初始化lateinit var的属性并添加 Inject 注解。为某个 Android 类添加注解则必须为依赖于该类的其它 Android 类添加注解例如为 FragmentA 添加注解则必须为所有使用该 FragmentA 的 Activity 添加注解。注入的字段不能为 private 会导致编译错误。注解使用位置Android类说明AndroidEntryPointActivity仅支持继承自 ComponentActivity 的 Activity如AppCompatActivity。Fragment仅支持继承自 androidx.Fragment 的 Fragment不支持 android.app.Fragment。ViewServiceBroadcastReceiverAndroidEntryPoint class MainActivity : AppCompatActivity() { //不能为private //使用 lateinit 延迟到 Hilt 决定何时如何实例化 //lateinit属性未手动初始化依赖注入提供了实例所以不会报错 Inject lateinit var logBean: LogBean //依赖了LogBean类型LogBean要进行绑定信息 }五、通过构造提供实例 Inject为目标类型的构造函数添加 Inject 注解向 Hilt 告知如何创建目标类型的实例。不像 Android 类只能字段注入这样就是构造注入。若构造函数有参数参数类型也要进行绑定信息A依赖B对A依赖自然要能对B进行依赖private 修饰的构造函数无法注入使用 Module 方式。//无参 class LogBean Inject constructor() {} //有参 data class LogBean Inject constructor( val userName: String, //又依赖了String类型String也要进行绑定信息 val time: TimeBean //又依赖了TimeBean类型TimeBean也要进行绑定信息 )六、通过模块提供实例 Module目标类型的构造函数我们无法添加 Inject 注解时private修饰的构造、接口类型没有构造、不属于自己的类型如String、必须使用构建器模式创建实例如Retrofit就需要通过 Module 手动创建一个模块类通过 InstallIn(XXX::class) 将模块装载到指定的 Hilt 组件中不同的Android类有对应的Hilt组件并通过函数提供实例 Binds Provids模块便可以为对应的 Android 类提供依赖对象的创建、注入、销毁。注解使用位置说明Module类告知 Hilt 如何提供该类型的实例。InstallIn告知模块将装载到哪个 Hilt 组件可用于哪些Android 类。Binds函数提供接口实例。必须对抽象函数注解所以类也是抽象的。返回值告知提供哪种接口类型的实例参数告知该接口的具体实现类型该类型也需要对构造注释。Provides提供类实例。可以对class注解若只包含provides函数定义为object更高效。返回值告知提供哪种类型的实例参数告知提供的实例还依赖了哪些类型这些类型也需要对构造注释函数体告知如何创建实例每当需要提供实例时都会执行函数体。SingletonProvidesInject用于Provides注解的方法或Inject注解的构造函数告诉Hilt提供的依赖是单例的。6.1 限制在Android中的生效范围 InstallIn模块通过 InstallIn(XXX::class) 装载到指定的 Hilt 组件中组件便可以在对应的 Android 类范围中提供依赖对象的创建、注入、销毁。例如InstallIn(ActivityComponent::class)这个模块安装到 Activity 中那其中的 Fragment、View 也可以使用但是其它地方就不行了。不会为 Broadcast 生成组件会直接从 SingletonComponent 注入广播接收器。没有 ContentProvider 因为 Hilt 从 Applicaiton#onCreate() 中开始的而 ContentProvider 在这之前就得到执行。ActivityRetainedComponent推荐使用不会随着Activity旋转销毁而销毁而ActivityComponentd每次都会重新生成组件。Hilt组件生效Android类范围SingletonComponentApplicationActivityRetainedComponentApplicationViewModelComponentSavedStateHandleServiceComponentApplication、ServiceActivityComponentApplication、ActivityViewComponentApplication、Activity、ViewFragmentComponentApplication、Activity、FragmentViewWithFragmentComponentApplication、Activity、Fragment、View6.2 提供单个实例6.2.1 提供抽象类/接口实例 Binds函数参数的类型即接口的实现类也要进行绑定依赖。interface IWork { fun show() } class WorkImpl Inject constructor(): IWork { override fun show() {} } Module InstallIn(ActivityComponent::class) abstract class WorkModule { //定义为抽象函数因为我们不需要实现 //函数名叫什么无所谓因为我们不会去调用 //返回值类型必须是接口类型表示给 IWork 类型提供实例 //函数参数接收了什么实现类类型就提供什么实例 Binds abstract fun bindIWork(workImpl: WorkImpl): IWork //又依赖了WorkImpl类型即实现类也要进行绑定构造 }6.2.2 提供普通类实例 Provides若函数有参数参数类型也要进行绑定依赖。Module InstallIn(SingletonComponent::class) class RetrofitModule { Singleton Provides fun provideRetrofit(okHeepClient: OkHttpClient): Retrofit { //又依赖了OkHttpClient类型OkhttpClient也要进行绑定依赖 return Retrofit.Builder() .client(okHeepClient) .baseUrl(ApiService.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build() } }6.3 提供同类型不同实现的多个实例实际开发中可能需要创建同类型的多个不同实现的对象使用如 Student(张三) 和 Student(李四、String(A) 和 String(“B”)。上面的方式只能为目标类型提供相同实现的对象通过使用限定符来实现区分不同实现。6.3.1 使用 Named只需要对 Binds 或 Provids 注解的函数再使用 Named 注解通过传入唯一的 tag 来区分使用时也要加入对应 tag 让 Hilt 注入的时候选择对应的实例。Module InstallIn(ActivityComponent::class) object StringModule { Provides Named(One) fun providesOneString() One Provides Named(Two) fun providesTwoString() Two } AndroidEntryPoint class DemoFragment : Fragment() { Inject Named(One) lateinit var oneString: String Inject Named(Two) lateinit var twoString: String }6.3.2 使用自定义注解 Qualifier使用 Named 方式只能硬编码因为注解的特性不能传入一个静态的String很容易写错或后期重构容易遗漏。先根据需要的分类定义注解使用 Qualifier 声明作用是为相同类型注入不同实例使用 Retention 声明注解的作用范围AnnotationRetention.BINARY表示注解在编辑后会得到保留但是无法通过反射去访问这个注解这应该是最合理的一个注解作用范围用法和 Named 相似。Qualifier Retention(AnnotationRetention.BINARY) annotation class OneString Qualifier Retention(AnnotationRetention.BINARY) annotation class TwoString Module InstallIn(ActivityComponent::class) object StringModule{ Provides OneString fun providesOneString(): String One Provides TwoString fun providesTwoString(): String Two } AndroidEntryPoint class DemoFragment : Fragment() { Inject OneString lateinit var oneString: String Inject TwoString lateinit var twoString: String }七、动态传参辅助注入用于结合框架注入的依赖项和运行时参数。Hilt通过注入它知道的内容如你的数据库、API客户端等来帮助你而你提供只有在运行时才知道的内容如用户ID、搜索查询或配置数据。AssistedInject放在构造函数上而不是常规的Inject表明该类需要注入参数和运行时参数的混合。Assisted标记运行时参数。创建实例时需要动态提供的值。AssistedFactory定义一个工厂接口使用运行时数据创建实例。可以看做是连接 Hilt 和 动态参数 的桥梁。7.1 使用7.1.1 简单使用class UserRepository AssistedInject constructor( private val database: AppDatabase, //Hilt提供 Assisted private val userId: String //你动态提供 ) AssistedFactory interface UserRepositoryFactory { fun create(userId: String): UserRepository //传入动态参数返回实例 } HiltViewModel class UserProfileViewModel Inject constructor( //注入的是工厂 private val userRepositoryFactory: UserRepositoryFactory, savedStateHandle: SavedStateHandle ) : ViewModel() { //通过工厂使用动态参数创建实例 val repository: UserRepository userRepositoryFactory.create(userId) }7.1.2 ViewModel 使用//ViewModel HiltViewModel(assistedFactory ArticleViewModelFactory::class) class ArticleViewModel AssistedInject constructor( private val articleRepository: ArticleRepository, // 自动注入 Assisted private val articleId: String, // 运行时传参 ) : ViewModel() { val article articleRepository.getArticle(articleId) } //工厂 AssistedFactory interface ArticleViewModelFactory { fun create(articleId: String): ArticleViewModel } //Activity AndroidEntryPoint class ArticleActivity : ComponentActivity() { val viewModel: ArticleViewModel by viewModels( extrasProducer { defaultViewModelCreationExtras.withCreationCallbackArticleViewModelFactory { factory - factory.create(articleId) } } ) }7.1.3 WorkManager 使用Workers不需要工厂会自动处理工厂创建。只需用HiltWorker注解并用Assisted标记Context和WorkerParameters。HiltWorker class DataSyncWorker AssistedInject constructor( Assisted private val context: Context, Assisted private val params: WorkerParameters, private val syncRepository: SyncRepository, // 注入的 private val notificationHelper: NotificationHelper // 注入的 ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { return try { val dataToSync params.inputData.getString(KEY_SYNC_DATA) ?: syncRepository.syncData(dataToSync) notificationHelper.showSyncCompleteNotification() Result.success() } catch (e: Exception) { if (runAttemptCount 3) { Result.retry() } else { Result.failure() } } } companion object { const val KEY_SYNC_DATA sync_data } } HiltAndroidApp class MyApplication : Application(), Configuration.Provider { Inject lateinit var workerFactory: HiltWorkerFactory override fun getWorkManagerConfiguration(): Configuration { return Configuration.Builder() .setWorkerFactory(workerFactory) .build() } } val workRequest OneTimeWorkRequestBuilderDataSyncWorker() .setInputData( workDataOf(DataSyncWorker.KEY_SYNC_DATA to user_data) ) .build() WorkManager.getInstance(context).enqueue(workRequest)7.2 处理相同类型的多个参数为每个参数添加字符串标识符。标识符必须在构造函数和工厂之间完全匹配。这告诉Hilt如何正确映射参数。class MessageComposer AssistedInject constructor( private val messagingService: MessagingService, // 注入的 Assisted(senderId) private val senderId: String, Assisted(recipientId) private val recipientId: String, Assisted(messageType) private val messageType: String ) AssistedFactory interface MessageComposerFactory { fun create( Assisted(senderId) senderId: String, Assisted(recipientId) recipientId: String, Assisted(messageType) messageType: String ): MessageComposer }八、Hilt组件的生命周期和作用范围Hilt组件最大Android类生效范围可指定的作用域默认绑定的依赖类型创建实际~销毁时机SingletonComponentApplicationSingletonApplicationApplication:onCreate()~已销毁ActivityRetainedComponent不适用ActivityRetainedScopeApplicationActivity:onCreate()~onDestroy()ViewModelComponentViewModelViewModelScopeSavedStateHandleViewModel:已创建~已销毁ServiceComponentServiceServiceScopedApplication、ServiceService:onCreate()~onDestroy()ActivityComponentActivityActivityScopedApplication、ActivityActivity:onCreate()~OnDestroy()ViewComponentViewViewScopedApplication、Activity、ViewView:super()~已销毁FragmentComponentFragmentFragmentScopedApplication、Activity、FragmentFragment:onAttach()~onDestroy()ViewWithFragmentComponentWithFragmentBindings 注解的ViewViewScopedApplication、Activity、Fragment、ViewView:super()~已销毁8.1 组件的生命周期组件与对应的 Android 类有着相同的生命周期不然会内存泄漏。ActivityRetainedComponent 在配置更改后仍然存在因此它在第一次调用 Activity#onCreate() 时创建在最后一次调用 Activity#onDestroy() 时销毁。Hilt组件创建时机 - 销毁时机SingletonComponentApplication#onCreate() - 已销毁ActivityRetainedComponentActivity#onCreate() - onDestroy()ViewModelComponentViewModel 已创建 - ViewModel 已销毁ServiceComponentService#onCreate() - onDestroy()ActivityComponentActivity#onCreate() - onDestroy()ViewComponentView#super() - View 已销毁FragmentComponentFragment#onAttach() - onDestroy()ViewWithFragmentComponentView#super() - View 已销毁8.2 组件的作用域范围默认情况下 Hilt 中所有的绑定都没有限定作用域也就是每次代码访问字段时都会新建一个实例当需要共享一个实例时就需要给绑定限定作用域即提供的实例在对应的Android类生效范围中为单例同一个Activity中保持单例不同Activity中实例不同。作用域与组件一一对应指定不一致报错Hilt组件作用域SingletonComponentSingletonActivityRetainedComponentActivityRetainedScopedViewModelComponentViewModelScopedServiceComponentServiceScopedActivityComponentActivityScopedViewComponentViewScopedFragmentComponentFragmentScopedViewWithFragmentComponentViewScopedActivityScoped //指定作用域 class Demo Inject constructor() {...} Module InstallIn(ActivityComponent::class) class StringModule { ActivityScoped //指定作用域 Provides fun providesOneString() One }8.3 组件的层次结构当一个依赖类型的作用域是整个APP那在Activity中肯定可以访问到作用域存在包含关系也就是组件存在层次结构。当模块装载到组件后模块所绑定的依赖类型也可以用于该组件层次结构以下的子组件绑定。ViewComponent 可以使用 ActivityComponent 中绑定的依赖类型如果还需要使用 FragmentComponent 中的绑定并且视图是 Fragment 的一部分用该将 WithFragmentBindings 注解和 AndroidEntryPoint 一起使用。8.4 预置Qualifier获取Context、Activity可以使用 ApplicationContext 和 ActivityContext 来获得上下文有参构造如果包含了 Application 或 Activity 不用额外处理。如果使用了 Singleton 但构造参数使用了 ActivityContext 会报错全局可用却依赖一个更小的Activity的上下文改成 ActivityScoped、FragmentScoped小依赖大没问题或者直接删掉都可以。必须是 Application 或 Activity 类型它们的子类无法被识别对于获取自定义 Application 可通过自定义一个 Module 提供在获取的方法中转换处理。class Demo Inject constructor( val activity: Activity, val app: Application, ActivityContext val context: Context )Module InstallIn(SingletonComponent::class) class MyApplicationModule { Provides fun provideMyApplication(application: Application): MyApplication { return application as MyApplication } } class Demo Inject constructor( val app: MyApplication ) {...}九、ViewModel 中使用给 ViewModel 添加 HiltViewModel并对构造函数使用 Inject。在带有 AndroidEntryPoint 的 Activity/Fragment 中可以使用 ViewModelProvider 或 by viewModels() 来获取实例。实例由 ViewModelComponent 提供它和 ViewModel 有相同的生命周期因此可在配置更改后继续存在。如果需要每次访问获取的实例是同一个使用 ViewModelScope 限制作用域。HiltViewModel class DemoViewModel Inject constructor( private val avedStateHandle: SavedStateHandle, private val repository: DemoRepository ) : ViewModel() {} AndroidEntryPoint class DemoActivity : AppCompatActivity() { private val viewModel: DemoViewModel by viewModels() }十、Navigation 中使用另见Compose中使用如果 ViewModel 的作用域限定为导航图使用 hiltNavGraphViewModels( )该函数可与带有 AndroidEntryPoint 的Fragment 搭配使用。implementation androidx.hilt:hilt-navigation-fragment:1.0.0val viewModel: ExampleViewModel by hiltNavGraphViewModels(R.id.my_graph)十一、实际使用场景举例是自己的类Inject constructor是接口要绑实现Binds是第三方类/需要配置逻辑Provides同类型有多个实例Qualifier只被一个页面用且有页面级状态非 Singleton 的 Component10.1 SharedPreference注入// 创建一个Hilt模块用于提供SharedPreferences实例 Module InstallIn(SingletonComponent::class) //作用范围全局 class SharedPreferencesModule { Singleton //全局单例 Provides fun provideSharedPreferences(ApplicationContext context: Context): SharedPreferences { return context.getSharedPreferences(pref_name, Context.MODE_PRIVATE) } }// 在Activity中你可以使用Inject注解来请求一个SharedPreferences实例。 AndroidEntryPoint class MainActivity : AppCompatActivity() { Inject lateinit var sharedPreferences: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 使用SharedPreferences val editor sharedPreferences.edit() editor.putString(key, value) editor.apply() } }10.2 多模块项目假设有一个data模块和一个app模块data模块提供了一个Repository类app模块需要使用这个Repository。//首先在data模块中你定义了一个Repository类并用Inject注解标记其构造函数 class Repository Inject constructor() {}//然后在app模块中你可以直接在需要的地方注入Repository AndroidEntryPoint class MainActivity : AppCompatActivity() { Inject lateinit var repository: Repository }10.3 RetrofitNetworkModule是一个Hilt模块它在应用级别的组件SingletonComponent中提供了一个Retrofit实例。有一个Repository类它需要这个Retrofit实例来发起网络请求。有一个ViewModel它需要这个Repository来获取数据。这就是一个典型的依赖链MyViewModel依赖于RepositoryRepository依赖于Retrofit。通过Hilt我们可以轻松地管理这个依赖链而无需手动创建和管理每个依赖。这使得代码更加清晰和直观也使得新成员更容易理解项目的结构。Module InstallIn(SingletonComponent::class) class NetworkModule { Singleton Provides fun provideRetrofit(): Retrofit { return Retrofit.Builder() .baseUrl(https://api.example.com/) .build() } }class Repository Inject constructor(private val retrofit: Retrofit) {}class MyViewModel ViewModelInject constructor( private val repository: Repository ) : ViewModel() {}10.4 数据库// 创建一个Hilt模块用于提供Room数据库实例 Module InstallIn(SingletonComponent::class) class DatabaseModule { Provides fun provideDatabase(ApplicationContext context: Context): MyDatabase { return Room.databaseBuilder( context, MyDatabase::class.java, database-name ).build() } Provides fun provideUserDao(database: MyDatabase): UserDao { return database.userDao() } } // 在需要UserDao的地方使用Inject注解来请求一个UserDao实例。 class UserRepository Inject constructor(private val userDao: UserDao) { // ... }
阅读完成 · 觉得有帮助?