首页 > 其他分享 >Jetpack架构组件学习(5)——Hilt 注入框架使用

Jetpack架构组件学习(5)——Hilt 注入框架使用

时间:2024-08-17 14:39:21浏览次数:11  
标签:Jetpack MyClient Hilt fun Inject constructor 组件 class 注入

原文: Jetpack架构组件学习(5)——Hilt 注入框架使用-Stars-One的杂货小窝

本篇需要有Kotlin基础知识,否则可能阅读本篇会有所困难!

介绍说明

实际上,郭霖那篇文章已经讲得比较明白了(具体参考链接都贴在下文了),这里简单总结下:

如果按照之前我们的MVC写法,我们可以直接在activity中发起网络请求,但发起网络请求我们需要调用一个Api对象的具体方法,而Api对象只能在activity中进行创建

这里activity和api对象实际上就是耦合关系,从客观上讲,我们activity不应该去负责创建一个api对象

所以使用注入框架,相当于有了个中间人帮activity处理,至于是中间人直接找到api对象,或者是中间人进行创建api对象,activity都不关心,activity只知道去找中间人就能帮得到一个api对象

优点

学习之后,目前感觉到的优点:

  1. 可能大型项目,多module那种比较适合
  2. MMVM/MVI架构的app也比较适合
  3. 注入接口,方便不同逻辑实现

可能就是接口注入可能有些用处,比如上面例子,假设我们网络框架刚开始用的是okhttp,但后期可能又会变更其他框架,我们可以考虑封装一个通用接口,然后使用依赖注入,后期更换其他网络框架只需要实现接口的对应方法即可

依赖注入比较针对是MVVM/MVI架构的app,传统mvc结构,我直接一个单例object,也可以解决问题,好像也没啥必要?

网上大多数说的都是解耦,方便后续测试,问题是我都不怎么写测试用例,实在无法感受到具体好处就是

总之,目前学习这个只是因为很多开源项目都开始用上了,学了这个发现大概才能看得懂哈哈,也顺手做下记录了

基本使用

1.依赖引入

注: 下面我使用的是ksl的gradle脚本

项目的build.kts文件

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48.1'
    }
}

在app模块里的build.kts加上插件和依赖

plugins {
    id("com.android.application")
    id("kotlin-kapt") // kotlin-kapt 插件
    id("dagger.hilt.android.plugin") // Hilt 插件
}

dependencies {	
	implementation("com.google.dagger:hilt-android:2.48.1")
	kapt("com.google.dagger:hilt-compiler:2.48.1")
}

//还有记得有下面此数据配置,不过一般都默认有
android{	
	compileOptions {
		sourceCompatibility = JavaVersion.VERSION_1_8
		targetCompatibility = JavaVersion.VERSION_1_8
	}
}

我这里直接新版本as创建的新项目,已经使用了toml+ksl的方式,贴下图参考下:

toml:

build.gradle.kt

app里的build.gradle.kt

这里有个坑:

就是ksp和kapt一起使用会导致编译失败,得设置插件不传递,及上面的build.gradle.kt截图

2.application上加注解

@HiltAndroidApp
class MyApplication:Application() {
    override fun onCreate() {
        super.onCreate()
		//...
    }
}

注意在清单文件中使用MyApplication对象哦!

<application
	android:name=".MyApplication"
	//省略其他...
/>

3.注入对象

class MyApi @Inject constructor(){
	fun sendApi(){
		
	}
}


@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

	/**
	 * 这里不能是private,且是懒加载的方式
	 */
	@Inject
	lateinit var api: MyApi
		
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

补充说明

我们注意到上面出现了3个注解

  • @HiltAndroidApp 在application上使用
  • @AndroidEntryPoint 在activity等类上使用此注解,下面有补充说明
  • @Inject 用来注入对象及标识需要注入实体

其中@HiltAndroidApp是在application中使用的,而

hilt有以下入口点:

  • Application
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

也就是说,我们使用依赖注入功能只能在这几个类中

对于application,我们使用@HiltAndroidApp注解,这步是必须的,否则依赖注入不会生效!

而另外的@AndroidEntryPoint注解,在哪里你需要使用到@Inject注入对象,则需要将当前类标明上注解@AndroidEntryPoint,(如上个步骤中的代码示例)

进阶使用

1.带参实体注入

给之前的MyApi添加个新的构造参数

class MyApi @Inject constructor(val client:Client){
    fun sendApi(){

    }
}

class Client @Inject constructor(){
    fun config() {
        
    }
}

总结: 需要依赖注入的实体,如果有其他参数,则保证其他参数实体也是有依赖注入即可

上面的MyApi,也可以写成下面这样:

class MyApi @Inject constructor(){
	@Inject
    lateinit var client: Client
	
    fun sendApi(){

    }
}

2.接口类型注入

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import javax.inject.Inject


interface ClientInterface{
    fun config()
}

class MyClient @Inject constructor():ClientInterface{
    override fun config() {
        Log.d("ttt", "myclient config ")
    }
}

class MyApi @Inject constructor(){

    @Inject
    lateinit var clientInterface: ClientInterface

    fun sendApi(){
        clientInterface.config()
        Log.d("ttt", "send api")
    }
}

@Module
@InstallIn(ActivityComponent::class)
abstract class ClientModule {
    @Binds
    abstract fun createClient(myClient: MyClient): ClientInterface

}

上面定义一个ClientInterface接口,我们要注入一个此接口实现类MyClient

得多加一个ClientModule类,依赖注入的时候会通过此类中的对应方法createClient来注入

这里类和方法名都是可以随意,不过方法里的参数就是你要注入的接口实现类,返回则是接口类,还要注意该类是抽象类!

关于@InstallIn注解,在下面章节会再次讲解,这里先跳过,先这样使用即可

PS:当然这里可以也可以不是接口类型,改成抽象类应该也是可以的!

3.相同类型不同实例注入

在上面接口类型注入的代码上加入一个新的类进行注入

class MyTwoClient @Inject constructor():ClientInterface{
    override fun config() {
        Log.d("ttt", "mytwoclient config ")
    }
}

@Module
@InstallIn(ActivityComponent::class)
abstract class ClientModule {
    @BindMyClient
    @Binds
    abstract fun createClient(myClient: MyClient): ClientInterface


	/**
	 * 注意这个方法名不能与上面createClient相同,否则编译会失败!
	 */
    @BindMyTwoClient
    @Binds
    abstract fun createTwoClient(myClient: MyTwoClient): ClientInterface
}

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

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

修改要注入对象的地方:

class MyApi @Inject constructor(){

	/**
	 * 这里使用@BindMyTwoClient来标明我们要注入MyTwoClient实例
	 */
    @BindMyTwoClient
    @Inject
    lateinit var clientInterface: ClientInterface

    fun sendApi(){
        clientInterface.config()
        Log.d("ttt", "send api")
    }
}

4.外部第三方实体注入

这里说的第三方,指的是第三方库,由于库里基本封装好了,代码修改不像上面那么自由,可能还没有构造方法,那我们应该如何实现注入?

这里是使用@Provides注解来实现

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import javax.inject.Inject
import javax.inject.Qualifier

class MyApi @Inject constructor(){

	/**
	 * 指定MyClient,依赖注入最终会调用后面createNClient()方法生成对象
	 */
    @Inject
    lateinit var clientInterface: MyClient

    fun sendApi(){
        Log.d("ttt", "send api")
    }
}

class MyClient {
    fun config() {
        Log.d("ttt", "myclient config ")
    }
}

@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
    @Provides
    fun createNClient(): MyClient{
		//这里方便延时,我直接通过创建一个实例了
        return MyClient()
    }
}

和上面接口类型注入不同,需要注意以下几点:

  1. ClientModule这个类不是抽象类了
  2. @Provides注解的那个方法,也不是抽象方法,且不用@Inject注解标明

或者方法还能加个参数(依赖其他类):

@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
    
	/**
	 * myClient这个也会被自动注入上面我们的那个返回数据
	 */
    @Provides
    fun createNewApi(myClient: MyClient): MyNewApi{
        return MyNewApi(myClient)
    }
    
}

class MyNewApi(myClient: MyClient)

组件和组件作用域

介绍

上面有个@InstallIn,翻译就是安装到的意思

@InstallIn(ActivityComponent::class): 就是把这个模块安装到 Activity 组件当中;

如果我们在Service中使用@Inject注入,则编译时就会提示出错,原因是ActivityComponent已经限定只能在activity里使用

当然,除了ActivityComponent这个组件,我们还有其他的组件可用,如下表

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
  • @Singleton 被它修饰的构造函数或是函数,返回的始终是同一个实例
  • @ActivityRetainedScoped 被它修饰的构造函数或是函数,在Activity的重建前后返回同一实例
  • @ActivityScoped 被它修饰的构造函数或是函数,在同一个Activity对象里,返回的都是同一实例
  • @ViewModelScoped 被它修饰的构造函数或是函数,与ViewModel规则一致

组件的生命周期

生成的组件 创建时机 销毁时机
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()

依赖注入实现单例

一般情况下,我们的api全局应该是单例模式,所以上面的可以改成下面代码:

@Module
@InstallIn(SingletonComponent::class)
class ClientModule {

    @Singleton
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

上面的@Singleton这个是不可省略的,省略了相当于你使用的默认的组件,相当于每次注入都是新创建实例了!

然后需要注意的是,下面几个错误的写法:

@Module
@InstallIn(SingletonComponent::class)
class ClientModule {

    @ActivityScoped //错误,与当前组件的作用域不一致
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

@Module
@InstallIn(ActivityComponent::class)
class ClientModule {

    @Singleton //错误,与当前组件的作用域不一致
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

组件作用域除了在module里使用,还可以修饰构造函数

@ActivityScoped
class Hardware @Inject constructor(){
    fun printName() {
        println("I'm fish")
    }
}

表示Hardware在同个Activity,只会有一个实例

组件的层次

组件有层次的使用,比如上面的全局的api,我们可以在其他地方组件作用域进行注入或者Activity,fragment中使用注入,如下代码:

@Module
@InstallIn(SingletonComponent::class)
class ClientModule {

    @Singleton
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

@ActivityScoped
class MyNewApi @Inject constructor(myClient: MyClient)

具体的关系结构层次如下图所示:

注入application或Activity

当我们构造函数需要传递application或Activity的时候,可以使用@ApplicationContext@ActivityContext 限定符。

如下面代码:

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }

特殊用法

似乎是自定义入口类,然后给application实现一个扩展方法

@Module
@InstallIn(SingletonComponent::class)
object PlayServiceModule {
fun Application.playerController(): PlayerController {
return accessEntryPoint<PlayerControllerEntryPoint>().playerController()
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface PlayerControllerEntryPoint {
fun playerController(): PlayerController
}

}

与ViewModel联用

为ViewModel添加 @HiltViewModel 注解,并在 ViewModel 对象的构造函数中使用 @Inject 注解

@HiltViewModel
class ExampleViewModel @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ViewModel() {
  ...
}

然后,带有 @AndroidEntryPoint 注解的 activity 或 fragment 可以使用 ViewModelProvider 或 by viewModels() KTX 扩展照常获取 ViewModel 实例:

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
  private val exampleViewModel: ExampleViewModel by viewModels()
  ...
}

参考

标签:Jetpack,MyClient,Hilt,fun,Inject,constructor,组件,class,注入
From: https://www.cnblogs.com/stars-one/p/18364360

相关文章

  • 关于鸿蒙开发中容器组件Tabs的介绍
    当页面内容较多时,可以通过Tabs组件进行分类展示。Tabs基本用法structTabbarDemo{build(){//外层顶级容器Tabs(){TabContent(){//内容区域:只能有一个子组件Text('首页内容')}......
  • 使用 prefetchComponents 进行组件预取
    title:使用prefetchComponents进行组件预取date:2024/8/17updated:2024/8/17author:cmdragonexcerpt:摘要:本文介绍Nuxt.js中的prefetchComponents功能,用于预取组件以提高用户体验。通过在客户端后台下载和缓存组件,确保在用户需要时快速加载。文章涵盖了prefetchComp......
  • Grid++Report 组件使用最简代码
    在‘添加引用’窗口中选择‘COM’选项卡,在列表中双击‘Grid++ReportEngine6.0TypeLibrary’项使用设计器设计一个模板,加入vs2022项目  设置为文件新则拷贝因为是打印标签,数据有限,所以模板使用参数传递数据,纸型按实际标签的长宽设置c#调用模板的代码如下privatev......
  • 界面控件DevExpress即将推出全新AI功能,WinForms & Blazor组件可用!
    DevExpress拥有.NET开发需要的所有平台控件,包含600多个UI控件、报表平台、DevExpressDashboardeXpressApp框架、适用于VisualStudio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress近期重要版本v24.1已正式发布,该版本拥有众多新产品和数十个具有高影响力......
  • .NET8 Blazor 从入门到精通:(二)组件
    目录Blazor组件基础路由导航参数组件参数路由参数生命周期事件状态更改组件事件Blazor组件基础新建一个项目命名为MyComponents,项目模板的交互类型选Auto,其它保持默认选项:客户端组件(Auto/WebAssembly):最终解决方案里面会有两个项目:服务器端项目、客户端项目,组件按......
  • ArkTs基础语法-声明式UI-页面和自定义组件生命周期
    页面和自定义组件生命周期组件和页面的关系生命周期页面生命周期组件生命周期普通流程为:其他流程:自定义组件的创建和渲染流程首次创建重新渲染自定义组件的删除自定义组件监听页面生命周期组件和页面的关系自定义组件:@Component装饰的UI单元,可以组合多个系统组件......
  • 一款开箱即用的整合第三方登录的开源组件,整合了国内外数十家知名平台的OAuth登录(附源
    前言在现代应用开发中,第三方登录认证是一个不可或缺的功能,它为用户带来了便捷的登录体验。然而,开发者在实现这一功能时往往会遇到一些痛点:需要对接多个第三方平台的SDK,每增加一个平台就要编写一套新的代码,导致代码维护变得复杂且困难。此外,从头开发一个完整的登录功能不仅需要......
  • 界面控件DevExpress .NET MAUI v24.1 - 发布TreeView等新组件
    DevExpress拥有.NET开发需要的所有平台控件,包含600多个UI控件、报表平台、DevExpressDashboardeXpressApp框架、适用于VisualStudio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress今年第一个重要版本v23.1正式发布,该版本拥有众多新产品和数十个具有高影响力......
  • 在vue中封装第三方组件的标准
    在Vue.js项目中,经常会用到各种第三方组件库来提升开发效率和用户体验。然而,直接使用这些组件可能会导致代码结构混乱、样式冲突等问题。为了保持项目的整洁和可维护性,我们需要优雅地封装这些第三方组件。下面是一篇关于如何在Vue中优雅地封装第三方组件的文章。如何在Vue中优......
  • 12 Text 组件
    12Text组件Tkinter是Python的标准GUI库,而Text组件是其中用于显示和编辑多行文本的控件。以下是对Text组件的详细说明和一个使用案例。Text组件属性基本属性width:文本框的宽度,通常以字符数为单位。height:文本框的高度,以行数为单位。wrap:指定文本换行的......