Android核心技术——Jetpack Hilt依赖注入

news2024/11/20 13:35:36

依赖注入是什么

个人理解:把有依赖关系的类放在容器中,解析这些类的实例,并在运行时注入到对应的字段中,就是依赖注入,目的是为了类的解耦

例子:A 类 中用到了 B 类,一般情况下需要在 A 类中 new B() 的实例对象

采用依赖注入后,在 A 类中 定义一个私有的 B 类 字段。并在运行的时候通过从相关的容器中获取出来 B 的对象并注入到 A 类中的 字段中。

这样做的好处是什么?

如果有很多个类需要使用 B 类。难道都要在各自的类中进行 new B() 吗。这样对后期的维护和管理都是不方便的。使用 依赖注入则就变得很简单了。

HILT 是什么

Hilt 是 Android 的依赖注入库,其实是基于 Dagger 。可以说 Hilt 是专门为 Andorid 打造的。

Hilt 创建了一组标准的 组件和作用域。这些组件会自动集成到 Android 程序中的生命周期中。在使用的时候可以指定使用的范围,事情作用在对应的生命周期当中。

HILT 常用的注解的含义

  • @HiltAndroidApp @HiltAndroidApp 将会触发 Hilt 的代码生成,作为程序依赖项容器的基类 生成的 Hilt 依附于 Application 的生命周期,他是 App 的父组件,提供访问其他组件的依赖 在 Application 中配置好后,就可以使用 Hilt 提供的组件了;组件包含 Application,Activity,Fragment,View,Service 等。
  • @HiltAndroidApp 创建一个依赖容器,该容器遵循 Android 的生命周期类,目前支持的类型是: Activity, Fragment, View, Service, BroadcastReceiver.
  • @Inject 使用 @Inject 来告诉 Hilt 如何提供该类的实例,常用于构造方法,非私有字段,方法中。 Hilt 有关如何提供不同类型的实例信息也称之为绑定
  • @Module module 是用来提供一些无法用 构造@Inject 的依赖,如第三方库,接口,build 模式的构造等。 使用 @Module 注解的类,需要使用 @InstallIn 注解指定 module 的范围 增加了 @Module 注解的类,其实代表的就是一个模块,并通过指定的组件来告诉在那个容器中可以使用绑定安装。
  • @InstallIn 使用 @Module 注入的类,需要使用 @InstallIn 注解指定 module 的范围。 例如使用 @InstallIn(ActivityComponent::class) 注解的 module 会绑定到 activity 的生命周期上。
  • @Provides 常用于被 @Module 注解标记类的内部方法上。并提供依赖项对象。
  • @EntryPoint

Hilt 支持最常见的 Android 类 Application、Activity、Fragment、View、Service、BroadcastReceiver 等等,但是您可能需要在Hilt 不支持的类中执行依赖注入,在这种情况下可以使用 @EntryPoint 注解进行创建,Hilt 会提供相应的依赖。

HILT 中的组件(COMPENENT)

使用 @Module 注解的类,需要使用 @Installin 注解来指定 module 的范围。

例如 @InstallIn(ApplicationComponent::class) 注解的 Module 就会绑定到 Application 的生命周期上。

Hilt 提供了以下组件来绑定依赖与对应 Android 类的活动范围

Hilt 组件对应 Android 类活动的范围
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponentView annotated with @WithFragmentBindings
ServiceComponentService

Hilt 没有为 broadcast receivers 提供组件,因为 Hilt 直接进从 ApplicationComponent 中注入 broadcast receivers。

HILT 中组件的生命周期

Hilt 会根据相应的 Android 类生命周期自动创建和销毁组件的实例,对应关系如下:

Hilt 提供的组件创建对应的生命周期结束对应的生命周期作用范围
ApplicationComponentApplication#onCreate()Application#onDestroy()@Singleton
ActivityRetainedComponentActivity#onCreate()Activity#onDestroy()@ActivityRetainedScope
ActivityComponentActivity#onCreate()Activity#onDestroy()@ActivityScoped
FragmentComponentFragment#onAttach()Fragment#onDestroy()@FragmentScoped
ViewComponentView#super()View destroyed@ViewScoped
ViewWithFragmentComponentView#super()View destroyed@ViewScoped
ServiceComponentService#onCreate()View destroyed@ViewScoped

Android常用的依赖注入框架

Dagger

由Square公司开源,基于Java反射去实现的,从而有两个潜在的隐患:

  • 反射是比较耗时的,用这种方式会降低程序的运行效率。(这问题不大,现在的程序中到处都在用反射)
  • 依赖注入框架的用法总体来说比较有难度,很难一次性编写正确。而基于反射实现的依赖注入功能,在编译期无法得知依赖注入的用法是否正确,只能在运行时通过程序是否崩溃来判断。这样测试的效率低下,容易将一些 bug 隐藏得很深。

Dagger2

由 Google 开发,基于 Java 注解实现的,把 Dagger1 反射的那些弊端解决了:

通过注解,Dagger2 会在编译时期自动生成用于依赖注入的代码,不会增加任何运行耗时。另外,Dagger2 会在编译时检查依赖注入用法是否正确,若不正确则会直接编译失败,从而将问题尽可能早地抛出。即项目正常编译通过,说明依赖注入的用法基本没问题了。

但 Dagger2 使用比较复杂,若不能很好地使用它,可能会拖累你的项目,甚至会将一些简单的项目过度设计。

Hilt

Google 发布了 Hilt,是在依赖项注入库 Dagger 的基础上构建而成,一个专门面向 Android 的依赖注入框架。

相比于 Dagger2,Hilt 最明显的特征就是: 简单、提供了 Android 专属的 API。

Hilt的使用

1、依赖版本配置

项目根build.gradle依赖

ext.kotlin_version = "1.5.0"//kotlin版本
ext.hilt_version = '2.35'//hilt版本
repositories {
    google()
    mavenCentral()
}
dependencies {
    classpath "com.android.tools.build:gradle:4.2.1"
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35'
}

项目app中build.gradle依赖

implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

项目模块中build.gradle所使用插件

id 'kotlin-kapt'
id 'kotlin-android-extensions'

2、Hilt应用

1.定义Hilt应用类

  • 用@HiltAndroidApp注解Application;所有使用Hilt注入框架的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类。
  • @HiltAndroidApp注解 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器;
@HiltAndroidApp
class MyApplication : MultiDexApplication() {
​
}

然后,在AndroidManifest.xml中引入我们自定义的Application类。此时,生成的Hilt组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,由于它也是应用的父组件,所以其他组件可以访问它提供的依赖项。

2.创建注入类

为了执行字段注入,需要在类的构造函数中使用 @Inject 注解,以告知 Hilt 如何提供该类的实例

class Car @Inject constructor() {
    var name: String = ""
​
    fun run() {
    }
}

使用 @Inject 注释执行字段 @AndroidEntryPoint 会为项目中的每个 Android 类生成一个单独的 Hilt 组件。这些组件可以从它们各自的父类接收依赖项, 如需从组件获取依赖项,请使用 @Inject 注释执行字段注入, 注意:Hilt注入的字段是不可以声明成private的;

3.依赖项注入 Android 类

用@AndroidEntryPoint注释类 目前支持6类入口点:Application(通过使用 @HiltAndroidApp),Activity,Fragment,View,Service,BroadcastReceiver

使用 @AndroidEntryPoint 注解 Android 类,还必须为依赖于该类的 Android 类添加注释,例如为注解 fragment ,则还必须为该 fragment 依赖的 Activity 添加@AndroidEntryPoint注释。

@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var car: Car
}

注意:带参数的依赖注入,需要构造函数中所依赖的所有其他对象都支持依赖注入;例如:Car的构造函数有Engine,则Engine构造函数也需要@Inject。

3、Hilt模块

有时一些类型参数(如接口或来自外部库的类)不能通过构造函数注入,对于这种情况,我们可以使用Hilt 模块来向Hilt提供绑定信息。

Hilt模块是一个带有@Module注释的类,并使用 @InstallIn 设置作用域。与Dagger模块的作用一样,它会告知Hilt如何提供某些类型的实例。与Dagger模块不同的是,我们必须使用@InstallIn注解为Hilt模块添加注释,以告知Hilt模块将用在哪个Android类中。

1.@Binds注入接口

由于Phone是一个接口,无法通过构造函数注入Hilt,而应向Hilt提供绑定信息。即在Hilt模块内创建一个带有@Binds注释的抽象函数。

通常,带有注释的函数会向Hilt提供以下信息:

  1. 函数返回类型会告知Hilt函数提供的是哪个接口的实例。
  2. 函数参数会告知Hilt需要提供哪种实现。
//1. 接口
interface Phone {
    fun call()
}
//2. 实现类
class Huawei @Inject constructor() : Phone {
    override fun call() {
    }
}
//3. 被注入的类,入参是接口类型
class People @Inject constructor(val phone: Phone) {
    fun call() {
        phone.call()
    }
}
//4. 使用@Binds注入接口实例
@Module
@InstallIn(ActivityComponent::class)
abstract class PhoneModel {
    @Binds
    abstract fun bindPhone(phone: Huawei): Phone
}
//5. 使用注入的实例
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var people: People
​
    fun test() {
        people.phone.call()
    }
}

由于PhoneModel 带有 @InstallIn(ActivityComponent.class) 注释,因为我们可以将该依赖项注入Activity中。并且,PhoneModel中的所有依赖项都可以在所有Activity中使用。

2.@Provides注入实例

如果某个类不归您所有(因为它来自外部库,如 Retrofit、OkHttpClient 或 Room 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。

这种情况注入方法是在Hilt模块内创建一个函数,然后使用@Provides为该函数添加注释。

带有注释的函数会向 Hilt 提供以下信息:

  1. 函数返回类型会告知 Hilt 函数提供哪个类型的实例。
  2. 函数参数会告知 Hilt 相应类型的依赖项。
  3. 函数主体会告知 Hilt 如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt 都会执行函数主体。
@Module
@InstallIn(SingletonComponent::class)
class DINetworkModule {
​
    /**
     * [OkHttpClient]依赖提供方法
     *
     * @return OkHttpClient
     */
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        // 日志拦截器部分
        val level = if (BuildConfig.VERSION_TYPE != VersionStatus.RELEASE) BODY else NONE
        val logInterceptor = HttpLoggingInterceptor().setLevel(level)
​
        return OkHttpClient.Builder()
            .retryOnConnectionFailure(true)
            .connectTimeout(15L * 1000L, TimeUnit.MILLISECONDS)
            .readTimeout(20L * 1000L, TimeUnit.MILLISECONDS)
            .writeTimeout(20L * 1000L, TimeUnit.MILLISECONDS)
            .addInterceptor(logInterceptor)
//            .addInterceptor(CookiesInterceptor())
            .build()
    }
​
    /**
     * 项目主要服务器地址的[Retrofit]依赖提供方法
     *
     * @param okHttpClient OkHttpClient OkHttp客户端
     * @return Retrofit
     */
    @Singleton
    @Provides
    fun provideMainRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(HttpBaseUrlConstant.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }
}

3.限定符

  • 限定符也是一种注解,当为某个类型定义了多个绑定时,我们可以使用它来标识该类型的特定绑定。
  • 使用@Qualifier注解实现限定符。

3.1为同一类型提供多个绑定

如果需要让Hilt以依赖项的形式提供同一类型的不同实现,那么必须向 Hilt 提供多个绑定,同一类型定义多个绑定可以使用限定符来实现。

//1. 接口和实现类
interface Phone {
    fun call()
}
​
class Huawei @Inject constructor() : Phone {
    override fun call() {
    }
}
​
class Xiaomi @Inject constructor() : Phone {
    override fun call() {
    }
}
​
//2. 创建多个类型的注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindHuawei
​
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindXiaomi
​
//@Retention:注解的生命周期
//AnnotationRetention.SOURCE:仅编译期,不存储在二进制输出中
//AnnotationRetention.BINARY:存储在二进制输出中,但对反射不可见
//AnnotationRetention.RUNTIME:存储在二进制输出中,对反射可见
​
​
//3. 在Hilt模块中使用注解
@Module
@InstallIn(ActivityComponent::class)
abstract class PhoneModel {
    @BindHuawei
    @Binds
    abstract fun bindHuawei(cpu: Huawei): Phone
​
    @ BindXiaomi
    @Binds
    abstract fun bindXiaomi(cpu: Xiaomi): Phone
}
​
//4. 使用依赖注入获取实例,可以用在字段注解,也可以用在构造函数或者方法入参中
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @BindHuawei
    @Inject
    lateinit var huawei: Phone
​
    @BindXiaomi
    @Inject
    lateinit var xiaomi: Phone
​
    fun use() {
        huawei.call()
        xiaomi.call()
    }
}

如果需要向某个类型添加限定符,那么应该向提供该依赖项的所有可能的渠道都添加限定符。这是因为让基本的实现或通用的实现在不带限定符的情况下避免出错,也是为了避免导致Hilt注入错误的依赖项。

3.2Hilt中的预定义限定符

Hilt提供了一些预定义的限定符。 Hilt 提供的@ApplicationContext 和 @ActivityContext 限定符用来获取Context 类

class ModuleOne @Inject constructor(@ApplicationContext private val context: Context)
​
class ModuleTwo @Inject constructor(@ActivityContext private val context: Context)

对于Application和Activity这两个类型,Hilt也是给它们预置好了注入功能(必须是这两个,即使子类也不可以)

class ModuleOne @Inject constructor(val application: Application)
​
class ModuleTwo @Inject constructor(val activity: Activity)

以上是Android开发中的Jetpack Hilt依赖注入学习,在Android开发进阶中还有大量的核心技术《Android核心技术手册》全部整理在这份文档中。上千个知识点30多个板块笔记,需要的可以参考学习进阶。

文末

HILT 好处

  • 降低 Android 开发者使用依赖注入框架的上手成本
  • 内部有一套标准的组件和作用域,对范围进行声明后,只能使用在指定的作用域中使用这个类,并且提供声明周期的管理,会自动释放不在使用的对象,减少资源的过度使用,提供代码的可重用性。
  • 使用起来简单,告别繁琐的 new。。。 这种流程,只需要添加注解即可。提高了代码的可读性,构建简单,耦合变低,容易测试
  • 我感觉最大的好处就是管理他们的生命周期,只能在对应的范围内进行使用。感觉非常好。

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

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

相关文章

Promise:工作流程、常见API、使用方法、手撕Promise、async/await

Promise和axios一、Promise的常见骚操作0.初体验1.使用Promise封装原生AJAX2.Promise实例对象的两个属性(1)状态属性PromiseState(2)结果值属性PromiseResult3.Promise的工作流程4.Promise的API(1).then和.…

ceph--理论

分布式存储--------Ceph 前言:随着OpenStack的快速发展,给Ceph的发展注入了强心剂,越来越多的人使用Ceph作为OpenStack的底层共享存储,Ceph在中国的社区也蓬勃发展起来。近两年OpenStack火爆度不及当年,借助于云原生尤…

SoringBoot+VUE前后端分离项目学习笔记 - 【01 环境配置以及VUE2集成ElementUI】

技术栈一览 SpringBoot2 Vue2 ElementUI Axios Hutool Mysql Echarts 所需软件环境 版本一览 JDK 1.8Mysql5.7Node 14.16.0navicatIdea 2021 Vue-cli 安装 npm install -g vue/cli 查看版本 创建VUE工程 初始化工程 vue create vue 选择Manually select feature…

【MySQL】数据库索引 - 浅谈索引类型

索引类型可以分为哈希表、有序数组和 N 叉树 不管是哈希还是有序数组,或者 N 叉树,它们都是基于其自身数据结构的特性来提高读写速度。在 NoSQL 里面还运用到了 LSM 树,来提高写的速度,还有跳表等数据结构来进行优化。 不过需要…

数据结构与算法-java

什么是数组? (1)数组是计算机中最基本的数据结构之一,我们会用一些名为索引的数字来标识每项数据在数组中的位置。 (2)大多数编程语言中索引是从0开始的。 (3)数组在内存中是存在连续…

如何打造一个流式数据湖

Flink将数据写入到 hudi 准备阶段 启动hadoop集群(单机模式) ./sbin/start-all.shhdfs离开安全模式 hdfs dfsadmin -safemode leave启动hive 后台启动元数据 ./hive --service metastore &启动hiveserver2 ./hiveserver2 &执行sql语句之前…

fpga实操训练(ip rom)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 altera的fpga本身自带了rom的ip,使用起来也十分方便。实际开发中,使用rom的场景也很多,比如一些默认的配置文件…

TensorFlow之回归模型-2

1 基本概念 回归模型 线性 线性模型 非线性模型 线性回归 逻辑回归 Log Loss(损失函数) 分类临界值 2 效率预测 回归问题是预测一个持续的值,主要是用于解决不确定性的问题,例如,一个商品在未来可能的价格或…

CMAKE_INSTALL_PREFIX

一、定义 CMAKE_INSTALL_PREFIX为cmake的内置变量,用于指定cmake执行install命令时,安装的路径前缀。Linux下的默认路径是/usr/local ,Windows下默认路径是 C:/Program Files/${PROJECT_NAME} 二、用…

dcloud如何苹果ios系统真机测试-HBuilderX真机运行ios测试

dcloud如何运行到IOS真机测试 1,下载安装iTunes 安装完毕后重新打开HBuilderX 2,点击运行真机 将iPhone 与电脑进行链接,点信任, 运行-运行到手机或模拟器-运行到IOS APP 基座 安装过itunes就会有显示,但是这里还有…

进程的学习 —— Linux下的进程

目录前言1 认识进程1.1 进程的概念1.2 进程的管理1.3 查看进程的两种方法1.4 getpid、getppid和fork函数2 进程状态2.1 普遍概念下的进程状态2.2 Linux下的进程状态2.2.1 测试Linux的各种进程状态2.2.2 僵尸进程2.3 孤儿进程3 进程切换与进程优先级3.1 并行、并发3.2 进程切换3…

kafka和sparkStreaming

1、Kafka 1、kafka集群架构 producer 消息生产者,发布消息到Kafka集群的终端或服务 broker Kafka集群中包含的服务器,一个borker就表示kafka集群中的一个节点 topic 每条发布到Kafka集群的消息属于的类别,即Kafka是面向 topic 的。 更通俗…

HDFS 常用命令

一、HDFS常用命令 1、查看版本 hdfs version 2、创建 HDFS 文件系统目录。 格式: hdfs dfs -mkdir /user/dir1 3、列出目录下的所有文件 类似 Linux Shell 的 ls 命令。用它可以列出指定目录下的所有文件 hdfs dfs -ls /user/ 4、把本地文件系统文件和目录拷贝…

整合Tkinter GUI界面的古诗词词云生成

Python语言提供的wordcloud词云功能,使文本数据的可视化,简单而美丽。但网上的大多数词云生成功能,多半没有可交互的GUI界面,使用起来稍觉不便。笔者结合网上的中文词云功能,以唐诗三百首,宋词三百首&#…

拟合算法(模型+代码)

拟合的结果是得到一个确定的曲线 最小二乘法的几何解释: argmin 存在参数k,b使括号里的值最小 第一种有绝对值,不易求导(求导在求最小值),计算较为复杂;所以我们往往使用第二种定义&#xff0…

什么软件可以录屏?这3款宝藏录屏软件,码住收藏

当我们处理剪辑视频时,我们需要使用到很多素材。有些素材我们可以直接从电脑网上进行下载。但有些素材我们在网上无法进行下载,这个时候就需要使用录屏软件进行录屏。什么软件可以录屏?今天小编向您分享3个宝藏录屏软件,赶紧码住收…

jmeter基础使用方法

文章目录一 配置环境变量二 Jmeter默认语言设置三 启动线程组的创建发送http请求数据报告一 配置环境变量 设置JMETER_HOME,及jemeter解压目录。 设置CLASSPATH,此处分别配置ApacheJMeter_core.jar和jorphan.jar所在位置。 关于环境变量配置多个值,在多个参数中间…

动态规划——状态压缩dp

文章目录概述状态压缩使用条件状压dp位运算棋盘(基于连通性)类问题概述例题蒙德里安的梦想小国王玉米田炮兵阵地集合类问题概述例题最短Hamilton路径愤怒的小鸟总结概述 状态压缩 状态压缩就是使用某种方法,简明扼要地以最小代价来表示某种…

MySQL 进阶篇2.0 存储过程 触发器 锁 InnoDB引擎

45.存储过程-介绍 46.存储过程-基本语法 -- 查看 select * from information_sc

Python中import语句用法详解

一. 什么是模块(module)? 在实际应用中,有时程序所要实现功能比较复杂,代码量也很大。若把所有的代码都存储在一个文件中,则不利于代码的复用和维护。一种更好的方式是将实现不同功能的代码分拆到多个文件…