Android Jetpack での Hilt の使用

Hilt は、プロジェクトで手動の依存関係注入を実行するために必要な定型コードを削減する、Android 用の依存関係注入ライブラリです。手動依存関係注入を実行するには、各クラスとその依存関係を手動で構築し、コンテナーを使用して依存関係を再利用および管理する必要があります。

Hilt は、プロジェクト内のすべての Android クラスにコンテナを提供し、そのライフ サイクルを自動的に管理することにより、アプリケーションで DI (依存関係注入) を使用する標準的な方法を提供します。Hilt は人気のある DI ライブラリ Dagger 上に構築されているため、Dagger のコンパイル時の正確性、実行時のパフォーマンス、スケーラビリティ、および Android Studio サポートの恩恵を受けています。この記事ではその使用方法についてのみ説明します。手順は次のとおりです。

Hilt をプロジェクトに導入します。

以下のproject/build.gradlekotlin プラグインと hilt プラグインを追加します

buildscript {
    ext.kotlin_version = '1.5.31'
    ext.hilt_version = '2.40'
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.3'
        //kotlin编译插件
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        //hilt编译插件
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

app/build.gradlekotlin に参加して以下の

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-parcelize'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    compileSdkVersion 31
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.android.hilt"
        minSdkVersion 16
        targetSdkVersion 31
        versionCode 1
        versionName "1.0"


        javaCompileOptions {
            annotationProcessorOptions {
                arguments["room.incremental"] = "true"
            }
        }
    }

    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    implementation 'androidx.recyclerview:recyclerview:1.2.1'

    // Room
    implementation "androidx.room:room-runtime:2.3.0"
    kapt "androidx.room:room-compiler:2.3.0"

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

プロジェクトで hilt を使用します。

ステップ 1:@HiltAndroidApp注釈を使用する

から継承した新しいクラスを作成しApplication、注釈を追加して@HiltAndroidAppHilt のコード生成をトリガーします (依存関係注入を使用できるアプリケーション基本クラスなど)。アプリ コンテナーはアプリの親コンテナーです。つまり、他のコンテナーはアプリ コンテナーが提供する依存関係にアクセスできます。

@HiltAndroidApp 
class LogApplication : Application()

ステップ 2: @AndroidEntryPointAndroid クラスに依存関係を挿入するために使用します。

Application クラスで Hilt を設定し、アプリケーション レベルのコンポーネントを用意すると、Hilt は @AndroidEntryPointアノテーションを使用して他の Android クラスに依存関係を提供できます。Hilt は現在、次の Android クラスをサポートしています。

  • アプリケーション (@HiltAndroidApp を使用)
  • 活動
  • 断片
  • ビュー
  • サービス
  • ブロードキャストレシーバー

@AndroidEntryPointAndroid クラスに の注釈を付ける場合は、そのクラスに依存する Android クラスにも注釈を付ける必要があります。たとえば、Fragmentフラグメントに注釈を付ける場合は、そのフラグメントを使用するすべてのアクティビティにも注釈を付ける必要があります。

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

ステップ 3: フィールド注入にヒルトを使用する

@Injectアノテーションを使用すると、Hilt はさまざまなタイプのインスタンスを挿入できます。実際、このアノテーションは変数を宣言するときに使用されます。

@AndroidEntryPoint
class LogsFragment : Fragment() {

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

    ...
}

ステップ 4: Hilt が例を提供します。

step4-条件1:コンストラクターを使用して@Injectインスタンスを取得します。

アノテーション付き変数の@Inject場合、インスタンスを提供するときに、それがコンストラクターを通じて作成されたインスタンスである場合は、コンストラクターのアノテーションを直接使用して@Inject、次の DateFormatter などのクラスのインスタンスを hilt に作成させることができます。

/**
 * 通过构造器创建依赖
 */
class DateFormatter @Inject constructor() {

    @SuppressLint("SimpleDateFormat")
    private val formatter = SimpleDateFormat("d MMM yyyy HH:mm:ss")

    fun formatDate(timestamp: Long): String {
        return formatter.format(Date(timestamp))
    }
}

別の例はStep3にありますloggerDateFormatter との違いは、その構築パラメータにパラメータがあることです。したがって、このケースでは、Hilt に LogDao のインスタンスを取得する方法を指示する必要もあります。つまり、コンストラクターを通じて LogDao を構築できる場合は、@Injectアノテーションを直接追加するだけです。ただし、ここでの logDao はインターフェイスであり、実装クラスを手動で追加することはできません (これは Android ルームの DAO です)。したがって、他の方法を使用して取得する必要があります

@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {
    ...
}
step4-condition2:@Provides例を提供します。

Hilt モジュール内の関数に注釈を付けて @Provides、コンストラクターを注入できない型を提供する方法を Hilt に指示できます。hilt モジュールは、アノテーションが付けられたクラスを使用し@Module ます@InstallIn コンストラクタ@Injectにアノテーションを追加して@Moduleand経由でインスタンスを提供する@InstallIn(スコープを指定して)オブジェクト インスタンスを提供するメソッドを宣言することはできません。これはModuleモジュールです。このモジュールを使用して Hilt にバインディングを追加する必要があります。言い換えれば、さまざまなタイプのインスタンスを提供する方法を Hilt に指示します。Hilt モジュールでは、プロジェクトに含まれていないインターフェイスやクラスなど、コンストラクターを注入できない型のバインディングを追加します。たとえば、OkHttpClient - インスタンスを作成するには、そのビルダーを使用する必要があります。実際にはデータベース操作がここで提供されるため、スコープはグローバルである必要があるため、これが使用されますSingletonComponent他のコンポーネントはここにあります

@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
//这个可以是个class,但是在 Kotlin 中,只包含 @Provides 函数的模块可以是 object 类。
//这样,提供程序即会得到优化,并几乎可以内联在生成的代码中。

    /**
     * 用 @Provides 提供实例。我们可以在 Hilt 模块中用 @Provides 注释函数,
     * 以告诉 Hilt 如何提供无法注入构造函数的 类型。
     */
    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
//
        return database.logDao()
        //Hilt 可从上述代码中得知,在提供 LogDao 的实例时需要执行 database.logDao()。
        //由于我们拥有 AppDatabase 作为传递依赖项,因此我们还需要告诉 Hilt 如何提供这种类型的实例。
    }

    //因为我们一直希望 Hilt 提供相同的数据库实例,所以我们用 @Singleton 注释 @Provides provideDatabase 方法。
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context):AppDatabase{
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }

}
step4-condition3:@Bindsインターフェースを提供するために使用します。

インターフェイスにはコンストラクター インジェクションを使用できません。インターフェイスにどの実装を使用するかを Hilt に指示するには、 Hilt モジュール内の関数の@Bindsアノテーションを使用できます。抽象関数には@Binds注釈を付ける必要があります(関数は抽象であるため、コードは含まれず、クラスも抽象である必要があります)。抽象関数の戻り値の型は、実装を提供するインターフェイス (つまり、AppNavigator) です。実装は、インターフェイス実装タイプ (つまり、AppNavigatorImpl) を使用して一意のパラメーターを追加することによって指定されます。たとえば、MainActivity では、依存するインターフェイスが

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var navigator: AppNavigator
    ....
}

したがって、このためには、取得するための新しいmodule使用法を作成する必要があります@Binds。型にスコープがある場合、 @Binds メソッドにはスコープ アノテーションが必要です。

//我们的新导航信息(即 AppNavigator)需要特定于 Activity 的信息
//(因为 AppNavigatorImpl 拥有 Activity 作为依赖项)。
// 因此,我们必须将其安装在 Activity 容器中,而不是安装在 Application 容器中,因为这是有关 Activity 的信息所在。
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun provideNavigator(impl: AppNavigatorImpl):AppNavigator
    //参数为具体的实现类,所以要告知hilt如何提供实现类的实例。下面的实现类通过构造函数提供实例
}


//======AppNavigatorImpl.ktx========//

//AppNavigatorImpl 会依赖于 FragmentActivity。由于系统会在 Activity 容器中提供 AppNavigator 实例
// (亦可用于 Fragment 容器和 View 容器,因为 NavigationModule 会安装在 ActivityComponent 中),所以 FragmentActivity 目前可用
class AppNavigatorImpl @Inject constructor(private val activity: FragmentActivity) : AppNavigator {

    override fun navigateTo(screen: Screens) {
        val fragment = when (screen) {
            Screens.BUTTONS -> ButtonsFragment()
            Screens.LOGS -> LogsFragment()
        }

        activity.supportFragmentManager.beginTransaction()
            .replace(R.id.main_container, fragment)
            .addToBackStack(fragment::class.java.canonicalName)
            .commit()
    }
}
step4-condition4: 修飾子を使用する

同じタイプの異なる実装 (複数のバインディング) を提供する方法を Hilt に指示するには、修飾子を使用できます。その定義は実際にはアノテーションです。

@Qualifier 
annotation class InMemoryLogger 
@Qualifier 
annotation class DatabaseLogger

たとえば、ログの追加と削除のチェックにメモリベースの実装メソッドを提供したい場合は、インターフェイスを定義します。

interface LogDataSource {
    fun addLog(msg: String)
    fun getAllLogs(callback: (List<Log>) -> Unit)
    fun removeLogs()
}

Roomベースの実装は以下の通りで、実際には冒頭で述べた実装ですが、インターフェースのみを実装しています。

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

    private val executorService: ExecutorService = Executors.newFixedThreadPool(4)
    private val mainThreadHandler by lazy {
        Handler(Looper.getMainLooper())
    }

    override fun addLog(msg: String) {
        executorService.execute {
            logDao.insertAll(
                Log(
                    msg,
                    System.currentTimeMillis()
                )
            )
        }
    }

    override fun getAllLogs(callback: (List<Log>) -> Unit) {
        executorService.execute {
            val logs = logDao.getAll()
            mainThreadHandler.post { callback(logs) }
        }
    }

    override fun removeLogs() {
        executorService.execute {
            logDao.nukeTable()
        }
    }
}

メモリベースの実装は次のとおりです。

@ActivityScoped
class LoggerInMemoryDataSource @Inject constructor():LogDataSource {
    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()
    }
}

上記の導入に基づいて、インターフェイスを使用する場合、次のように実装クラスを定義します。

@Module
@InstallIn(SingletonComponent::class)
abstract class LoggingDatabaseModule {
    @DatabaseLogger
    @Binds
    @Singleton
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LogDataSource
}

@Module
@InstallIn(ActivityComponent::class)
abstract class LoggingInMemoryModule {
    @InMemoryLogger
    @Binds
    @ActivityScoped
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LogDataSource
}

2 つのモジュールが定義されていることがわかりますがmodule、これらが 1 つのモジュールではない理由は、2 つの実装のスコープが異なるためです。また、これをInMemoryメソッド@Bindsにも追加しました。これは、スコープが実装クラスで指定されている@ActivityScopedために必要です同様に、インスタンスを提供するためにどのメソッドを選択するかを Hilt に指示するカスタム アノテーションもここに追加しました。修飾子が追加されていない場合は、エラーが報告されます。このインターフェースの実際の使用法は次のとおりです。InMemoryLogger

class ButtonsFragment : Fragment() {
    @InMemoryLogger
    @Inject lateinit var logger: LogDataSource
    ...
}

ステップ 3との違いは、ここでの変数の型が特定の実装ではなくインターフェイスであること、そして第 2 に修飾子が追加されていることであることがわかりますまとめると、これが Hilt の基本的な使い方です

Androidの勉強メモ

Android パフォーマンス最適化の記事: https://qr18.cn/FVlo89
Android 車両の記事: https://qr18.cn/F05ZCM
Android リバース セキュリティ研究ノート: https://qr18.cn/CQ5TcL
Android フレームワークの基礎となる原則の記事: https://qr18.cn/AQpN4J
Android オーディオとビデオの記事: https://qr18.cn/Ei3VPD
Jetpack ファミリー バケットの記事 (Compose を含む): https://qr18.cn/A0gajp
Kotlin の記事: https://qr18.cn/CdjtAF
Gradle の記事: https://qr18.cn/DzrmMB
OkHttp ソース コード分析のメモ: https://qr18.cn/Cw0pBD
Flutter の記事: https://qr18.cn/DIvKma
Android の 8 つの知識体系: https://qr18.cn/CyxarU
Android コア ノート:https://qr21.cn/CaZQLo
過去の Android 面接の質問: https://qr18.cn/CKV8OZ
2023 年の最新の Android 面接の質問: https://qr18.cn/CgxrRy
Android 車両開発職の面接演習:https://qr18.cn/FTlyCJ
音声およびビデオの面接の質問:https://qr18.cn/AcV6Ap

おすすめ

転載: blog.csdn.net/maniuT/article/details/132714544