Android Jetpack 中Hilt的使用

news2025/2/27 16:44:01

Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。执行 手动依赖项注入 要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。

Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法。Hilt 在热门 DI 库 Dagger 的基础上构建而成,因而能够受益于 Dagger 的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。本篇只探讨其使用方式,其步骤如下

在项目中引入Hilt。

project/build.gradle下加入kotlin和hilt的插件

buildscript {
    ext.kotlin_version = '1.5.31'
    ext.hilt_version = '2.40'
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.3'
        //kotlin编译插件
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        //hilt编译插件
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

app/build.gradle下加入kotlin和hilt

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-parcelize'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    compileSdkVersion 31
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.android.hilt"
        minSdkVersion 16
        targetSdkVersion 31
        versionCode 1
        versionName "1.0"


        javaCompileOptions {
            annotationProcessorOptions {
                arguments["room.incremental"] = "true"
            }
        }
    }

    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    implementation 'androidx.recyclerview:recyclerview:1.2.1'

    // Room
    implementation "androidx.room:room-runtime:2.3.0"
    kapt "androidx.room:room-compiler:2.3.0"

    // Hilt dependencies
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}

在项目中使用hilt。

Step1:使用@HiltAndroidApp注解

新建继承自Application的类并添加注解@HiltAndroidApp,触发 Hilt 的代码生成,其中包括可以使用依赖项注入的应用基类。应用容器是应用的父容器,这意味着其他容器可以访问其提供的依赖项。

@HiltAndroidApp 
class LogApplication : Application()

Step2:使用@AndroidEntryPoint将依赖注入Android类。

在 Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注解的其他 Android 类提供依赖项。Hilt 目前支持以下 Android 类:

  • Application(通过使用 @HiltAndroidApp)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

如果您使用 @AndroidEntryPoint 为某个 Android 类添加注解,则还必须为依赖于该类的 Android 类添加注解。例如,如果您为某个 Fragment 添加注解,则还必须为使用该 Fragment 的所有 Activity 添加注解。

@AndroidEntryPoint 
class LogsFragment : Fragment() { .... }

Step3:使用hilt进行字段注入

@Inject 注解让 Hilt 注入不同类型的实例。其实就是声明变量的时候用上这个注解

@AndroidEntryPoint
class LogsFragment : Fragment() {

    @Inject lateinit var logger: LoggerLocalDataSource
    @Inject lateinit var dateFormatter: DateFormatter

    ...
}

Step4:Hilt提供实例。

step4-condition1:在构造器上利用@Inject获取实例。

对于用@Inject注解的变量,提供其实例时,如果是通过构造器创建的实例那么我们可以直接在构造器上利用@Inject注解就可以让hilt为我们创建类的实例,比如下面的DateFormatter

/**
 * 通过构造器创建依赖
 */
class DateFormatter @Inject constructor() {

    @SuppressLint("SimpleDateFormat")
    private val formatter = SimpleDateFormat("d MMM yyyy HH:mm:ss")

    fun formatDate(timestamp: Long): String {
        return formatter.format(Date(timestamp))
    }
}

再比如Step3中的logger。它与DateFormatter的区别在于它的构造参数是有参数的。那么对于这种情况,我们还需要告诉hilt如何获取LogDao的实例。也就是说如果LogDao能通过构造器构建的话,直接添加@Inject注解就可以了。但是这里的logDao是一个接口,而且它无法手动添加实现类(这个是Android room中的DAO)。所以我们需要使用其他的方式获取

@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {
    ...
}

step4-condition2:用 @Provides 提供实例

我们可以在 Hilt 模块中用 @Provides 注释函数,以告诉 Hilt 如何提供无法注入构造函数的 类型。hilt模块也就是用@Module @InstallIn 注释的类的使用。无法通过对构造器添加@Inject注解方式提供实例时通过@Module@InstallIn(指定作用域)来声明提供对象实例的方式。 这个Module是模块,我们需要使用模块向 Hilt 添加绑定,换句话说,就是告诉 Hilt 如何提供不同类型的实例。 在 Hilt 模块中,您需针对无法注入构造函数的类型(如项目中未包含的接口或类)添加绑定。例如 OkHttpClient - 您需要使用其构建器来创建实例。因为这里实际上是提供数据库操作,所以作用域应该是全局的,所以采用的是SingletonComponent。这里还有其他的component

@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
//这个可以是个class,但是在 Kotlin 中,只包含 @Provides 函数的模块可以是 object 类。
//这样,提供程序即会得到优化,并几乎可以内联在生成的代码中。

    /**
     * 用 @Provides 提供实例。我们可以在 Hilt 模块中用 @Provides 注释函数,
     * 以告诉 Hilt 如何提供无法注入构造函数的 类型。
     */
    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
//
        return database.logDao()
        //Hilt 可从上述代码中得知,在提供 LogDao 的实例时需要执行 database.logDao()。
        //由于我们拥有 AppDatabase 作为传递依赖项,因此我们还需要告诉 Hilt 如何提供这种类型的实例。
    }

    //因为我们一直希望 Hilt 提供相同的数据库实例,所以我们用 @Singleton 注释 @Provides provideDatabase 方法。
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context):AppDatabase{
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }

}

step4-condition3:用 @Binds 提供接口。

对于接口我们不能使用构造函数注入。 要告诉 Hilt 对接口使用什么实现,可以在 Hilt 模块内的函数上使用 @Binds 注释。@Binds必须对抽象函数作出注释(因为该函数是抽象的,因此其中不包含任何代码,并且该类也必须是抽象的)。抽象函数的返回类型是我们要为其提供实现的接口(即 AppNavigator)。通过添加具有接口实现类型(即 AppNavigatorImpl)的唯一参数来指定实现。比如在MainActivity中我们依赖的接口

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var navigator: AppNavigator
    ....
}

所以对此我们需要新建module使用@Binds获取,如果类型有作用域,则@Binds 方法必须有作用域注释

//我们的新导航信息(即 AppNavigator)需要特定于 Activity 的信息
//(因为 AppNavigatorImpl 拥有 Activity 作为依赖项)。
// 因此,我们必须将其安装在 Activity 容器中,而不是安装在 Application 容器中,因为这是有关 Activity 的信息所在。
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun provideNavigator(impl: AppNavigatorImpl):AppNavigator
    //参数为具体的实现类,所以要告知hilt如何提供实现类的实例。下面的实现类通过构造函数提供实例
}


//======AppNavigatorImpl.ktx========//

//AppNavigatorImpl 会依赖于 FragmentActivity。由于系统会在 Activity 容器中提供 AppNavigator 实例
// (亦可用于 Fragment 容器和 View 容器,因为 NavigationModule 会安装在 ActivityComponent 中),所以 FragmentActivity 目前可用
class AppNavigatorImpl @Inject constructor(private val activity: FragmentActivity) : AppNavigator {

    override fun navigateTo(screen: Screens) {
        val fragment = when (screen) {
            Screens.BUTTONS -> ButtonsFragment()
            Screens.LOGS -> LogsFragment()
        }

        activity.supportFragmentManager.beginTransaction()
            .replace(R.id.main_container, fragment)
            .addToBackStack(fragment::class.java.canonicalName)
            .commit()
    }
}

step4-condition4:使用限定符

要告诉 Hilt 如何提供相同类型的不同实现(多个绑定),可以使用限定符。它的定义其实就是注解。

@Qualifier 
annotation class InMemoryLogger 
@Qualifier 
annotation class DatabaseLogger

要比如对log的增删查提供一套基于内存的实现方式,那么定义接口

interface LogDataSource {
    fun addLog(msg: String)
    fun getAllLogs(callback: (List<Log>) -> Unit)
    fun removeLogs()
}

基于Room的实现如下,其实就是开篇提到的实现,只不过实现了该接口

@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao):LogDataSource {

    private val executorService: ExecutorService = Executors.newFixedThreadPool(4)
    private val mainThreadHandler by lazy {
        Handler(Looper.getMainLooper())
    }

    override fun addLog(msg: String) {
        executorService.execute {
            logDao.insertAll(
                Log(
                    msg,
                    System.currentTimeMillis()
                )
            )
        }
    }

    override fun getAllLogs(callback: (List<Log>) -> Unit) {
        executorService.execute {
            val logs = logDao.getAll()
            mainThreadHandler.post { callback(logs) }
        }
    }

    override fun removeLogs() {
        executorService.execute {
            logDao.nukeTable()
        }
    }
}

基于内存的实现如下

@ActivityScoped
class LoggerInMemoryDataSource @Inject constructor():LogDataSource {
    private val logs = LinkedList<Log>()

    override fun addLog(msg: String) {
        logs.addFirst(Log(msg, System.currentTimeMillis()))
    }

    override fun getAllLogs(callback: (List<Log>) -> Unit) {
        callback(logs)
    }

    override fun removeLogs() {
        logs.clear()
    }
}

基于上面介绍,使用接口时我们定义实现类如下

@Module
@InstallIn(SingletonComponent::class)
abstract class LoggingDatabaseModule {
    @DatabaseLogger
    @Binds
    @Singleton
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LogDataSource
}

@Module
@InstallIn(ActivityComponent::class)
abstract class LoggingInMemoryModule {
    @InMemoryLogger
    @Binds
    @ActivityScoped
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LogDataSource
}

可以看到我们定义了两个module,之所以不是一个module是因为两种实现的作用域不一样。而且在InMemory的@Binds方法上我们还加入了@ActivityScoped,这个是必须加入的,因为实现类中指定了作用域。同理在这儿我们还加入了自定义的注解InMemoryLogger,就是告诉hilt选择那种方式提供实例。如果不加限定符的话会报错。真正使用该接口时如下

class ButtonsFragment : Fragment() {
    @InMemoryLogger
    @Inject lateinit var logger: LogDataSource
    ...
}

可以看到与Step3中的区别在于此处变量的类型为接口而不是具体的实现,其次加入了限定符。综上就是Hilt的基本使用

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/981479.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图的学习,深度和广度遍历

一、什么是图 表示“多对多”的关系 包括&#xff1a; 一组顶点&#xff1a;通常用V&#xff08;Vertex&#xff09;表示顶点集合一组边&#xff1a;通常用E&#xff08;Edge&#xff09;表示边的集合 边是顶点对&#xff1a;(v, w)∈E&#xff0c;其中v,w∈V有向边<v, w&…

线性代数的学习和整理20,关于向量/矩阵和正交相关,相似矩阵等

目录 1 什么是正交 1.1 正交相关名词 1.2 正交的定义 1.3 正交向量 1.4 正交基 1.5 正交矩阵的特点 1.6 正交矩阵的用处 1 什么是正交 1.1 正交相关名词 orthogonal set 正交向量组正交变换orthogonal matrix 正交矩阵orthogonal basis 正交基orthogonal decompositio…

【微服务】五. Nacos服务注册

Nacos服务注册 5.1 Nacos服务分级存储模型Nacos服务分级存储模型&#xff1a;服务集群属性&#xff1a;总结&#xff1a; 5.2 根据集群负载均衡总结 5.3 Nacos服务实例的权重设置总结&#xff1a; 5.6 环境隔离namespace总结 5.7 Nacos和Eureka的对比总结 5.1 Nacos服务分级存储…

2023年亲测有效----树莓派启动时自动邮件上报ip

2023年亲测 树莓派启动时自动邮件上报ip 首先开启qq邮箱smtp服务shell文件内容启动自动执行python文件注意事项 首先开启qq邮箱smtp服务 然后点击开启就会有授权码 shell文件内容 在自己的shell里&#xff0c;运行echo $PATH&#xff0c;把内容覆盖下面的path。 功能 作用就…

图解系列 图解Kafka之Producer

开局一张图&#xff0c;其他全靠吹 发送消息流程如下&#xff1a; 1.初始化流程 指定bootstrap.servers&#xff0c;地址的格式为 host:port。它会连接bootstrap.servers参数指定的所有Broker&#xff0c;Producer启动时会发起与这些Broker的连接。因此&#xff0c;如果你为这…

点云从入门到精通技术详解100篇-伪雷达点云预测

前言 近年来,“自动驾驶”已经成为一个耳熟能详的词语,它是一种通过车载计 算实现无人驾驶的智能汽车系统。自动驾驶汽车依靠人工智能、视觉计算、视觉 传感器、控制设备和定位系统协同合作,让系统可以在无人主动操作的情况下, 自动安全地操作机动车辆。其中视觉传感器作…

【继RNN之后的一项技术】Transfomer 学习笔记

谷歌团队在17年的神作&#xff0c;论文17年6月发布 https://arxiv.org/abs/1706.03762 被NIPS2017收录&#xff0c;目前引用量已经逼近3w。 以下内容参考李沐老师的课程《动⼿学深度学习(Pytorch版)》 简介 注意力 自主性&#xff1a;有目的的搜索某样东西&#xff08;键&…

Python小知识 - 如何使用Python进行机器学习

如何使用Python进行机器学习 Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。 机器学习是人工智能的一个分支&#xff0c;是让计算机自动“学习”。学习的过程是从经验E中获得知识K。经验E可以是一个数据集&#xff0c;比如一个图像数据集。知识K可以是计算机…

Python爬虫-爬取文档内容,如何去掉文档中的表格,并保存正文内容

前言 本文是该专栏的第58篇,后面会持续分享python爬虫干货知识,记得关注。 做过爬虫项目的同学,可能或多或少爬取过文档数据,比如说“政务网站,新闻网站,小说网站”等平台的文档数据。爬取文档数据,笔者这里就不过多详述,而本文,笔者将主要介绍在爬取文档数据的过程中…

六安RapidSSL泛域名https能保护几个域名

RapidSSL是Geotrust旗下的子品牌&#xff0c;Geotrust是国际知名的CA认证机构&#xff0c;每年都要通过WebTrust年检&#xff0c;而Geotrust旗下的https证书已经应用于市场多年&#xff0c;Geotrust的子品牌RapidSSL证书经营的DV基础型泛域名https证书也受到市场的欢迎。今天就…

MFC新建内部消息

提示&#xff1a;记录一下MFC新建内部消息的成功过程 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 先说一下基本情况&#xff0c;因为要在mapview上增加一个显示加载时间的功能。然后发现是要等加载完再显示时间&#xff0c;显示在主…

动手学深度学习d2l.Animator无法在PyCharm中显示动态图片的解决方案

from d2l import torch as d2l一、问题描述 运行d2l的训练函数&#xff0c;仅在控制台输出以下内容&#xff0c;无法显示动态图片&#xff08;训练监控&#xff09; <Figure size 350x250 with 1 Axes> <Figure size 350x250 with 1 Axes> <Figure size 350x2…

数据结构与算法之贪心动态规划

一&#xff1a;思考 1.某天早上公司领导找你解决一个问题&#xff0c;明天公司有N个同等级的会议需要使用同一个会议室&#xff0c;现在给你这个N个会议的开始和结束 时间&#xff0c;你怎么样安排才能使会议室最大利用&#xff1f;即安排最多场次的会议&#xff1f;电影的话 那…

Fiddler 系列教程(一)初识Fiddler,我们能用fiddler做什么?

Fiddler是最强大最好用的Web调试工具之一&#xff0c;它能记录所有客户端和服务器的http和https请求&#xff0c;允许你监视&#xff0c;设置断点&#xff0c;甚至修改输入输出数据. 使用Fiddler无论对开发还是测试来说&#xff0c;都有很大的帮助。 阅读目录 Fiddler的基本介…

23个react常见问题

1、setState 是异步还是同步&#xff1f; 合成事件中是异步 钩子函数中的是异步 原生事件中是同步 setTimeout中是同步 相关链接&#xff1a;你真的理解setState吗&#xff1f;&#xff1a; 2、聊聊 react16.4 的生命周期 图片 相关连接&#xff1a;React 生命周期 我对 Reac…

康耐视visionpro破解版满天飞,那么如何查询康耐视Visionpro加密狗支持哪些工具

目录 第一步骤&#xff0c;点击WinR&#xff0c;弹出命令符第二步骤&#xff1a;输入CMD&#xff0c;回车第三步骤&#xff1a;输入cogtool -p&#xff08;cogtool与-p之间有空格&#xff09;&#xff0c;输入完毕后&#xff0c;记得回车&#xff0c;稍等3秒钟不到。 第一步骤&…

[js] 图解 event.pageX event.clientX event.offsetX getBoundingClientRect

event.clientX、event.clientY 鼠标相对于浏览器窗口可视区域的X&#xff0c;Y坐标&#xff08;窗口坐标&#xff09;&#xff0c;可视区域不包括工具栏和滚动条。IE事件和标准事件都定义了这2个属性 event.pageX、event.pageY 类似于event.clientX、event.clientY&#xff0c;…

作为一名游戏开发工作者,分享一些工作经验

作为一名游戏开发工作者&#xff0c;有一些重要的心得和经验可以分享&#xff0c;这些经验可以帮助你更好地在游戏开发领域取得成功&#xff1a; 学习不断进步&#xff1a;游戏开发是一个不断演进的领域&#xff0c;你需要不断学习新的技术和工具。跟踪最新的游戏开发趋势&…

Excel VSTO开发2 -建立Excel VSTO项目

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 2 建立Excel VSTO项目 新建项目&#xff0c;选择Excel 2013和2016 VSTO外接程序。输入项目名称&#xff08;本示例的项目名称为&am…

python基于Django健身爱好者交流分享平台vue

而肌友网—健身交流平台能很好地解决这一问题&#xff0c;轻松应对健身交流&#xff0c;既能提高用户对健身交流的评价&#xff0c;又能加快健身交流平台的效率&#xff0c;取代人工管理是必然趋势。 本肌友网—健身交流平台以Django作为框架&#xff0c;B/S模式以及MySql作为后…