Jetpack 新成员 Hilt 实践(一,真是恍然大悟啊

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

然后在 App 模块中的 build.gradle 文件中添加以下代码。

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
} 

坑:需要注意的是如果同时使用 hilt 和 data binding,Android Studio 的版本必须 >= 4.0

所以还没有升级的朋友们,尽快升级吧,升级到 Android Studio 4.0 也会遇到一些坑,不过好在这些坑现在都有相应的解决方案了。

Hilt 使用 Java 8 的功能,所以要在项目中启用 Java 8,需要在 App 模块的 build.gradle 文件中,添加以下代码

android {
  ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
} 

注意: 这里有一个坑,对于 Kotlin 项目,需要添加 kotlinOptions,这是 Google 文档 Dependency injection with Hilt 中没有提到的,否则使用 ViewModel 会编译不过,下文会有详细的讲解。

Hilt 依赖添加方式相比于 Koin 太麻烦了,使用 koin 只需要添加相应的依赖就可以使用了。

Application 是 App 的入口,所以所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application

@HiltAndroidApp
class HiltApplication : Application() {
    /**
     * 1. 所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application
     * 2. @HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖项容器的基类
     * 3. 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其他组件访问的依赖
     * 4. 在 Application 中设置好 @HiltAndroidApp 之后,就可以使用 Hilt 提供的组件了,
     *    Hilt 提供的 @AndroidEntryPoint 注解用于提供 Android 类的依赖(Activity、Fragment、View、Service、BroadcastReceiver)等等
     *    Application 使用 @HiltAndroidApp 注解
     */
} 
  1. @HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖容器的基类
  2. 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其他组件访问的依赖

准备工作都做完了,接下来我们来看几个例子,如何使用 Hilt 进行依赖注入。

如何使用 Hilt 进行依赖注入

我们先来看一个简单的例子,注入 HiltSimple 并在 Application 中调用它的 doSomething 方法。

class HiltSimple @Inject constructor() {
    fun doSomething() {
        Log.e(TAG, "----doSomething----")
    }
}

@HiltAndroidApp
class HiltApplication : Application() {
    @Inject
    lateinit var mHiltSimple: HiltSimple

    override fun onCreate() {
        super.onCreate()
        mHiltSimple.doSomething()
    }
} 

Hilt 需要知道如何从相应的组件中提供必要依赖的实例。使用 @Inject 注解来告诉 Hilt 如何提供该类的实例,@Inject 常用于构造函数、非私有字段、方法中。

Hilt 如何和 Android 组件一起使用

如果是 Hilt 支持的 Android 组件,直接使用 @AndroidEntryPoint 注解即可。

/**
 *
 * 为项目中的每个 Android 类生成一个 Hilt 组件,这些组件可以从它们各自的父类接收依赖项,
 * 如果是抽象类则不能使用 @AndroidEntryPoint 注解
 *
 * 如果使用 @AndroidEntryPoint 注解 Android 类,还必须注解依赖于它的 Android 类,
 * 例如 如果 注解 fragment 然后还必须注解  fragment 依赖的 Activity, 否则会抛出以下异常
 * java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity
 */
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 用到了 Fragment 1.2.0 中重要的更新
        // 可以查看之前写的这篇文章 @see https://juejin.im/post/6844904167685750798
        supportFragmentManager.beginTransaction()
            .add(R.id.container, HiltFragment::class.java, null)
            .commit()
    }
}

/**
 *  如果 注解 fragment 然后还必须注解  fragment 依赖的 Activity, 否则会抛出以下异常
 * java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity
 */
@AndroidEntryPoint
class HiltFragment : Fragment() {

    // 使用 @Inject 注解从组件中获取依赖
    @Inject
    lateinit var mHiltSimple: HiltSimple

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_hilt, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        mHiltSimple.doSomething()
    }
} 
  • 如果是抽象类则不需要使用 @AndroidEntryPoint 注解。
  • @AndroidEntryPoint 注解 仅仅支持 ComponentActivity 的子类例如 FragmentActivity、AppCompatActivity 等等。
  • 如果使用 @AndroidEntryPoint 注解 Android 类,必须在它依赖的 Android 类添加同样的注解,例如在 Fragment 中添加 @AndroidEntryPoint 注解,必须在 Fragment 依赖的 Activity 上也添加 @AndroidEntryPoint 注解。

注意: 在 Activity 中添加 Fragment,用到了 Fragment 1.2.0 中重要的更新,可以查看之前写的这篇文章 [译][Google工程师] 详解 FragmentFactory 如何优雅使用 Koin 以及部分源码分析

Hilt 如何和第三方组件一起使用

如果要在项目中注入第三方依赖,我们需要使用 @Module 注解,使用 @Module注解的普通类,在其中创建第三方依赖的对象。

@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,因此 NetworkModule 绑定到 Application 的生命周期。
object NetworkModule {

    /**
     * @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
     * @Singleton 提供单例
     */
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun provideGitHubService(retrofit: Retrofit): GitHubService {
        return retrofit.create(GitHubService::class.java)
    }
} 
  • @Module 常用于创建依赖类的对象(例如第三方库 OkHttp、Retrofit等等)。
  • 使用 @Module 注入的类,需要使用 @InstallIn 注解指定 module 的范围,会绑定到 Android 类对应的生命周期上。
  • @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。

Hilt 如何和 ViewModel 一起使用

在 App 模块中的 build.gradle 文件中添加以下代码。

implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01' 

注意: 这个是在 Google 文档上没有提到的,如果使用的是 kotlin 的话需要额外在 App 模块中的 build.gradle 文件中添加以下代码,否则调用 by viewModels() 会编译不过。

// For Kotlin projects
kotlinOptions {
    jvmTarget = "1.8"
} 

在 ViewModel 对象的构造函数中使用 @ViewModelInject 注解提供一个 ViewModel。

class HiltViewModel @ViewModelInject constructor(
) : ViewModel() {

    /**
     * 在 LifeCycle 2.2.0 之后,可以用更精简的方法来完成,使用 LiveData 协程构造方法 (coroutine builder)。
     * liveData 协程构造方法提供了一个协程代码块,产生的是一个不可变的 LiveData,emit() 方法则用来更新 LiveData 的数据。
     *
     * 具体可以查看之前写的这篇文章 [https://juejin.im/post/6844904193468137486#heading-10] 有详细介绍
     */
    val mHitLiveData = liveData {
        emit(" i am a ViewModelInject")
    }
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val mHitViewModule: HiltViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        mHitViewModule.mHitLiveData.observe(this, Observer {
            tvResult.setText(it)
        })
    }
} 

在 HiltViewModel 里面使用了 LifeCycle 2.2.0 之后新增的方法,LiveData 协程构造方法提供了一个协程代码块,产生的是一个不可变的 LiveData,emit() 方法则用来更新 LiveData 的数据,具体可以查看之前写的这篇文章 Jetpack 成员 Paging3 实践以及源码分析(一) 里面有详细介绍。

Hilt 如何和 Room 一起使用

这里需要用到 @Module 注解,使用 @Module 注解的普通类,在其中提供 Room 的实例。

@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,因此 NetworkModule 绑定到 Application 的生命周期。
object RoomModule {

    /**
     * @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
     * @Singleton 提供单例
     */
    @Provides
    @Singleton
    fun provideAppDataBase(application: Application): AppDataBase {
        return Room
            .databaseBuilder(application, AppDataBase::class.java, "dhl.db")
            .fallbackToDestructiveMigration()
            .allowMainThreadQueries()
            .build()
    }

    @Provides
    @Singleton
    fun providePersonDao(appDatabase: AppDataBase): PersonDao {
        return appDatabase.personDao()
    }
} 

总结

全文到这里就结束了,本篇文章的案例已经全部上传到了 GitHub:HiltSimple

这篇文章里面分别介绍了 @HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module、@InstallIn、@Provides 的含义以及实战案例,下篇文章我们一起来分析一下 @EntryPoint,以及和其他 Jetpack 组件如何一起使用。

需要注意的是使用 Hilt 有三个需要注意的地方

  • 如果注解非 ComponentActivity 子类,例如 Activity 则会抛出以下异常。

    Activities annotated with @AndroidEntryPoint must be a subclass of androidx.activity.ComponentActivity. (e.g. FragmentActivity, AppCompatActivity, etc.) 
    
  • 如果使用 @AndroidEntryPoint 注解 Android 类,必须在它依赖的 Android 类添加同样的注解,例如在 Fragment 中添加 @AndroidEntryPoint 注解,必须在 Fragment 依赖的 Activity 上也添加 @AndroidEntryPoint 注解 , 否则会抛出以下异常。

    java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity 
    
  • 需要注意的是如果同时使用 hilt 和 data binding,Android Studio 的版本必须 >= 4.0

    所以还没有升级的朋友们,尽快升级吧,升级到 Android Studio 4.0 也会遇到一些坑,不过好在这些坑现在都有相应的解决方案了。

Hilt 如果和 ViewModel 一起使用有点需要注意

这个是在 Google 文档上没有提到的,如果使用的是 kotlin 语言的话,需要额外在 App 模块中的 build.gradle 文件中添加以下代码,否则调用 by viewModels() 会编译不过。

kotlinOptions {
    jvmTarget = "1.8"
} 

计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。

结语

致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,正在努力写出更好的文章,如果这篇文章对你有帮助给个 star,一起来学习,期待与你一起成长。

算法

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

Jetpack 新成员 Hilt 实践(一,真是恍然大悟啊

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

Jetpack 新成员 Hilt 实践(一,真是恍然大悟啊

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以去我的主页加一下技术群。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

大家。

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

[外链图片转存中…(img-CXV0U6L7-1630933131433)]

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以去我的主页加一下技术群。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下~

上一篇:Jetpack--->ViewModel知识点梳理


下一篇:jetpack实战之startup处理应用程序启动初始化