Jetpack架构组件学习(5)——Hilt 注入框架使用
本篇需要有Kotlin基础知识,否则可能阅读本篇会有所困难!
介绍说明
实际上,郭霖那篇文章已经讲得比较明白了(具体参考链接都贴在下文了),这里简单总结下:
如果按照之前我们的MVC写法,我们可以直接在activity中发起网络请求,但发起网络请求我们需要调用一个Api对象的具体方法,而Api对象只能在activity中进行创建
这里activity和api对象实际上就是耦合关系,从客观上讲,我们activity不应该去负责创建一个api对象
所以使用注入框架,相当于有了个中间人帮activity处理,至于是中间人直接找到api对象,或者是中间人进行创建api对象,activity都不关心,activity只知道去找中间人就能帮得到一个api对象
优点
学习之后,目前感觉到的优点:
- 可能大型项目,多module那种比较适合
- MMVM/MVI架构的app也比较适合
- 注入接口,方便不同逻辑实现
可能就是接口注入可能有些用处,比如上面例子,假设我们网络框架刚开始用的是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()
}
}
和上面接口类型注入不同,需要注意以下几点:
- ClientModule这个类不是抽象类了
@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新成员,一篇文章带你玩转Hilt和依赖注入 - 掘金
- 玩转Jetpack依赖注入框架——Hilt - 掘金
- Android使用Hilt依赖注入,让人看不懂你代码 - 掘金
- 使用 Hilt 实现依赖项注入 | Android Developers
Jetpack架构组件学习(5)——Hilt 注入框架使用的更多相关文章
- Jetpack架构组件学习(1)——LifeCycle的使用
原文地址:Jetpack架构组件学习(1)--LifeCycle的使用 | Stars-One的杂货小窝 要看本系列其他文章,可访问此链接Jetpack架构学习 | Stars-One的杂货小窝 最近 ...
- Jetpack架构组件学习(2)——ViewModel和Livedata使用
要看本系列其他文章,可访问此链接Jetpack架构学习 | Stars-One的杂货小窝 原文地址:Jetpack架构组件学习(2)--ViewModel和Livedata使用 | Stars-One ...
- Jetpack架构组件学习(3)——Activity Results API使用
原文地址:Jetpack架构组件学习(3)--Activity Results API使用 - Stars-One的杂货小窝 技术与时俱进,页面跳转传值一直使用的是startActivityForRe ...
- Jetpack架构组件学习(4)——APP Startup库的使用
最近在研究APP的启动优化,也是发现了Jetpack中的App Startup库,可以进行SDK的初始化操作,于是便是学习了,特此记录 原文:Jetpack架构组件学习(4)--App Startup ...
- Jetpack 架构组件 Room 数据库 ORM MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Jetpack 架构组件 LiveData ViewModel MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Jetpack 架构组件 Lifecycle 生命周期 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Android 架构:Android Jetpack 架构组件的学习和分析
参考:https://mp.weixin.qq.com/s/n-AzV7Ke8wxVhmC6ruUIUA 参考:https://jekton.github.io/2018/06/30/android- ...
- Android Jetpack 架构组件最佳实践之“网抑云”APP
背景 近几年,Android 相关的新技术层出不穷.往往这个技术还没学完,下一个新技术又出来了.很多人都是一脸黑人问号? 不少开发者甚至开始哀嚎:"求求你们别再创造新技术了,我们学不动了!& ...
- Jetpack 架构组件 Paging 分页加载 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
随机推荐
- 解决模拟器中交换机不通的bug
模拟器实在是坑,相信大家都遇到过. 几个交换机连在一起,有冗余的线路,连两台PC,结果它们死活都通不了. 而且!!!交换机还会疯狂报错 *Aug 2 03:06:39.561: %AMDP2_FE-6 ...
- CLR via C# 笔记 -- 计算限制的异步操作(27)
1. 线程池基础. 创建和销毁线程是一个昂贵的操作,要耗费大量时间.太多的线程会浪费内存资源.由于操作系统必须调度可运行的线程并执行上下文切换,所以大多的线程还对性能不利.为了改善这个情况,CLR包含 ...
- python重拾第八天-Socket网络编程
本节内容 Socket介绍 Socket参数介绍 基本Socket实例 Socket实现多连接处理 通过Socket实现简单SSH 通过Socket实现文件传送 作业:开发一个支持多用户在线的FTP程 ...
- vim中ctags 的使用
--- title: vim中ctags 的使用 EntryName: vim-config-with-ctags date: 2020-08-19 11:17:38 categories: tags ...
- OpenCV程序练习(四):人脸识别
一.人脸检测 准备图片 代码 import cv2 img=cv2.imread("Faces.jpeg") faceCascade=cv2.CascadeClassifier(' ...
- win10 搭建 npm 环境
前言 最近,根据CSDN和博客园等文章的帮助下,搭建了一个npm的环境,现在将搭建过程记录下来,留作参考. 搭建过程 下载nodejs,我是使用的zip包安装的,安装包官网地址https://node ...
- 1. 简述一下你对 HTML 语义化的理解?
用正确的标签做正确的事情.1.HTML 语义化让页面的内容结构化,结构更清晰,便于对浏览器.搜索引擎解析; 2.即使在没有样式 CSS 的情况下也能以一种文档格式显示,并且是容易阅读的; 3.搜索引擎 ...
- yb课堂之跨域配置 《二十三》
CorsInterceptor.java package net.ybclass.online_ybclass.interceptor; import org.springframework.http ...
- [oeasy]python019_ 如何在github仓库中进入目录_找到程序代码_找到代码
继续运行 回忆上次内容 上上次 真写了万行代码 这 万行代码 都是写在明面上的 这次 使用git命令 下载了 github上面的仓库 添加图片注释,不超过 140 字(可选) 下载仓库 ...
- es6高级~promise
1.Promise对象 Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值.其作用是为了解决回调地狱 回调地狱:回调函数的结果作为下一个回调函数的参数时,产生回调链,也称之为回调 ...