跟着Google学-Hilt

快速上手Google 提供的依赖注入框架 「Hilt」,这些知识点你会吗 回顾当初学习依赖注入的过程, 我带着以下的问题慢慢的熟悉了 Hilt 怎么告诉「Hilt」当前变量需要被提供实例 怎么告诉「Hilt」该去哪里拿实例 怎么告诉「Hilt」产生的实例该不该以单例的形式提供 从使用的角度上讲,「Hilt」通过解析注解识别并解决上面的问题。所以,使用「Hilt」其实很大程度上是学习其提供的各种注解。 那么「Hi lt」提供了哪些注解, 我们又该如何根据自己的需求来选择。 使用步骤 @HiltAndroidApp: 初始化:自定义 Application 为其添加 @HiltA 阅读详情

Hilt最详细教程

by google
在这里插入图片描述

1. 简介

在本 Codelab 中,您将了解在打造可靠且能够顺利发展为大型项目的应用时,依赖项注入 (DI) 的重要性。我们将使用 Hilt 作为依赖项注入工具来管理依赖项。

依赖项注入是一种在编程中运用广泛的技术,非常适用于 Android 开发。遵循依赖项注入的原则可以为良好的应用架构奠定基础。

实现依赖项注入可为您带来以下优势:

  • 重用代码
  • 易于重构
  • 易于测试

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

Hilt 通过为项目中的每个 Android 组件提供容器并自动为您管理容器生命周期,提供了一种在应用中执行依赖项注入的标准方法。这通过利用热门依赖项注入库 Dagger 实现。

如果在此 Codelab 的操作过程中遇到任何问题(代码错误、语法错误、措辞含义不明等),欢迎通过 Codelab 左下角的“报告错误”链接向我们报告相应问题。

前提条件

  • 您有 Kotlin 语法经验。
  • 您了解依赖项注入为什么对应用至关重要。

如需详细了解依赖项注入,请参阅以下资源:

  1. 依赖项注入基础知识
  2. Android 中的手动依赖项注入

学习内容

  • 如何在 Android 应用中使用 Hilt。
  • 与打造可持续发展的应用相关的 Hilt 概念。
  • 如何使用限定符为同一类型添加多个绑定。
  • 如何使用 @EntryPoint 从 Hilt 不支持的类访问容器。
  • 如何使用单元测试和插桩测试来测试使用 Hilt 的应用。

所需条件

  • Android Studio 4.0 或更高版本。

2. 准备工作

获取代码

从 GitHub 获取 Codelab 代码:

$ git clone https://github.com/googlecodelabs/android-hilt

或者,您可以下载 Zip 文件形式的代码库:

下载 Zip 文件

打开 Android Studio

本 Codelab 需要 Android Studio 4.0 或更高版本。如果您需要下载 Android Studio,可以在此处下载。

运行示例应用

在本 Codelab 中,您将向记录用户互动并使用 Room 将数据存储到本地数据库的应用添加 Hilt。

按照下列说明在 Android Studio 中打开示例应用。

  • 如果您是下载的 zip 压缩档,请在本地解压缩文件。
  • 在 Android Studio 中打开项目。
  • 点击 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Run 按钮,然后选择模拟器或连接 Android 设备。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如您所见,每当您与某个有编号的按钮互动时,系统都会创建并存储一条日志。在 See All Logs 屏幕中,您将看到之前所有互动的列表。如需移除日志,请点按 Delete Logs 按钮。

项目设置

本项目在多个 GitHub 分支中构建:

  • master 是您签出或下载的分支,也是本 Codelab 的起点。
  • solution 包含本 Codelab 的解决方案。

建议您从 master 分支中的代码着手,按照自己的节奏逐步完成 Codelab。

在本 Codelab 中,系统会为您显示需要添加到项目的代码段。在某些地方,您还需要移除代码,我们将在代码段的注释中明确标出这部分内容。

如需使用 Git 获取 solution 分支,请使用以下命令:

$ git clone -b solution https://github.com/googlecodelabs/android-hilt

3. 将 Hilt 添加到项目中

为什么选择 Hilt?

如果您查看起始代码,会看到 ServiceLocator 类的一个实例存储在 LogApplication 类中。ServiceLocator 会创建并存储依赖项,供需要它的类按需获取。您可以将其视为依赖项的容器,这种容器附着于应用的生命周期,因为当应用不存在时它将随之销毁。

容器是一个类,负责在您的代码库中提供依赖项,并且知道如何为应用的其他类型创建实例。它通过创建依赖项并管理其生命周期来管理提供这些实例所需的依赖关系图。

容器给出了一些方法,可用于获取它所提供的类型的实例。这些方法可能会始终返回不同的实例或相同的实例。如果方法始终提供相同的实例,则表明相应类型的作用域限定为容器。

如 Android 依赖项注入指南中所述,若使用服务定位器,一开始样板代码较少,但随着代码规模逐渐增长,会出现一些问题。如需开发大型 Android 应用,您应使用 Hilt。

在 Android 应用中使用手动依赖项注入或服务定位器模式需要样板代码,而 Hilt 通过生成本应由您手动创建的代码(例如,ServiceLocator 类中的代码),消除了不必要的样板代码。

如需详细了解服务定位器模式,请参阅关于在 Android 上实现依赖项注入的指南。

在接下来的步骤中,您将使用 Hilt 来替换 ServiceLocator 类。然后,我们会为该项目添加新功能,以探索 Hilt 的更多功能。

在项目中使用 Hilt

Hilt 已在 master 分支(您下载的代码)中进行配置。您无需将以下代码添加到项目中,因为我们已代您执行该操作。尽管如此,我们还是来看看在 Android 应用中使用 Hilt 需要做些什么。

除了库依赖项之外,Hilt 还会使用在项目中配置的 Gradle 插件。打开根 build.gradle 文件,并在类路径中找到以下 Hilt 依赖项:

buildscript {
    ...
    ext.hilt_version = '2.28-alpha'
    dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

然后,为在 app 模块中使用 gradle 插件,我们应在 app/build.gradle 文件中指定它,具体方法为:将 gradle 插件添加到此文件的顶部、kotlin-kapt 插件之下:

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

android {
    ...
}

最后,Hilt 依赖项会包含在项目的同一个 app/build.gradle 文件中:

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

在您构建和同步项目时会下载包括 Hilt 在内的所有库。让我们开始使用 Hilt!

4. 在应用中使用 Hilt

与 LogApplication 类中 ServiceLocator 的实例的使用和初始化方式类似,要添加附着于应用的****生命周期的容器,我们需要为 Application 类添加 @HiltAndroidApp 注解。打开 LogApplication.kt 并为该类添加注解:

@HiltAndroidApp
class LogApplication : Application() {
    ...
}

@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类可使用依赖项注入。application 容器是应用的父级容器,这意味着其他容器可以访问它提供的依赖项。

现在,应用已经可以开始使用 Hilt 了!

5. 使用 Hilt 实现字段注入

我们将使用 Hilt 提供依赖项,而不是从类的 ServiceLocator 中按需抓取依赖项。接下来,我们开始替换对类中 ServiceLocator 的调用。

打开 ui/LogsFragment.kt 文件。LogsFragment 会在 onAttach 中填充其字段。我们可以使用 Hilt 来创建和管理 LoggerLocalDataSource 和 DateFormatter 的实例,而不是使用 ServiceLocator 手动填充这些类型的实例。

如要让 LogsFragment 使用 Hilt,我们需要为其添加 @AndroidEntryPoint 注解。

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

为 Android 类添加 @AndroidEntryPoint 注解会创建一个沿袭 Android 类生命周期的依赖项容器。

Hilt 目前支持以下 Android 类型:Application(通过使用 @HiltAndroidApp)、Activity、Fragment、View、Service 和 BroadcastReceiver。

Hilt 仅支持扩展 FragmentActivity(例如 AppCompatActivity)的 activity 和扩展 Jetpack 库 Fragment 的 fragment,不支持 Android 平台中的 Fragment(现已弃用)。

警告:Hilt 不支持保留的 fragment。

利用 @AndroidEntryPoint,Hilt 可创建附着于 LogsFragment 生命周期的依赖项容器,并能够将实例注入 LogsFragment。如何让 Hilt 进行字段注入?

对于要进行注入的字段(例如 logger 和 dateFormatter),我们可以利用 @Inject 注解****让 Hilt 注入不同类型的实例:

@AndroidEntryPoint
class LogsFragment : Fragment() {

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

    ...
}

这就是所谓的字段注入。

如要执行字段注入,请对您希望由 Hilt 提供(或注入)的 Android 类字段使用 @Inject 注解。

警告:由 Hilt 注入的字段不能是私有字段。

由于 Hilt 将负责填充这些字段,因此我们不再需要 populateFields 方法。让我们从类中移除该方法:

@AndroidEntryPoint
class LogsFragment : Fragment() {

    // Remove following code from LogsFragment

    override fun onAttach(context: Context) {
        super.onAttach(context)

        populateFields(context)
    }

    private fun populateFields(context: Context) {
        logger = (context.applicationContext as LogApplication).serviceLocator.loggerLocalDataSource
        dateFormatter =
            (context.applicationContext as LogApplication).serviceLocator.provideDateFormatter()
    }

    ...
}

在后台,Hilt 将使用自动生成的 LogsFragment 依赖项容器中内置的实例在 onAttach() 生命周期方法中填充这些字段。

要详细了解 Android 类注入到了哪些生命周期回调,请参阅文档的“组件生命周期”部分。

要执行字段注入,Hilt 需要知道如何提供这些依赖项的实例!在本例中,Hilt 需要知道如何提供 LoggerLocalDataSource 和 DateFormatter 的实例。但是,Hilt 还不知道如何提供这些实例。

告知 Hilt 如何通过 @Inject 提供依赖项

打开 ServiceLocator.kt 文件,以了解 ServiceLocator 是如何实现的。您可以看看为什么调用 provideDateFormatter() 总是返回 DateFormatter 的不同实例。

这与我们希望通过 Hilt 实现的行为完全相同。幸运的是,DateFormatter 不依赖其他类,因此目前我们无需考虑传递依赖项。

如要告知 Hilt 如何提供类型的实例,请向要注入的类的构造函数添加 @Inject 注解。

打开 util/DateFormatter.kt 文件并为 DateFormatter 的构造函数添加 @Inject 注解。请注意,要在 Kotlin 中为构造函数添加注解,您还需要 constructor 关键字:

class DateFormatter @Inject constructor() { ... }

通过它,Hilt 便会知道如何提供 DateFormatter 的实例。需要针对 LoggerLocalDataSource 执行相同的操作。打开 data/LoggerLocalDataSource.kt 文件并为其构造函数添加 @Inject 注解:

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

Hilt 拥有的有关如何提供不同类型的实例的信息也称为绑定。

目前,Hilt 有两个绑定:如何提供 1) DateFormatter 和 2) LoggerLocalDataSource 的实例。

如果再次打开 ServiceLocator 类,可以看到有一个公开的 LoggerLocalDataSource 字段。这意味着,无论何时调用 ServiceLocator,它始终返回 LoggerLocalDataSource 的相同实例。这就是所谓的“将实例的作用域限定为容器”。在 Hilt 中如何实现这一点?

6. 将实例的作用域限定为容器

我们可以使用注解将实例的作用域限定为容器。由于 Hilt 可以生成具有不同生命周期的不同容器,因此有不同的注解可用于将作用域限定为这些容器。

将实例的作用域限定为 application 容器的注解是 @Singleton。该注解将使 application 容器始终提供相同的实例,无论相应类型是否用作其他类型的依赖项,也无论它是否需要字段注入。

可以将同一逻辑应用到附着于 Android 类的所有容器。您可以在该文档中找到所有限定作用域的注解列表。例如,如果您希望 activity 容器始终提供某类型的相同实例,则可以为该类型添加 @ActivityScoped 注解。

如上所述,由于我们希望 application 容器始终提供 LoggerLocalDataSource 的相同实例,因此为其类添加 @Singleton 注解:

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

如果绑定在层次结构的较高层级中可用,那么在较低层级的容器中也可用。因此,如果 LoggerLocalDataSource 的某个实例在 application 容器中可用,那么它在 activity 和 fragment 容器中也可用。

现在,Hilt 知道如何提供 LoggerLocalDataSource 实例了。但是,这一次,该类型具有传递依赖项!如要提供 LoggerLocalDataSource 实例,Hilt 还需要知道如何提供 LogDao 实例。

不过,由于 LogDao 是一个接口,而接口没有构造函数,因此我们无法为其构造函数添加 @Inject 注解。该怎么告知 Hilt 如何提供该类型的实例?

7. Hilt 模块

模块用于向 Hilt 添加绑定,换句话说,用于告知 Hilt 如何提供不同类型的实例。在 Hilt 模块中,您可以为无法通过构造函数注入的类型(例如接口或未包含在您项目中的类)添加绑定。这种类型的一个示例是 OkHttpClient,您需要使用其构建器来创建实例。

Hilt 模块是带有**@Module** 和 @InstallIn 注解的类。@Module 会告知 Hilt 这是一个模块,而 @InstallIn 会通过指定 Hilt 组件告知 Hilt 绑定在哪些容器中可用。您可以将 Hilt 组件视为容器,如需查看组件的完整列表,请点击此处。

对于每个可被 Hilt 注入的 Android 类,都有一个关联的 Hilt 组件。例如,Application 容器与 SingletonComponent 相关联,而 Fragment 容器与 FragmentComponent 相关联。

创建模块

我们来创建一个可添加绑定的 Hilt 模块。在 hilt 文件包下创建一个名为 di 的新文件包,并在该文件包中创建一个名为 DatabaseModule.kt 的新文件。

由于 LoggerLocalDataSource 的作用域限定为 application 容器,因此 LogDao 绑定需要在 application 容器中可用。我们通过传递与其相关联的 Hilt 组件的类(即 SingletonComponent:class),使用 @InstallIn 注解指定该要求:

package com.example.android.hilt.di

@InstallIn(ApplicationComponent::class)
@Module
object DatabaseModule {

}

在 ServiceLocator 类实现中,LogDao 的实例通过调用 logsDatabase.logDao() 来获取。因此,为了提供 LogDao 的实例,我们在 AppDatabase 类上创建了一个传递依赖项。

在 Kotlin 中,仅包含 @Provides 函数的模块可以是 object 类。通过这种方式,提供程序会得到优化,并几乎内嵌到生成的代码中。

使用 @Provides 提供实例

我们可以在 Hilt 模块中为函数添加 @Provides 注解,告知 Hilt 如何提供无法通过构造函数注入的类型。

每当 Hilt 需要提供相应类型的实例时,都会执行带有 @Provides 注解的函数的函数主体。带有 @Provides 注解的函数的返回值类型会告知 Hilt 绑定的类型,即如何提供该类型的实例。函数参数是该类型的依赖项。

在本例中,我们将此函数包含在 DatabaseModule 类中:

@Module
object DatabaseModule {

    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
        return database.logDao()
    }
}

上述代码告知 Hilt 当提供 LogDao 的实例时需要执行 database.logDao()。由于我们将 AppDatabase 作为传递依赖项,因此还需要告知 Hilt 如何提供该类型的实例。

AppDatabase 类由 Room 生成,我们的项目也不拥有该类,因此我们也能够以类似于在 ServiceLocator 类中构建数据库实例的方式,使用 @Provides 函数提供它。

@Module
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
        return Room.databaseBuilder(
            appContext,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }

    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
        return database.logDao()
    }
}

由于我们希望 Hilt 始终提供相同的数据库实例,因此为 @Provides provideDatabase 方法添加了 @Singleton 注解。

每个 Hilt 容器都随附一组默认绑定,可作为依赖项注入到您的自定义绑定。applicationContext 便是这样:要访问它,您需要为字段添加 @ApplicationContext 注解。

如需查看预定义的绑定列表,请参阅文档的这一页面。

运行应用

现在,Hilt 具有在 LogsFragment 中注入实例所需的全部信息。但是,在运行应用之前,Hilt 需要了解托管 Fragment 的 Activity 才能正常运作。我们需要为 MainActivity 添加 @AndroidEntryPoint 注解。

打开 ui/MainActivity.kt 文件并为 MainActivity 添加 @AndroidEntryPoint 注解:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() { ... }

现在,您可以运行应用并检查一切是否可以像以前一样正常工作。

我们来继续重构应用,以从 MainActivity 中移除 ServiceLocator 调用。

8. 使用 @Binds 提供接口

MainActivity 从调用 provideNavigator(activity: FragmentActivity) 函数的 ServiceLocator 中获取 AppNavigator 的实例。

由于 AppNavigator 是一个接口,因此我们无法使用构造函数注入。要告知 Hilt 为接口使用哪种实现,您可以对 Hilt 模块内的函数使用 @Binds 注解。

必须为抽象函数添加 @Binds 注解(它是抽象的,因此不包含任何代码,且该类也需要是抽象的)。抽象函数的返回值类型是我们要提供实现的接口(即 AppNavigator)。实现通过添加具有接口实现类型(即 AppNavigatorImpl)的唯一参数指定。

我们可以向之前创建的 DatabaseModule 类中添加信息吗,或者我们需要新模块吗?应创建新模块的原因有多种:

  • 为便于组织,模块的名称应体现它提供的信息类型。例如,在名为 DatabaseModule 的模块中添加导航绑定是没有意义的。
  • DatabaseModule 模块安装在 SingletonComponent 中,因此绑定在 application 容器中可用。新导航信息(即 AppNavigator)需要特定于 Activity 的信息(因为 AppNavigatorImpl 将 Activity 作为依赖项)。因此,它必须安装在 Activity 容器中而不是 Application 容器中,因为关于 Activity 的信息位于前者之中。
  • Hilt 模块不能同时包含非静态和抽象绑定方法,因此您不能将 @Binds 和 @Provides 注解放在同一个类中。

在 di 文件夹中创建一个名为 NavigationModule.kt 的新文件。在该文件中,我们来创建一个名为 NavigationModule 的新抽象类,并为其添加 @Module 和 @InstallIn(ActivityComponent::class) 注解,如上所述:

@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}

在该模块内,我们可以为 AppNavigator 添加绑定。一个抽象函数会返回我们告知 Hilt 的接口(即 AppNavigator),而函数参数是该接口的实现(即 AppNavigatorImpl)。

现在,我们必须告知 Hilt 如何提供 AppNavigatorImpl 的实例。由于该类可以进行构造函数注入,因此我们只需为其构造函数添加 @Inject 注解。

打开 navigator/AppNavigatorImpl.kt 文件,然后执行该操作:

class AppNavigatorImpl @Inject constructor(
    private val activity: FragmentActivity
) : AppNavigator {
    ...
}

AppNavigatorImpl 依赖于 FragmentActivity。由于 AppNavigator 实例在 Activity 容器(因为 NavigationModule 安装在 ActivityComponent 中,所以该实例也在 Fragment 容器和 View 中可用)中提供,FragmentActivity 以预定义绑定的形式提供,因此它已经可用。

在 Activity 中使用 Hilt

现在,Hilt 具有注入 AppNavigator 实例所需的全部信息。打开 MainActivity.kt 文件并执行以下操作:

  1. 为 navigator 字段添加 @Inject 注解,以供 Hilt 获取;
  2. 移除 private 可见性修饰符;并
  3. 移除 onCreate 函数中的 navigator 初始化代码。

新代码应如下所示:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var navigator: AppNavigator

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (savedInstanceState == null) {
            navigator.navigateTo(Screens.BUTTONS)
        }
    }

    ...
}

运行应用

您可以运行该应用,看看它是否按预期工作。

完成重构

目前唯一仍在使用 ServiceLocator 提取依赖项的类是 ButtonsFragment。由于 Hilt 已经知道如何提供 ButtonsFragment 需要的所有类型,因此我们只需在该类中执行字段注入。

正如我们之前所了解的,要让 Hilt 对该类进行字段注入,我们需要执行以下操作:

  1. 为 ButtonsFragment 添加 @AndroidEntryPoint 注解;
  2. 从 logger 和 navigator 字段中移除私有修饰符,并为其添加 @Inject 注解;
  3. 移除字段初始化代码(即 onAttach 和 populateFields 方法)。

ButtonsFragment 的代码如下:

@AndroidEntryPoint
class ButtonsFragment : Fragment() {

    @Inject lateinit var logger: LoggerLocalDataSource
    @Inject lateinit var navigator: AppNavigator

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
    }
}

请注意,LoggerLocalDataSource 的实例与我们在 LogsFragment 中所用的实例相同,因为该类型的作用域限定为 application 容器。但是,AppNavigator 的实例与 MainActivity 中的实例不同,因为我们尚未将其作用域限定为相应的 Activity 容器。

现在,ServiceLocator 类不再提供依赖项,因此我们可以将其从项目中彻底移除。只有 LogApplication 类使用了它,我们在此类中保留了它的一个实例。由于不再需要该类,下面我们来将其清除。

打开 LogApplication 类并移除使用的 ServiceLocator。Application 类的新代码如下:

@HiltAndroidApp
class LogApplication : Application()

现在,您可以随时从项目中彻底移除 ServiceLocator 类。由于 ServiceLocator 仍在测试中使用,请也从 AppTest 类中将其移除。

已介绍基本内容

您刚刚所学的知识应该足以让您在 Android 应用中将 Hilt 用作依赖项注入工具。

从现在开始,我们将向应用添加一些新功能,以便您了解如何在不同情形中使用更高级的 Hilt 功能。

9. 限定符

现在,我们已从项目中移除 ServiceLocator 类,并且您学习了 Hilt 的基础知识,下面我们来向应用中添加新功能,以探索其他 Hilt 功能。

在本部分中,您将学习以下内容:

  • 如何将作用域限定为 Activity 容器。
  • 什么是限定符、限定符解决什么问题以及如何使用。

为展示这一部分内容,我们需要应用有不同的行为。我们会将日志存储位置从数据库切换为内存中列表,以仅记录应用会话期间的日志。

LoggerDataSource 接口

让我们开始将数据源抽象化到一个接口中。在 data 文件夹下创建一个名为 LoggerDataSource.kt 的新文件,并在其中包含以下内容:

package com.example.android.hilt.data

// Common interface for Logger data sources.
interface LoggerDataSource {
    fun addLog(msg: String)
    fun getAllLogs(callback: (List<Log>) -> Unit)
    fun removeLogs()
}}

ButtonsFragment 和 LogsFragment 这两个 Fragment 都使用 LoggerLocalDataSource。我们需要重构它们,使其改为使用 LoggerDataSource 的实例。

打开 LogsFragment 并将 logger 变量的类型设置为 LoggerDataSource:

@AndroidEntryPoint
class LogsFragment : Fragment() {

    @Inject lateinit var logger: LoggerDataSource
    ...
}

在 ButtonsFragment 中执行相同的操作:

@AndroidEntryPoint
class ButtonsFragment : Fragment() {

    @Inject lateinit var logger: LoggerDataSource
    ...
}

接下来,让我们让 LoggerLocalDataSource 实现此接口。打开 data/LoggerLocalDataSource.kt 文件并:

  1. 使其实现 LoggerDataSource 接口,以及
  2. 使用 override 标记其方法
@Singleton
class LoggerLocalDataSource @Inject constructor(
    private val logDao: LogDao
) : LoggerDataSource {
    ...
    override fun addLog(msg: String) { ... }
    override fun getAllLogs(callback: (List<Log>) -> Unit) { ... }
    override fun removeLogs() { ... }
} 

现在,我们创建 LoggerDataSource 的另一个实现,名为 LoggerInMemoryDataSource,它会将日志保存到内存中。在 data 文件夹下创建一个名为 LoggerInMemoryDataSource.kt 的新文件,并在其中包含以下内容:

package com.example.android.hilt.data

import java.util.LinkedList

class LoggerInMemoryDataSource : LoggerDataSource {

    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()
    }
}

将作用域限定为 Activity 容器

为了能够将 LoggerInMemoryDataSource 用作实现细节,我们需要告知 Hilt 如何提供此类型的实例。和以前一样,我们为类构造函数添加 @Inject 注解:

class LoggerInMemoryDataSource @Inject constructor(
) : LoggerDataSource { ... }

由于我们的应用仅包含一个 Activity(也称为“单 Activity”应用),因此我们应该在 Activity 容器中有一个 LoggerInMemoryDataSource 的实例,并在 Fragment 中重复使用该实例。

我们可以通过将 LoggerInMemoryDataSource 的作用域限定为 Activity 容器来实现内存中记录行为:创建的每个 Activity 都有自己的容器及不同的实例。在每个容器中,当 logger 需要作为依赖项或用于字段注入时,系统将提供 LoggerInMemoryDataSource 的相同实例。此外,在组件层次结构下的容器中,也将提供相同的实例。

根据“将作用域限定为组件”文档,要将某个类型的作用域限定为 Activity 容器,我们需要为该类型添加 @ActivityScoped 注解:

@ActivityScoped
class LoggerInMemoryDataSource @Inject constructor(
) : LoggerDataSource { ... }

目前,Hilt 知道如何提供 LoggerInMemoryDataSource 和 LoggerLocalDataSource 的实例,但 LoggerDataSource 呢?Hilt 不知道请求 LoggerDataSource 时应使用哪种实现。

如前面部分所述,我们可以在模块中使用 @Binds 注解来告知 Hilt 应使用哪种实现。但是,**如果我们需要在同一项目中提供两种实现,该怎么办?**例如,在应用运行时使用 LoggerInMemoryDataSource并在 Service 中使用 LoggerLocalDataSource。

同一接口的两种实现

让我们在 di 文件夹中创建一个名为 LoggingModule.kt 的新文件。由于 LoggerDataSource 的不同实现的作用域限定为不同的容器,因此我们不能使用同一个模块:LoggerInMemoryDataSource 的作用域限定为 Activity 容器,而 LoggerLocalDataSource 的作用域限定为 Application 容器。

幸运的是,我们可以在刚才创建的同一文件中为两个模块定义绑定:

package com.example.android.hilt.di

@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

如果类型限定了作用域,@Binds** 方法必须具有限定作用域的注解**,因此,上面的函数带有 @Singleton 和 @ActivityScoped 注解。如果 @Binds 或 @Provides 用作某个类型的绑定,则该类型中限定作用域的注解将不会再使用,因此您可以开始将其从其他实现类中移除。

如果您现在尝试构建项目,将看到 DuplicateBindings 错误!

error: [Dagger/DuplicateBindings] com.example.android.hilt.data.LoggerDataSource is bound multiple times

这是因为 LoggerDataSource 类型注入到了 Fragment 中,但**同一类型有两个绑定,Hilt 不知道应使用哪种实现!**如何告知 Hilt 应使用哪种实现呢?

使用限定符

如要告知 Hilt 如何提供同一类型的不同实现(多个绑定),您可以使用限定符。

限定符是用于标识绑定的注解。

由于每个限定符将用于标识一个绑定,我们需要为每种实现定义一个限定符。在 Android 类中注入该类型或将该类型作为其他类的依赖项时,需要使用限定符注解以避免歧义。

由于限定符只是注解,因此我们可以在添加模块的 LoggingModule.kt 文件中定义它们:

package com.example.android.hilt.di

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

现在,我们必须使用限定符为提供每种实现的 @Binds(或 @Provides,如果我们需要它)函数添加注解。请查看完整的代码,并注意 @Binds 方法中使用的限定符:

package com.example.android.hilt.di

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @DatabaseLogger
    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @InMemoryLogger
    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

此外,在注入时,必须将这些限定符用于要注入的实现。在本例中,我们将在 Fragment 中使用 LoggerInMemoryDataSource 实现。

重要提示:@DatabaseLogger 限定符安装在 SingletonComponent 中,可注入到 LogApplication 类中。但是,由于 @InMemoryLogger 安装在 ActivityComponent 中,它无法注入到 LogApplication 类中,原因是 application 容器不知道该绑定。

打开 LogsFragment 并对 logger 字段使用 @InMemoryLogger 限定符,已告知 Hilt 注入 LoggerInMemoryDataSource 的实例:

@AndroidEntryPoint
class LogsFragment : Fragment() {

    @InMemoryLogger
    @Inject lateinit var logger: LoggerDataSource
    ...
}

对 ButtonsFragment 执行相同的操作:

@AndroidEntryPoint
class ButtonsFragment : Fragment() {

    @InMemoryLogger
    @Inject lateinit var logger: LoggerDataSource
    ...
}

如果您希望更改要使用的数据库实现,只需为注入的字段添加 @DatabaseLogger 而不是 @InMemoryLogger 注解。

运行应用

我们可以运行应用,通过与按钮互动并观察“See all logs”屏幕上是否显示相应的日志,确认所执行的操作是否有效。

请注意,系统不会再将日志保存到数据库。日志不会在不同会话之间保留,只要您关闭并再次打开应用,日志屏幕就是空的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10. 界面测试

现在,该应用已完全迁移到 Hilt,我们还可以迁移项目中的插桩测试。该测试用于检查应用的功能,位于 app/androidTest 文件夹下的 AppTest.kt 文件中。打开它!

您会发现,该测试无法编译,这是因为我们从项目中移除了 ServiceLocator 类。通过从类中移除 @After tearDown 方法,移除对不再使用的 ServiceLocator 的引用。

androitTest 测试在模拟器上运行。happyPath 测试会确认对“Button 1”的点按已记录到数据库中。由于应用使用的是内存中数据库,因此测试结束后,所有日志都会消失。

使用 Hilt 进行界面测试

Hilt 会在您的界面测试中注入依赖项,就像在您的生产代码中一样。

使用 Hilt 进行测试不需要维护,因为 Hilt 会自动为每个测试生成一组新的组件。

添加测试依赖项

Hilt 使用另外一个包含测试专用注解的库,该库名为 hilt-android-testing,可让您更轻松地测试代码。此外,由于 Hilt 需要在 androidTest 文件夹中为各个类生成代码,因此其注解处理器也必须能够在其中运行。要实现这一点,您需要在 app/build.gradle 文件中添加两个依赖项。

如要添加这些依赖项,请打开 app/build.gradle 并将以下配置添加到 dependencies 部分的底部:

dependencies {

    // Hilt testing dependency
    androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
    // Make Hilt generate code in the androidTest folder
    kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
}

自定义 TestRunner

使用 Hilt 的插桩测试需要在支持 Hilt 的 Application 中执行。该库中已随附 HiltTestApplication,可用于运行界面测试。通过在项目中创建新的测试运行程序,可指定要在测试中使用的 Application。

在同一级别,AppTest.kt 文件位于 androidTest 文件夹下,创建一个名为 CustomTestRunner 的新文件。CustomTestRunner 从 AndroidJUnitRunner 扩展而来,并按如下方式实现:

class CustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

接下来,我们需要指示项目在插桩测试中使用该测试运行程序。这在 app/build.gradle 文件的 testInstrumentationRunner 属性中指定。打开该文件,并用以下内容替换默认的 testInstrumentationRunner 内容:

...
android {
    ...
    defaultConfig {
        ...
        testInstrumentationRunner "com.example.android.hilt.CustomTestRunner"
    }
    ...
}
...

现在,我们可以开始在界面测试中使用 Hilt 了!

运行使用 Hilt 的测试

接下来,模拟器测试类要使用 Hilt,它需要满足以下条件:

  1. 带有 @HiltAndroidTest 注解,该注解负责为每个测试生成 Hilt 组件
  2. 使用 HiltAndroidRule,该规则可用于管理组件的状态,并对测试执行注入。

我们来将其添加到 AppTest 中:

@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
class AppTest {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    ...
}

现在,如果您使用类定义或测试方法定义旁边的播放按钮运行测试,模拟器将会启动。如果您已配置模拟器,测试将通过。

要详细了解测试和功能,例如字段注入或如何在测试中替换绑定,请参阅此文档。

11. @EntryPoint 注释

在本 Codelab 的这一部分,我们将了解如何使用 @EntryPoint 注解,该注解用于在 Hilt 不支持的类中注入依赖项。

正如我们之前看到的那样,Hilt 随附对最常见的 Android 组件的支持。但是,您可能需要在 Hilt 不直接支持或无法使用 Hilt 的类中执行字段注入。

在此类情况下,您可以使用 @EntryPoint。入口点是一个边界位置,在该位置,您可以从无法使用 Hilt 注入依赖项的代码中获取由 Hilt 提供的对象。在入口点,代码首次进入到由 Hilt 管理的容器中。

用例

我们希望能够在应用进程之外导出日志。为此,需要使用 ContentProvider。我们仅允许使用方使用 ContentProvider 查询应用中的某个特定日志(提供一个 id)或所有日志。我们将使用 Room 数据库检索数据。因此,LogDao 类应提供使用数据库 Cursor 返回所需信息的方法。打开 LogDao.kt 文件并向接口添加以下方法。

@Dao
interface LogDao {
    ...

    @Query("SELECT * FROM logs ORDER BY id DESC")
    fun selectAllLogsCursor(): Cursor

    @Query("SELECT * FROM logs WHERE id = :id")
    fun selectLogById(id: Long): Cursor?
}

接下来,我们必须创建一个新的 ContentProvider 类,并替换 query 方法,以返回包含日志的 Cursor。在新的 contentprovider 目录下创建一个名为 LogsContentProvider.kt 的新文件,并在其中包含以下内容:

package com.example.android.hilt.contentprovider

import android.content.ContentProvider
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
import com.example.android.hilt.data.LogDao
import dagger.hilt.EntryPoint
import dagger.hilt.EntryPoints
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import java.lang.UnsupportedOperationException

/** The authority of this content provider.  */
private const val LOGS_TABLE = "logs"

/** The authority of this content provider.  */
private const val AUTHORITY = "com.example.android.hilt.provider"

/** The match code for some items in the Logs table.  */
private const val CODE_LOGS_DIR = 1

/** The match code for an item in the Logs table.  */
private const val CODE_LOGS_ITEM = 2

/**
 * A ContentProvider that exposes the logs outside the application process.
 */
class LogsContentProvider: ContentProvider() {

    private val matcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI(AUTHORITY, LOGS_TABLE, CODE_LOGS_DIR)
        addURI(AUTHORITY, "$LOGS_TABLE/*", CODE_LOGS_ITEM)
    }

    override fun onCreate(): Boolean {
        return true
    }

    /**
     * Queries all the logs or an individual log from the logs database.
     *
     * For the sake of this codelab, the logic has been simplified.
     */
    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        val code: Int = matcher.match(uri)
        return if (code == CODE_LOGS_DIR || code == CODE_LOGS_ITEM) {
            val appContext = context?.applicationContext ?: throw IllegalStateException()
            val logDao: LogDao = getLogDao(appContext)

            val cursor: Cursor? = if (code == CODE_LOGS_DIR) {
                logDao.selectAllLogsCursor()
            } else {
                logDao.selectLogById(ContentUris.parseId(uri))
            }
            cursor?.setNotificationUri(appContext.contentResolver, uri)
            cursor
        } else {
            throw IllegalArgumentException("Unknown URI: $uri")
        }
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        throw UnsupportedOperationException("Only reading operations are allowed")
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        throw UnsupportedOperationException("Only reading operations are allowed")
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        throw UnsupportedOperationException("Only reading operations are allowed")
    }

    override fun getType(uri: Uri): String? {
        throw UnsupportedOperationException("Only reading operations are allowed")
    }
}

您会发现,getLogDao(appContext) 调用无法编译!我们需要通过从 Hilt application 容器获取 LogDao 依赖项来实现它。但是,Hilt 不提供对注入到 ContentProvider 的开箱支持;对于 Activity,则提供该支持,例如使用 @AndroidEntryPoint。

我们需要创建一个带有 @EntryPoint 注解的新接口才能访问它。

@EntryPoint 的实际应用

入口点是一个接口,对于我们所需的每个绑定(包括其限定符),都具有访问器方法。此外,该接口还必须带有 @InstallIn 注解,以指定要安装入口点的组件。

最佳做法是在使用入口点接口的类中添加新的接口。因此,我们将该接口添加到 LogsContentProvider.kt 文件中:

class LogsContentProvider: ContentProvider() {

    @InstallIn(ApplicationComponent::class)
    @EntryPoint
    interface LogsContentProviderEntryPoint {
        fun logDao(): LogDao
    }

    ...
}

请注意,该接口带有 @EntryPoint 注解,并且安装在 SingletonComponent 中,因为我们需要来自 Application 容器的实例的依赖项。在该接口中,我们会提供要访问的绑定的方法,在本例中为 LogDao。

如需访问入口点,请使用来自 EntryPointAccessors 的适当静态方法。参数应该是组件实例或充当组件持有者的 @AndroidEntryPoint 对象。确保您以参数形式传递的组件和 EntryPointAccessors 静态方法都与 @EntryPoint 接口上的 @InstallIn 注解中的 Android 类匹配:

现在,我们可以实现上面的代码中缺少的 getLogDao 方法。让我们在 LogsContentProviderEntryPoint 类中使用在上面定义的入口点接口:

class LogsContentProvider: ContentProvider() {
    ...

    private fun getLogDao(appContext: Context): LogDao {
        val hiltEntryPoint = EntryPointAccessors.fromApplication(
            appContext,
            LogsContentProviderEntryPoint::class.java
        )
        return hiltEntryPoint.logDao()
    }
}

请注意我们如何将 applicationContext 传递给静态 EntryPoints.get 方法和带有 @EntryPoint 注解的接口的类。

12. 恭喜!

现在,您已经熟悉 Hilt,应该能够将其添加到您的 Android 应用。在本 Codelab 中,您学习了以下内容:

  • 如何使用 @HiltAndroidApp 在 Application 类中设置 Hilt。
  • 如何使用 @AndroidEntryPoint 向不同的 Android 生命周期组件添加依赖项容器。
  • 如何使用模块告知 Hilt 如何提供特定类型。
  • 如何使用限定符为某些类型提供多个绑定。
  • 如何使用 Hilt 测试应用。
  • @EntryPoint 何时实用以及如何使用它。
Android开发中Kapt编译错误排查指南:从原理到实战解决KaptWithoutKotlincTask 在Android应用开发中,构建工具是连接源代码与可执行应用的关键桥梁。Gradle作为主流的构建系统,通过插件机制支持Kotlin等现代语言。其中,Kapt(Kotlin注解处理工具)扮演着重要角色,它通过生成Java存根文件,使Kotlin代码能够兼容传统的Java注解处理器,从而实现对Room、Dagger/Hilt等流行库的支持。这项技术的核心价值在于打通了Kotlin与庞大Java生态的互操作性,让开发者能在享受Kotlin语言优势的同时,复用成熟的Java库。然而,在实际工程实践中,Kapt执行 阅读详情

相关推荐

安卓APP开发工程师进阶指南:从性能优化到面试实战全解析

在移动互联网技术迭代加速的今天,安卓开发早已从单纯的界面编写进化为涉及系统底层、性能调优与工程化落地的复合型工程领域。理解Kotlin协程的挂起原理、掌握Jetpack组件化架构、熟练运用Perfetto等工具定位卡顿与内存泄漏,是区分普通码农与高阶工程师的关键。这些基础能力不仅决定了APP的启动速度、滑动流畅度与稳定性,更直接关系到复杂业务场景下的故障排查效率与包体治理成效。从应用层开发到系统定制,从逆向安全到跨平台方案,安卓技术栈的深度与广度正在重塑行业人才标准。本文站在面试官视角,系统梳理岗位核心技能

weixin_34184561的博客 393

Android Hilt教程_基本使用

基于 Dagger 开发,能够大大简化依赖注入的使用。在 build.gradle (Project)Hilt 是 Android 官方推荐的。让 Hilt 进行全局依赖注入的初始化。:比 Dagger 需要更少的模板代码。在 app/build.gradle。(例如:构造函数需要参数),需要在。:推荐用于 Android 开发。中使用 Hilt 进行依赖注入。的实例,并在需要的地方注入。:通过注解即可完成依赖注入。按照本教程,已经学会如何在。:减少手动管理实例的工作。Hilt 会自动创建。

琦琦的博客 1684

Android驾校一点通App开发:基于Kotlin+Room+MVVM的离线题库与模拟考试实现

移动端考试类应用常面临弱网、进程被杀等复杂场景,如何保证核心功能稳定可靠成为架构设计的关键。离线数据存储是解决此类问题的基石,通过本地数据库将题库与用户作答记录持久化,能在无网络环境下提供流畅体验。Kotlin作为现代Android开发语言,配合Room这一SQLite抽象层,可高效管理数据实体与事务操作;MVVM架构则清晰分离界面与业务逻辑,提升代码可维护性。以驾校一点通为实战案例,从题库导入、顺序刷题到模拟考试倒计时恢复,展示了如何利用Room的索引与事务机制、进程重建时的状态恢复策略,以及针对5万级数

weixin_33491377的博客 119

Android Hilt 入门教程_实战

Android Hilt 依赖注入框架入门教程 Hilt 是 Android 官方推荐的依赖注入框架,基于 Dagger 但更简单易用。本文介绍 Hilt 的基本使用方法和优势,帮助开发者快速上手。 主要特点: 自动管理依赖对象创建 无缝集成 ViewModel 提供作用域管理(Singleton、ActivityScoped等) 简化 Dagger 的复杂配置 核心使用步骤: 添加 Hilt 依赖和注解处理器 使用 @HiltAndroidApp 初始化应用 在 Activity/Fragment 使用

琦琦的博客 1012

通过声音识别鸟类

根据野生鸟类的外表来识别它是一回事。仅仅根据声音来识别同一只鸟是另一回事。除非你是一个熟练的观鸟者,通过声音识别鸟类可能相当具有挑战性德国马尔堡大学的一组数学、计算机科学和生物学研究人员设计了一种快速识别鸟类和监测当地生物多样性的方法。他们使用连接到 NVIDIA 的便携式设备拍摄的录音根据研究人员的说法是一个边缘人工智能系统,“基于分布式系统中运行的嵌入式边缘设备,能够对森林中记录的声景进行高效、连续的评估。”要了解更多信息,请参阅。

分享AI前沿科技 1680

使用 MediaPipe 身体跟踪构建不良身体姿势检测和警报系统

详细介绍如何使用 MediaPipe 身体跟踪构建不良身体姿势检测和警报系统,并附实现的源码下载链接

DT程序员的博客 1815

Android Hilt 入门教程_Hilt原理

Hilt是基于Dagger的依赖注入框架,通过编译时代码生成自动管理对象的创建、注入和生命周期。其核心原理包括:1)通过@Inject和@Module/@Providers定义依赖关系图;2)利用作用域注解(@Singleton等)将对象与Android组件生命周期绑定;3)支持测试环境下依赖替换;4)实现代码解耦,使业务逻辑仅依赖抽象接口。Hilt通过分层组件结构(SingletonComponent、ActivityComponent等)实现作用域管理,让依赖项随组件生命周期自动释放,显著提高代码可维护

琦琦的博客 1213

宇树 Qmini 双足机器人完全开源:3D打印+5小时组装,新手也能玩转!

教育教学,成为学校里的竞赛竞技,实验课程,论文设计的本体;开发者可以根据开源资料轻松集齐所有零部件,结构件可全部通过3D打印完成(几乎完全不需要任何机加工零部件),搭配宇树的高可靠性电机和标准电池,客户打印完零部件后,大概仅需要3~5小时的手工组装即可得到一只可爱的Qmini小双足机器人本体。一起推动机器人的未来发展,创新没有边界,创造力是无限的,Qmini就是你的第一步,如果你也想做一个能动、能学、还能萌的小双足机器人,现在就可以动手了,全开源、真低门槛、只差你的3D打印机和一点点耐心。

雕爷学编程 1890

Hilt 介绍 | MAD Skills

本文是 MAD Skills 系列 中有关 Hilt 的第一篇文章!在本文中,我们将探讨依赖项注入 (DI) 对应用的重要性,以及 Jetpack 推荐的 Android DI 解决方案——Hilt。 如果您更喜欢通过视频了解此内容,可以 点击这里 查看。 在 Android 应用中,您可以通过遵循依赖项注入的原则,为良好的应用架构奠定基础。这有助于重用代码、易于重构、易于测试!更多关于 DI 的好处,请参阅: Android 中的依赖项注入。 在项目中创建类的实例时,您可以通过提供及传递所需依赖项,手动处

Android开发者 709

Nvidia Jetson平台 JP4.2.1版本GStreamer视频采集异常问题调试记录

Nvidia Jetson平台GStreamer in JP4.2.1不能采集但是 v4l2-ctl和 ffmpeg能正常采集视频。

jzwjzw19900922的博客 2127

Ultralytics 框架中 model.predict() 返回的 results 对象的详细解析

通过掌握 results 对象的使用方法,您可以快速实现模型部署、数据分析与可视化。

浩瀚之水的专栏 749

Android Hilt:依赖注入解耦与 Hilt MVVM 解耦思路

Android Hilt:依赖注入解耦与 Hilt MVVM 解耦思路、@HiltAndroidApp、@AndroidEntryPoint、@Inject、@Binds、@Provides、Qualifier、@InstallIn、@HiltViewModel

OuO-1 的博客 416

基于巴法云物联网云平台构建可视化控制网页(以控制LED为例)

本文基于巴法云物联网云平台构建可视化控制网页,实现了对LED的控制。

kevin1499的博客 4521

深入解析AutoService:Google轻量级组件化通信框架的核心原理与实践

本文深入解析了Google AutoService框架的核心原理与实践。作为一款轻量级组件化通信方案,它基于编译时注解处理器自动生成Java SPI配置文件,实现模块间解耦与动态服务发现。文章通过实际案例详细阐述了其工作原理、ServiceLoader的使用技巧,并提供了构建完整组件化框架的实战指南,帮助开发者高效实施Android组件化架构。

qqq44的博客 907

Cadence OrCAD Capture 原理图中创建差分对约束的两种常用方法

本文介绍在原理图设计阶段,添加差分对的方法。

fydar的博客 3932

基于STM32的浴室环境智能控制系统设计

摘要:本文设计了一款基于STM32F103的智能卫浴系统,通过多传感器协同检测浴室环境参数。系统采用DS18B20检测环境与卫浴温度,DHT11监测湿度,MQ-135检测空气质量,光敏电阻测量光照强度,D203S人体热释电模块检测人员存在。当检测到异常参数时,系统自动启动相应调节功能:温度异常时控制加热/制冷继电器,湿度过高时启动通风除湿,空气质量差时进行换气,光照不足且有人时自动开灯。系统支持按键、蓝牙APP和语音三种控制方式,可手动设置参数阈值、切换防雾模式、控制音乐播放等功能。实验结果表明,该系统能有

3254968421的博客 485

Pokedex-AR开发者教程:5步向AR场景添加一只新宝可梦(从Poly模型到.sfb导入)

Pokedex-AR 是基于 ARCore + Sceneform 的 AR 宝可梦示例应用,结合 Hilt、Coroutines + Flow、MVVM 架构等现代 Android 技术栈,将 3D 宝可梦模型渲染进真实环境。本教程以「新增一只宝可梦」为主线,用 5 个步骤讲清从 Poly 挑选 3D 模型、Sceneform Viewer 转换 .sfb,到最终在 AR 场景中看到它的完整流程

gitblog_00521的博客 1017

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Hilt的使用(基本用法)二

Hilt的基本用法

Mr_Tony的专栏 4165

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Hilt在Android中的使用

会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类可使用依赖项注入。application 容器是应用的父级容器,这意味着其他容器可以访问它提供的依赖项。//将标有@AndroidEntryPoint和@HiltAndroidApp的父类替换为Hilt_XXX。如要告知 Hilt 如何提供同一类型的不同实现(多个绑定),您可以使用限定符。生命周期的依赖项容器,并能够将实例注入。6.4.3通过注解选中实现方式。,Hilt 可创建附着于。6.4.2对实现添加注解。6.4.1 定义注解。

qq_32671919的博客 1868

Android Jetpack组件之Hilt使用

本篇文章的主题是Hilt。 Hilt是一个功能强大且用法简单的依赖注入框架,同时也可以说是今年Jetpack家族中最重要的一名新成员。 那么为什么说这是一篇我自己都比较怕的文章呢?因为关于依赖注入的文章太难写了。我觉得如果只是向大家讲解Hilt的用法倒还算是简单,但是如果想要让大家弄明白为什么要使用Hilt?或者再进一步,为什么要使用依赖注入?这就不是一个非常好写的话题了。 本篇文章我会尝试将以上几个问题全部讲清楚,希望我可以做得到。 为什么要使用依赖注入? 依赖注入的英文名是Dependency

Jason_Lee155的博客 5755

Jetpack新成员,一篇文章带你玩转Hilt和依赖注入

依赖注入的英文名是Dependency Injection,简称DI。事实上这并不是什么新兴的名词,而是软件工程学当中比较古老的概念了。 如果要说对于依赖注入最知名的应用,大概就是Java中的Spring框架了。Spring在刚开始其实就是一个用于处理依赖注入的框架,后来才慢慢变成了一个功能更加广泛的综合型框架。 我在学生时代学习Spring时产生了和绝大多数开发者一样的疑惑,就是为什么我们要使用依赖注入呢? 现在的我或许可以给出更好的答案了,一言以蔽之:解耦。 耦合度过高可能会是你的项目中一个比较

郭霖的专栏 3万+

Hilt-依赖注入框架上手指南

Hilt 是Google 最新的依赖注入框架,其是基于Dagger。对于Android开发者来说,Hilt可以说专门为Android 打造,提供了一种将Dagger依赖项注入到Android应用程序的标准方法,而且创建了一组标准的组件和作用域,这些组件会自动集成到Android应用程序的各个生命周期中,以简化开发者的上手难度。 在学习本文之前,假定大家已经了解依赖注入是什么,如果没有了解过,可以先了解概念。Hilt 的目的是降低Android 开发者使用依赖注入框架的上手成本,但是基本的理念大家还是要明.

petterp的博客 1万+

Hilt的使用

添加依赖配置 首先,将 hilt-android-gradle-plugin 插件添加到项目的根级 build.gradle文件中: buildscript { ... dependencies { ... classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha' } } 然后,应用 Gradle 插件并在 app/build.gradle 文件中添加以下依赖项: ...

吴唐人的博客 1477

Android依赖注入框架Hilt的使用

Hilt 是Google 最新的依赖注入框架,其基于Dagger研发。Hilt可以说是专门为Android 打造,提供了一种将Dagger依赖项注入到Android应用程序的标准方法,而且创建了一些标准的组件和作用域,这些组件会自动集成到Android应用程序的各个生命周期中,以简化开发者的上手难度。

XBlithe的博客 2399

谷歌官方文档Hilt依赖项注入kotlin示例@InstallIn参数报错

如果 AnalyticsService 是一个接口,则您无法通过构造函数注入它,而应向 Hilt 提供绑定信息,方法是在 Hilt 模块内创建一个带有 @Binds 注解的抽象函数。官方文档在kotlin示例代码@InstallIn(ActivityComponent.class)有个错误,本来也不是很熟悉kotlin,折腾了半天。@Binds 注解会告知 Hilt 在需要提供接口的实例时要使用哪种实现。函数返回类型会告知 Hilt 该函数提供哪个接口的实例。函数参数会告知 Hilt 要提供哪种实现。

bbq61898762的博客 560

Hilt 简单使用篇

引入Hilt dependencies { ... // hilt 依赖导入 implementation "com.google.dagger:hilt-android:2.28-alpha" annotationProcessor "com.google.dagger:hilt-android-compiler:2.28-alpha" ... } buildscript { ... dependencies { ...

chan_yang的博客 1970
上一篇: UML类图六种连接方式
下一篇: 理解原型和原型链
CodingTrueMan
博客等级 码龄14年 34粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值