Jetpack学习之 Hilt,安卓面试题2024笔试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

Git学习地址:传送门

1. 概述

=======================================================================

随着Android 11 的发布,Jetpack家族新添了 HiltApp Startup等成员。

而 Hilt 是被定义为 依赖注入框架而被发布。什么?又是依赖注入框架?不是之前已经有了一个 Dagger2 了吗?除了 Dagger2, 还有 ButterKnife ,Kotlin甚至还有轻量级的 Koin

为什么?为什么谷歌这么 这么的想让我们去了解依赖注入,并使用依赖注入的框架呢?下面请听我慢慢分析。

1.1 依赖注入(DI)概念


什么是依赖注入?先来看下面代码:

class MyClass {

val user = User()

}

我们在一个类 MyClass 中 声明了一个变量 user,并初始化-------调用 User 构造函数,创建一个对象。

上面这段代码就产生了一个依赖关系

我们要先看懂谁依赖了谁?首先 MyClass 是我们的类,User 可以是我们自己写的类,也可以是通过第三方Jar包或SDK里面的类。

在我们写的 MyClass 的代码里面,我们需要一个 User 对象来完成一些任务,所以我们创建了 User 对象,这就说明 MyClass 依赖了 User。对于 MyClass来说,User是外面之物,但是又需要依赖它。

如果上面这个 User,不是由自己创建,而是由外部创建,然后在本类只做赋值工作 ,这个过程就是 依赖注入

有一个我们非常熟悉的设计模式,就使用了依赖注入的方法—工厂模式

class UserFactory {

fun newUser(): User{

return User()

}

}

class MyClass {

val user = UserFactory.newUser()

}

我们的 MyClass 类需要使用 User 类,但是这次没有自己来创建(没有自己new出来),而是交由给 UserFactory 来创建出来,MyClass就做了最后的赋值工作。

对于 MyClass 来说,这就是一次依赖注入,和上面例子相比,把对象创建的过程交由给了别的类。

所以我们通过上面两个例子就能知道依赖注入的本质是什么:借由外部得到对象依赖注入框架就是这个外部

现在流行的 Dagger2、Koin框架,只是让我们更轻松、更容易的去得到对象。

Dagger的中文翻译是 “刀”,它就像一把刀,插进我们代码中。那相信你也知道 ButterKnife 为什么这么取名了吧。

PS: 对于我来说,针筒注射更适合来描述这个过程。

1.2 为什么在代码中使用外部去注入依赖


1.2.1 满足合理的架构设计

举一个Andorid常用的例子。 在 MVP / MVC / MVVM 架构没有流行之前,我们的代码都是如何的?

我们会把所有的 数据逻辑代码、视图代码都写在了 Activity / Fragment 中。

这会导致什么结果:

  1. Activity / Fragment 代码臃肿,逻辑混乱

  2. 难维护,难复用

举一个代码例子,我们要在冰箱中放一个苹果,那么我们代码会这样写:

class Fridge {

val apple = Apple() // 创建一个苹果

fun store() {

storeAnApple(apple) // 存放苹果

}

}

这样写有没有问题?如果你觉得没有问题,那我的文章你应该继续往下读,如果觉得有问题,那你可以跳过这整节了。

Fridge 是冰箱类,那么按照 单一职责原则,它的作用应该是存储物品。但是在代码中,它却做了一个事情:那就是将苹果创建出来了。

冰箱类,它不应该知道苹果是如何产生的,它只需要拿到苹果并存储就行了。不然的话,冰箱要存储香蕉,那岂不是又new出一个香蕉,要放水果蔬菜,又要new出水果蔬菜…

显然,我们不需要 Fridge 来创建 Apple 对象,所以我们需要外部来帮我创建。比如苹果工厂。

或者我们使用 依赖注入框架 Dagger2,通过类似下面的代码:

class Fridge {

@Inject lateinit val apple: Apple // 从DI框架中拿到一个苹果

fun store() {

storeAnApple(apple) // 存放苹果

}

}

这样,冰箱就可以不用关心苹果的构造,而直接存储了。

1.2.2 满足一些数据共享场景

假设我们 Activity 有一个自定的TextView,并且该 TextView 需要依赖 MainActivity 的数据,那么这个TextView要这样写:

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

@Inject lateinit var user: User

}

class MyTextView: TextView(…) {

// 需要用到 MainActivity中的 user 对象

override fun onAttachedToWindow() {

super.onAttachedToWindow()

// 强制转化

text = (context as MainActivity).user.name

}

}

这样的代码很丑,因为做了强转,并且只适用于 MainActivity,不灵活。

而如果使用了Dagger、Hilt框架,就能这样写:

@ActivityScoped

data class User(var id: Int, var name: String, var mood: String) {

}

@AndroidEntryPoint

class MyTextView(context: Context?, attrs: AttributeSet?) : TextView(context, attrs) {

@Inject

lateinit var user: User

override fun onAttachedToWindow() {

super.onAttachedToWindow()

// 直接使用 MainActivity的

text = user.name

}

}

这里就能直接使用到 MainActivity的user对象。相当于 MainActivity 中注入后 User 后,又在代码的运行中将该数据分享到了 MyTextView 中。

在我们的代码中,就有许多需要数据共享的地方,比如 OkHttp / Retrofit 的单例,一些数据Bean等。

那这里有一个问题:假如我的数据不需要被共享,只在一处用,那我还要给它做依赖注入吗?

答案是:根据设计原则的扩展性,我们不能在一开始就断定一个类之后的迭代中是否会被各种类使用、继承,既然我们保证不了其以后不被共享,那我们就可以在一开始设计时,给它使用依赖注入的形式被创建,这样便于以后的迭代。

1.3 Hilt是什么


了解了 依赖注入 是什么之后,我们再来了解 Hilt,反正我们知道他是一种得到对象的手段。

Hilt 是基于 Dagger2 的针对 Android场景定制化 的框架

这有点像什么? RxAndroid 是 RxJava 的Android平台定制化扩展。Andorid虽然由Java、Kotlin构成,但是它有很多平台的特性,比如它有 Java开发 所不知道的 Context 等。

Dagger框架虽然很出名,在国外也很流行,但是在国内使用其的App少之又少,列举一些缺点:

  1. 上手难,众多Android应用框架中,Dagger必定是最难学的那一档;

  2. 它就是一个复杂框架,没有针对任何平台,所以对于所有平台来说会难用;

  3. 在Android Studio4.0版本以前,无法追踪Dagger的依赖关系(就类比IDE无法通过快捷键知道一个接口有哪些实现类)

  4. 开发者不知道为啥要做依赖注入

对于第三点,Android Studio4.1已经支持了该功能,但是4.1有许多Bug,很多开发者都没有升级 = =。

Hilt的出现解决前两点问题,因为 Hilt 是 Dagger 针对Android平台的场景化框架,比如Dagger需要我们手动声明注入的地方,而Android声明的地方不都在 onCreate()吗,所以Hilt就帮我们做了,除此之外还做了很多事情,这样一来,相较于Dagger,我们能用更少代码、更容易、更轻松的配置依赖注入。

至于 KoinHilt 的对比,两者配置的代码都比较少, 所以比较的层次更多在性能方面,我因为没有学习过Koin,所以这里就不做比较,可以看看这篇文章: 全方面分析 Hilt 和 Koin 性能

1.4 Hilt使用地方


DaagerKoinHilt,Google为什么致力于让开发者使用DI框架,可以看下Android开发者文档的这篇文章:传送门

Google认为移动端应用的架构设计,最重要的 Separation of concerns(分离关注点)。上网找解释,其实它就是 模块解耦。下面是Google官方推荐的Android应用架构图:

在这里插入图片描述

该图不多做说明,郭神的文章中就已经分析了该图片,Jetpack完全就是为该图架构服务。

Activity / Fragment是由系统所创建的,所以我们可以不用关心如何去创建。 ViewModel 层由 JetPack 来提供,所以我们也不用关心其创建。但是该有谁来创建 Repository呢?

该架构下,Activity是不知道 Repository 存在的, 而 ViewModel 显然不能来创建 Repository,因为它只是依赖。

如果我们将Repostirory 设置成单例类,那每处地方都能够引用它,这样的处理显得有些不好。

这个问题比较棘手,但是如果我们使用 依赖注入框架,就能灵活的解决这个问题了。

2. Hilt使用

===========================================================================

2.1 导入


在 app 的 build.gradle 中加入:

plugins {

id ‘kotlin-kapt’

id ‘dagger.hilt.android.plugin’

}

dependencies {

implementation ‘com.google.dagger:hilt-android:2.28-alpha’

kapt ‘com.google.dagger:hilt-android-compiler:2.28-alpha’

}

在 project的 build.gradle 中加入:

classpath ‘com.google.dagger:hilt-android-gradle-plugin:2.28-alpha’

2.2 一个简单的例子


Hilt 需要 AndroidManifest 使用带有 @HiltAndroidApp 注解的 Application 类,所以我们的 Application需要这样:

@HiltAndroidApp

class HiltApp : Application() {

}

然后在 AndroidManifest 文件中声明:

<application

android:name=“.HiltApp”

假如我们要在 MainActivity 中注入一个 User 对象, 我们首先编写一个 User 类,User类有两个属性 nameage

诶,这个时候有同学就会问了:我通过 @Inject 声明一个User,Hilt就能给我创建一个 User 对象,那这个User对象里面的name和age是啥?答案是:编译会报错,因为我们自己都不知道这两个参数是啥,Hilt怎么可能会知道,所以这两个属性也要通过依赖注入的方式来注入。

为了简化这个问题,我定义一个默认的无参构造函数,反正创建之后,里面的值也是可以修改的嘛。

data class User(var name: String, var age: Int) {

// 定义一个默认的无参构造函数,并使用 @Inject 注解修饰

@Inject

constructor() : this(“Rikka”, 23)

}

接着我们在 MainActivity 中声明一个 User,通过 @Inject 来修饰,并且MainActivity 需要通过 @AndroidEntryPoint 修饰:

// 1

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

// 2

@Inject

lateinit var user: User

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

Log.d(TAG, “user name: u s e r . n a m e a g e : {user.name} age: user.nameage:{user.age}”)

}

}

Logcat 打印结果如下:

在这里插入图片描述

代码解析:

注释1: 为 MainActivity 修饰 @AndroidEntryPoint,该注解表明 该类为需要进行依赖注入的 Android类,是Dagger针对Android场景化的地方。当我们类中需要进行依赖注入,我们为该类加入这个注解,它会帮助创建一个单独的 Hilt组件。它不能修饰Abstract,它只能修饰:

  • ComponentActivity

  • (Support)Fragment

  • View

  • Service

  • BroadcastReceiver

注释2:我们需要注入一个 User,所以我们给它加一个 @Inject 注解,告诉 Hilt。因为Kotlin的语法问题,这里不得不声明 lateinit(而这里Koin的写法更加优雅),接下来步骤大概是这样的:

  1. Hilt 会去找 User 这个类的构造函数,以此来创建一个对象

  2. Hilt 发现 有两个个构造函数,而无参构造函数被 @Inject 声明

  3. Hilt 会去调用被 @Inject 的构造函数,创建一个 User("Rikka", 23) 对象

  4. 返回这个对象, MainActivity 实现外部帮忙创建 User对象,实现 User 的依赖注入。

Inject 的中文翻译是 “注入、注射”,所以可以形象的认为, @Inject 修饰的变量是被外界通过针筒注入进来的。

@Inject 可以修饰

  • 构造函数 Constructors

  • 变量 Fields

  • 方法 Methods

构造函数是最先被注解的,然后再是变量和方法。所以它修饰构造函数和修饰变量,其实是不同的作用。但为了便于理解,我们可以把它看成是一个插眼工具,便于Hilt去寻找要注入的地方。

我们上面的 User 类是无参构造函数,这次假设我们要有参数的呢?其实就是参数也要注入嘛,这就是套娃来的,来看看我们给User新增一个 属性:Clothes

class Clothes @Inject constructor() {

}

class User @Inject constructor(var clothes: Clothes){

}

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

Log.d(TAG, “user clothes:${user.clothes}”)

}

打印结果:

在这里插入图片描述

PS:大家不要太拘泥于有参构造函数的创建,我认为注入的作用是创建出一个对象,这个对象里面的内容可以后续再传入,它更多的体现、或者我们需要注意的是 “分离关注点”

2.3 实现接口实例注入


因为接口没有构造函数,所以当我们想要依赖一些接口时,该怎么办。

我们来下面的示例,我们写一个 Profession 接口,代表职业:

interface Profession {

fun doJob()

}

假设我们有两个实现接口的类:医生类和程序猿类:

class Doctor : Profession{

override fun doJob() {

Log.d(“Doctor”, “doctor do job”)

}

}

class Programmer : Profession{

override fun doJob() {

Log.d(“Programmer”, “programmer do job”)

}

}

这个时候我给 User 类添加一个职业的属性,并希望它能够自动注入:

class User @Inject constructor(var clothes: Clothes){

@Inject

lateinit var profession: Profession

}

因为 Profession 是一个接口,它有两个实现类,所以这样 Hilt 并不能知道我们要实例化哪个具体的实现类,所以编译的时候就会报错。

而 Hilt 也解决这种问题,首先我们要在每个实现类上注入构造函数:

class Doctor @Inject constructor() : Profession{

}

class Programmer @Inject constructor() : Profession{

}

接着我们需要实现一个和该接口有关的 XXXModule类,它被 @Module 修饰,这个和 Dagger 中的一样,代表它会为接口提供一个创建实例的工厂,同时需要加上 @InstallIn 注解,用来声明它是被安装到哪个组件中,该注解后面会说到。 代码如下:

@Module

@InstallIn(ActivityComponent::class)

abstract class ProfessionModule { // 1

// 2

@Binds

abstract fun bindDoctor(doctor: Doctor): Profession

}

注释1: 我们写出来的类是一个抽象类,因为我们不需要具体的实现它,而且它没有具体的命名规则,因为我们也不会在代码中直接调用它,但是为了便于理解,我们起名一般叫 接口名 + Module

注释2: 我们假设该Module提供一个 Doctor 的职业,那我们需要定义一个 抽象方法 来获取一个Doctor类。 并且 该方法需要被 @Binds 注解修饰。这样就能被 Hilt 识别。

这样一来,我们就实现了接口的一个实例化的注入,我们来实验一下,在 User 中去展示它:

class User @Inject constructor(var clothes: Clothes){

@Inject

lateinit var profession: Profession

fun showMyself() {

profession.doJob()

}

}

// MainActivity

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

@Inject

lateinit var user: User

override fun onCreate(savedInstanceState: Bundle?) {

写在最后

本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!

最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:

对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

Android架构师之路很漫长,一起共勉吧!

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ompatActivity() {

@Inject

lateinit var user: User

override fun onCreate(savedInstanceState: Bundle?) {

写在最后

本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!

最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:

[外链图片转存中…(img-9lVmMXt7-1713558774636)]

对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

Android架构师之路很漫长,一起共勉吧!

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

[外链图片转存中…(img-uvycMhxS-1713558774637)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-nPsB8ISa-1713558774637)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值