Android Jetpack 系列Jetpackkotlin

Android Jetpack系列--9. Hilt使用详解

2021-09-14  本文已影响0人  今阳说

相关知识

依赖注入

什么是依赖项注入

  1. 在类中创建所需依赖项的实例
class CPU () {
    var name: String = ""
    fun run() {
        LjyLogUtil.d("$name run...")
    }
}
class Phone1 {
    val cpu = CPU()
    fun use() {
        cpu.run()
    }
}
  1. 通过父类或其他类获取
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = cm.getNetworkCapabilities(cm.activeNetwork)
LjyLogUtil.d("是否有网络连接:${networkCapabilities==null}")
  1. 以参数形式提供,可以在构造类时提供这些依赖项,或者将这些依赖项传入需要各个依赖项的函数;

Android中的依赖注入方式

手动依赖注入
1. 构造函数注入
//在构造类时提供这些依赖项
class Phone (private val cpu: CPU) {
    fun use() {
        cpu.run()
    }
}
val cpu = CPU()
val phone=Phone(cpu)
phone.use()
2. 字段注入(或 setter 注入)
//将依赖项传入需要依赖项的函数
class Phone {
    lateinit var cpu: CPU
    fun use() = cpu.run()
}
val phone = Phone()
val cpu = CPU()
phone.cpu = cpu
phone.use()
1. 使用越来越繁琐;
2. 产生大量模板代码;
3. 必须按顺序声明依赖项;
4. 很难重复使用对象;
自动依赖注入框架
1. 通过反射,在运行时连接依赖项;
2. 通过注解,编译时生成连接依赖项的代码;
3. kotlin 强大的语法糖和函数式编程;
1. Dagger:
Dagger 1.x版本:Square基于反射实现的,有两个缺点一个是反射的耗时,另一个是反射是运行时的,编译期不会报错。而使用难度较高,刚接触时有经常容易写错,造成开发效率底;
Dagger 2.x版本:Google基于Java注解实现的,完美解决了上述问题,
2. Koin
3. Hilt:

Hilt

Hilt使用流程

添加依赖项

//1. 配置Hilt的插件路径
buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
//2. 引入Hilt的插件
plugins {
    ...
    id 'dagger.hilt.android.plugin'
    id 'kotlin-kapt'
}
//3. 添加Hilt的依赖库及Java8
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  //对于 Kotlin 项目,需要添加 kotlinOptions
  kotlinOptions {
      jvmTarget = "1.8"
  }
}
dependencies {
    ...
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

Hilt 应用类

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

将依赖项注入 Android 类

1. 用@AndroidEntryPoint注释类;
2. 使用 @Inject 注释执行字段
3. 构造函数中使用 @Inject 注释
@AndroidEntryPoint
class HiltDemoActivity : AppCompatActivity() {
    @Inject
    lateinit var cpu: CPU
    ...
}

class CPU @Inject constructor() {
    var name: String = ""

    fun run() {
        LjyLogUtil.d("$name run...")
    }
}
带参数的依赖注入:
class CPU @Inject constructor() {
    var name: String = ""
    fun run() {
        LjyLogUtil.d("$name run...")
    }
}

class Phone @Inject constructor(val cpu: CPU) {
    fun use() {
        cpu.run()
    }
}

@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var phone: Phone

    fun test() {
        phone.cpu.name = "麒麟990"
        phone.use()
    }
}

Hilt Module

使用 @Binds 注入接口实例
//1. 接口
interface ICPU {
    fun run()
}
//2. 实现类
class KylinCPU @Inject constructor() : ICPU {
    override fun run() {
        LjyLogUtil.d("kylin run...")
    }
}
//3. 被注入的类,入参是接口类型
class Phone @Inject constructor(val cpu: ICPU) {
    fun use() {
        cpu.run()
    }
}
//4. 使用@Binds注入接口实例
@Module
@InstallIn(ActivityComponent::class)
abstract class CPUModel {
    @Binds
    abstract fun bindCPU(cpu: KylinCPU): ICPU
}
//5. 使用注入的实例
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var phone: Phone

    fun test() {
        phone.cpu.name = "麒麟990"
        phone.use()
    }
}
使用 @Provides 注入实例
@Module
@InstallIn(ApplicationComponent::class)
class NetworkModel {
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient().newBuilder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(90, TimeUnit.SECONDS)
            .build()
    }
}
为同一类型提供多个绑定
//1. 接口和实现类
interface ICPU {
    fun run()
}


class KylinCPU @Inject constructor() : ICPU {
    override fun run() {
        LjyLogUtil.d("kylin run...")
    }
}

class SnapdragonCPU @Inject constructor() : ICPU {
    override fun run() {
        LjyLogUtil.d("snapdragon run...")
    }
}

//2. 创建多个类型的注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindKylinCPU

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

//@Retention:注解的生命周期
//AnnotationRetention.SOURCE:仅编译期,不存储在二进制输出中
//AnnotationRetention.BINARY:存储在二进制输出中,但对反射不可见
//AnnotationRetention.RUNTIME:存储在二进制输出中,对反射可见


//3. 在Hilt模块中使用注解
@Module
@InstallIn(ActivityComponent::class)
abstract class CPUModel {
    @BindKylinCPU
    @Binds
    abstract fun bindKylinCPU(cpu: KylinCPU): ICPU

    @BindSnapdragonCPU
    @Binds
    abstract fun bindSnapdragonCPU(cpu: SnapdragonCPU): ICPU
}

//4. 使用依赖注入获取实例,可以用在字段注解,也可以用在构造函数或者方法入参中
class Phone5 @Inject constructor(@BindSnapdragonCPU private val cpu: ICPU) {
    @BindKylinCPU
    @Inject
    lateinit var cpu1: ICPU

    @BindSnapdragonCPU
    @Inject
    lateinit var cpu2: ICPU

    fun use() {
        cpu.run()
        cpu1.run()
        cpu2.run()
    }

    fun use(@BindKylinCPU cpu: ICPU) {
        cpu.run()
    }
}

组件默认绑定

class Test1 @Inject constructor(@ApplicationContext private val context: Context)

class Test2 @Inject constructor(@ActivityContext private val context: Context)
class Test3 @Inject constructor(val application: Application)

class Test4 @Inject constructor(val activity: Activity)

Hilt内置组件类型

Hilt内置了7种组件可选:
  1. ApplicationComponent:对应Application,依赖注入实例可以在全项目中使用
  2. ActivityRetainedComponent:对应ViewModel(在配置更改后仍然存在,因此它在第一次调用 Activity#onCreate() 时创建,在最后一次调用 Activity#onDestroy() 时销毁)
  3. ActivityComponent:对应Activity,Activity中包含的Fragment和View也可以使用;
  4. FragmentComponent:对应Fragment
  5. ViewComponent:对应View
  6. ViewWithFragmentComponent:对应带有 @WithFragmentBindings 注释的 View
  7. ServiceComponent:对应Service

组件作用域

Hilt内置7种组件作用域注解
  1. @Singleton:对应组件ApplicationComponent,整个项目共享同一个实例
  2. @ActivityRetainedScope:对应组件ActivityRetainedComponent
  3. @ActivityScoped:对应组件ActivityComponent,在同一个Activity(包括其包含的Fragment和View中)内部将会共享同一个实例
  4. @FragmentScoped:对应组件FragmentComponent
  5. @ViewScoped:对应组件ViewComponent和ViewWithFragmentComponent;
  6. @ServiceScopedService:对应ServiceComponent
interface ApiService {
    @GET("search/repositories?sort=stars&q=Android")
    suspend fun searRepos(@Query("page") page: Int, @Query("per_page") perPage: Int): RepoResponse
}

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModel {

    companion object {
        private const val BASE_URL = "https://api.github.com/"
    }

    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }

    //组件作用域:Hilt默认会为每次的依赖注入行为都创建不同的实例。
    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .build()
    }

    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient().newBuilder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(90, TimeUnit.SECONDS)
            .build()
    }
}
@Database(entities = [RepoEntity::class], version = Constants.DB_VERSION)
abstract class AppDatabase : RoomDatabase() {
    abstract fun repoDao(): RepoDao
}

@Module
@InstallIn(ApplicationComponent::class)
object RoomModule {
    @Provides
    @Singleton
    fun provideAppDatabase(application: Application): AppDatabase {
        return Room
            .databaseBuilder(
                application.applicationContext,
                AppDatabase::class.java,
                Constants.DB_NAME
            )
            .allowMainThreadQueries() //允许在主线程中查询
            .build()
    }

    @Provides
    @Singleton
    fun provideRepoDao(appDatabase: AppDatabase):RepoDao{
        return appDatabase.repoDao()
    }
}

ViewModel的依赖注入

//1. 仓库层
class Repository @Inject constructor(){
    @Inject
    lateinit var apiService: ApiService
    suspend fun getData(): RepoResponse {
        return apiService.searRepos(1, 5)
    }
}
//2. ViewModel层
@ActivityRetainedScoped
class MyViewModel @Inject constructor(private val repository: Repository): ViewModel() {
    var result: MutableLiveData<String> = MutableLiveData()
    fun doWork() {
        viewModelScope.launch {
            runCatching {
                withContext(Dispatchers.IO){
                    repository.getData()
                }
            }.onSuccess {
                result.value="RepoResponse=${gson().toJson(it)}"
            }.onFailure {
                result.value=it.message
            }
        }
    }
}
//3. Activity层
@AndroidEntryPoint
class HiltMvvmActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hilt_mvvm)
        viewModel.result.observe(this, Observer {
            LjyLogUtil.d("result:$it")
        })
        lifecycleScope
        viewModel.doWork()
    }
}
ViewModel 和 @ViewModelInject 注解
//1. 添加两个额外的依赖
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
//2. 修改MyViewModel: 去掉@ActivityRetainedScoped注解,把@Inject改为@ViewModelInject
class MyViewModel @ViewModelInject constructor(private val repository: Repository): ViewModel() {
    ...
}
//3. activity中viewModel获取改为常规写法
@AndroidEntryPoint
class HiltMvvmActivity : AppCompatActivity() {
//    @Inject
//    lateinit var viewModel: MyViewModel
    val viewModel: MyViewModel by viewModels()
//  或  val viewModel: MyViewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) }
    ...
}
SavedStateHandle 和 @assist注解
class MyViewModel @ViewModelInject constructor(
    private val repository: Repository,
    //SavedStateHandle 用于进程被终止时,保存和恢复数据
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    var result: MutableLiveData<String> = MutableLiveData()
    private val userId: MutableLiveData<String> = savedStateHandle.getLiveData("userId")

    fun doWork() {
        viewModelScope.launch {
            runCatching {
                withContext(Dispatchers.IO) {
                    repository.getData(userId)
                }
            }.onSuccess {
                result.value = "RepoResponse=${Gson().toJson(it)}"
            }.onFailure {
                result.value = it.message
            }
        }
    }
}

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

@EntryPoint
@InstallIn(ApplicationComponent::class)
interface MyEntryPoint{
    fun getRetrofit():Retrofit
}
在ContentProvider中使用
class MyContentProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        context?.let {
            val appContext=it.applicationContext
            //调用EntryPointAccessors.fromApplication()函数来获得自定义入口点的实例
            val entryPoint=EntryPointAccessors.fromApplication(appContext,MyEntryPoint::class.java)
            //再调用入口点中定义的getRetrofit()函数就能得到Retrofit的实例
            val retrofit=entryPoint.getRetrofit()
            LjyLogUtil.d("retrofit:$retrofit")
        }
        return true
    }
    ...
}
在 App Startup 中使用
class LjyInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        //调用EntryPointAccessors.fromApplication()函数来获得自定义入口点的实例
        val entryPoint= EntryPointAccessors.fromApplication(context, MyEntryPoint::class.java)
        //再调用入口点中定义的getRetrofit()函数就能得到Retrofit的实例
        val retrofit=entryPoint.getRetrofit()
        LjyLogUtil.d("retrofit:$retrofit")
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

报错解决

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                             "$projectDir/schemas".toString()]
            }
        }
    }
}
//If so, try changing from "arguments =" to "arguments +=", as just using equals overwrites anything set previously.

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章

上一篇 下一篇

猜你喜欢

热点阅读