Внедрение зависимостей с помощью Hilt,Внедрение зависимостей с помощью Hilt

Hilt — это библиотека внедрения зависимостей для Android, которая упрощает шаблонное внедрение зависимостей вручную в ваш проект. Выполнение внедрения зависимостей вручную требует от вас создания каждого класса и его зависимостей вручную, а также использования контейнеров для повторного использования и управления зависимостями.

Hilt предоставляет стандартный способ использования внедрения зависимостей в вашем приложении, предоставляя контейнеры для каждого класса Android в вашем проекте и автоматически управляя их жизненными циклами. Hilt построен на основе популярной библиотеки DI Dagger, чтобы получить преимущества от корректности времени компиляции, производительности во время выполнения, масштабируемости и поддержки Android Studio , которые обеспечивает Dagger. Для получения дополнительной информации см. Рукоять и Кинжал .

В этом руководстве объясняются основные концепции Hilt и созданных им контейнеров. Он также включает демонстрацию того, как загрузить существующее приложение для использования Hilt.

Добавление зависимостей

Сначала добавьте плагин hilt-android-gradle-plugin в корневой файл build.gradle вашего проекта:

классный

plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.51.1' apply false
}

Котлин

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

Затем примените плагин Gradle и добавьте эти зависимости в файл app/build.gradle :

классный

...
plugins {
  id 'kotlin-kapt'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

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

// Allow references to generated code
kapt {
  correctErrorTypes true
}

Котлин

plugins {
  id("kotlin-kapt")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.51.1")
  kapt("com.google.dagger:hilt-android-compiler:2.51.1")
}

// Allow references to generated code
kapt {
  correctErrorTypes = true
}

Hilt использует функции Java 8 . Чтобы включить Java 8 в вашем проекте, добавьте следующее в файл app/build.gradle :

классный

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

Котлин

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

Класс применения рукояти

Все приложения, использующие Hilt, должны содержать класс Application , помеченный @HiltAndroidApp .

@HiltAndroidApp запускает генерацию кода Hilt, включая базовый класс для вашего приложения, который служит контейнером зависимостей на уровне приложения.

Котлин

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

Ява

@HiltAndroidApp
public class ExampleApplication extends Application { ... }

Этот сгенерированный компонент Hilt прикрепляется к жизненному циклу объекта Application и предоставляет ему зависимости. Кроме того, это родительский компонент приложения, а это означает, что другие компоненты могут получить доступ к предоставляемым им зависимостям.

Внедрение зависимостей в классы Android

Как только Hilt настроен в вашем классе Application и доступен компонент уровня приложения, Hilt может предоставлять зависимости другим классам Android, имеющим аннотацию @AndroidEntryPoint :

Котлин

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

Ява

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

В настоящее время Hilt поддерживает следующие классы Android:

  • Application (с помощью @HiltAndroidApp )
  • ViewModel (с помощью @HiltViewModel )
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

Если вы аннотируете класс Android с помощью @AndroidEntryPoint , вам также необходимо аннотировать классы Android, которые от него зависят. Например, если вы аннотируете фрагмент, вы также должны аннотировать все действия, в которых вы используете этот фрагмент.

@AndroidEntryPoint создает отдельный компонент Hilt для каждого класса Android в вашем проекте. Эти компоненты могут получать зависимости от соответствующих родительских классов, как описано в разделе «Иерархия компонентов» .

Чтобы получить зависимости от компонента, используйте аннотацию @Inject для внедрения поля:

Котлин

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Ява

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

Классы, которые внедряет Hilt, могут иметь другие базовые классы, которые также используют внедрение. Этим классам не нужна аннотация @AndroidEntryPoint , если они абстрактные.

Дополнительные сведения о том, в какой обратный вызов жизненного цикла внедряется класс Android, см. в разделе Время жизни компонентов .

Определить привязки рукоятки

Чтобы выполнить внедрение полей, Hilt необходимо знать, как предоставить экземпляры необходимых зависимостей из соответствующего компонента. Привязка содержит информацию, необходимую для предоставления экземпляров типа в качестве зависимости.

Одним из способов предоставления информации о привязке в Hilt является внедрение конструктора . Используйте аннотацию @Inject в конструкторе класса, чтобы сообщить Hilt, как предоставлять экземпляры этого класса:

Котлин

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Ява

public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

Параметры аннотированного конструктора класса являются зависимостями этого класса. В этом примере AnalyticsAdapter имеет AnalyticsService в качестве зависимости. Следовательно, Hilt также должен знать, как предоставлять экземпляры AnalyticsService .

Модули рукояти

Иногда тип невозможно внедрить с помощью конструктора. Это может произойти по нескольким причинам. Например, вы не можете внедрить интерфейс в конструктор. Вы также не можете внедрить в конструктор тип, которым вы не владеете, например класс из внешней библиотеки. В этих случаях вы можете предоставить Hilt информацию о привязке, используя модули Hilt .

Модуль Hilt — это класс, помеченный @Module . Как и модуль Dagger , он сообщает Hilt, как предоставлять экземпляры определенных типов. В отличие от модулей Dagger, вы должны пометить модули Hilt с помощью @InstallIn чтобы сообщить Hilt, в каком классе Android каждый модуль будет использоваться или установлен.

Зависимости, которые вы предоставляете в модулях Hilt, доступны во всех сгенерированных компонентах, связанных с классом Android, в котором вы устанавливаете модуль Hilt.

Внедрение экземпляров интерфейса с помощью @Binds

Рассмотрим пример AnalyticsService . Если AnalyticsService является интерфейсом, вы не можете внедрить его в конструктор. Вместо этого предоставьте Hilt информацию о привязке, создав абстрактную функцию с аннотацией @Binds внутри модуля Hilt.

Аннотация @Binds сообщает Hilt, какую реализацию использовать, когда необходимо предоставить экземпляр интерфейса.

Аннотированная функция предоставляет Hilt следующую информацию:

  • Тип возвращаемого значения функции сообщает Hilt, экземпляры какого интерфейса предоставляет функция.
  • Параметр функции сообщает Hilt, какую реализацию предоставить.

Котлин

interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

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

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Ява

public interface AnalyticsService {
  void analyticsMethods();
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
  ...
  @Inject
  AnalyticsServiceImpl(...) {
    ...
  }
}

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

Модуль Hilt AnalyticsModule помечен аннотацией @InstallIn(ActivityComponent.class) поскольку вы хотите, чтобы Hilt внедрил эту зависимость в ExampleActivity . Эта аннотация означает, что все зависимости в AnalyticsModule доступны во всех действиях приложения.

Внедрить экземпляры с помощью @Provides

Интерфейсы — не единственный случай, когда вы не можете внедрить тип в конструктор. Внедрение в конструктор также невозможно, если вы не являетесь владельцем класса, поскольку он взят из внешней библиотеки (такие классы, как Retrofit , OkHttpClient или базы данных Room ), или если экземпляры необходимо создавать с помощью шаблона компоновщика .

Рассмотрим предыдущий пример. Если вы не являетесь владельцем класса AnalyticsService напрямую, вы можете указать Hilt, как предоставлять экземпляры этого типа, создав функцию внутри модуля Hilt и аннотировав эту функцию @Provides .

Аннотированная функция предоставляет Hilt следующую информацию:

  • Тип возвращаемого значения функции сообщает Hilt, экземпляры какого типа предоставляет функция.
  • Параметры функции сообщают Hilt зависимости соответствующего типа.
  • Тело функции сообщает Hilt, как предоставить экземпляр соответствующего типа. Hilt выполняет тело функции каждый раз, когда ему необходимо предоставить экземпляр этого типа.

Котлин

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Ява

@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Предоставьте несколько привязок для одного и того же типа.

В тех случаях, когда вам нужно, чтобы Hilt предоставил различные реализации одного и того же типа в качестве зависимостей, вы должны предоставить Hilt несколько привязок. Вы можете определить несколько привязок для одного и того же типа с помощью квалификаторов .

Квалификатор — это аннотация, которую вы используете для идентификации конкретной привязки для типа, если для этого типа определено несколько привязок.

Рассмотрим пример. Если вам нужно перехватить вызовы AnalyticsService , вы можете использовать объект OkHttpClient с перехватчиком . Для других услуг вам может потребоваться перехват вызовов другим способом. В этом случае вам нужно указать Hilt, как предоставить две разные реализации OkHttpClient .

Сначала определите квалификаторы, которые вы будете использовать для аннотирования методов @Binds или @Provides :

Котлин

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

Ява

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

Затем Hilt необходимо знать, как предоставить экземпляр типа, соответствующего каждому квалификатору. В этом случае вы можете использовать модуль Hilt с @Provides . Оба метода имеют один и тот же тип возвращаемого значения, но квалификаторы помечают их как две разные привязки:

Котлин

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

Ява

@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient(
    AuthInterceptor authInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient(
    OtherInterceptor otherInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(otherInterceptor)
                   .build();
  }
}

Вы можете ввести конкретный тип, который вам нужен, аннотируя поле или параметр соответствующим квалификатором:

Котлин

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

Ява

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    @AuthInterceptorOkHttpClient OkHttpClient okHttpClient
  ) {
      return new Retrofit.Builder()
                  .baseUrl("https://example.com")
                  .client(okHttpClient)
                  .build()
                  .create(AnalyticsService.class);
  }
}

// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
  }
}

// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

Рекомендуется, если вы добавляете квалификатор к типу, добавлять квалификаторы ко всем возможным способам предоставления этой зависимости. Если оставить базовую или общую реализацию без квалификатора, это чревато ошибками и может привести к тому, что Hilt внедрит неправильную зависимость.

Предопределенные квалификаторы в Hilt

Hilt предоставляет некоторые предопределенные квалификаторы. Например, поскольку вам может понадобиться класс Context либо из приложения, либо из действия, Hilt предоставляет квалификаторы @ApplicationContext и @ActivityContext .

Предположим, что классу AnalyticsAdapter из примера нужен контекст действия. Следующий код демонстрирует, как предоставить контекст активности AnalyticsAdapter :

Котлин

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

Ява

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}

Другие предопределенные привязки, доступные в Hilt, см. в разделе Привязки компонентов по умолчанию .

Сгенерированные компоненты для классов Android

Для каждого класса Android, в котором вы можете выполнять внедрение полей, существует связанный компонент Hilt, на который вы можете сослаться в аннотации @InstallIn . Каждый компонент Hilt отвечает за внедрение своих привязок в соответствующий класс Android.

Предыдущие примеры продемонстрировали использование ActivityComponent в модулях Hilt.

Hilt предоставляет следующие компоненты:

Компонент рукояти Инжектор для
SingletonComponent Application
ActivityRetainedComponent Н/Д
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View с аннотацией @WithFragmentBindings
ServiceComponent Service

Срок службы компонентов

Hilt автоматически создает и уничтожает экземпляры сгенерированных классов компонентов в соответствии с жизненным циклом соответствующих классов Android.

Сгенерированный компонент Создано в Разрушен в
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()

Области компонентов

По умолчанию все привязки в Hilt не ограничены . Это означает, что каждый раз, когда ваше приложение запрашивает привязку, Hilt создает новый экземпляр нужного типа.

В этом примере каждый раз, когда Hilt предоставляет AnalyticsAdapter в качестве зависимости от другого типа или посредством внедрения поля (как в ExampleActivity ), Hilt предоставляет новый экземпляр AnalyticsAdapter .

Однако Hilt также позволяет ограничить привязку конкретным компонентом. Hilt создает привязку с ограниченной областью только один раз для каждого экземпляра компонента, областью действия которого является привязка, и все запросы к этой привязке используют один и тот же экземпляр.

В таблице ниже перечислены аннотации области действия для каждого сгенерированного компонента:

Android-класс Сгенерированный компонент Объем
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View с аннотацией @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

В этом примере, если вы привязываете AnalyticsAdapter к ActivityComponent с помощью @ActivityScoped , Hilt предоставляет один и тот же экземпляр AnalyticsAdapter на протяжении всего срока действия соответствующего действия:

Котлин

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Ява

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

Предположим, что AnalyticsService имеет внутреннее состояние, которое требует, чтобы каждый раз использовался один и тот же экземпляр — не только в ExampleActivity , но и в любом месте приложения. В этом случае уместно ограничить область действия AnalyticsService SingletonComponent . В результате всякий раз, когда компоненту необходимо предоставить экземпляр AnalyticsService , он каждый раз предоставляет один и тот же экземпляр.

В следующем примере показано, как определить область действия привязки к компоненту в модуле Hilt. Область привязки должна соответствовать области действия компонента, в котором она установлена, поэтому в этом примере вы должны установить AnalyticsService в SingletonComponent вместо ActivityComponent :

Котлин

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Ява

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent.class)
public class AnalyticsModule {

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Чтобы узнать больше об областях действия компонентов Hilt, см. раздел «Область действия в Android и Hilt» .

Иерархия компонентов

Установка модуля в компонент позволяет получить доступ к его привязкам как к зависимости от других привязок в этом компоненте или в любом дочернем компоненте ниже него в иерархии компонентов:

ViewWithFragmentComponent находится в разделе FragmentComponent. FragmentComponent и ViewComponent находятся в разделе ActivityComponent. ActivityComponent находится в разделе ActivityRetainedComponent. ViewModelComponent находится в разделе ActivityRetainedComponent. ActivityRetainedComponent и ServiceComponent находятся в SingletonComponent.
Рисунок 1. Иерархия компонентов, которые генерирует Hilt.

Привязки компонента по умолчанию

Каждый компонент Hilt поставляется с набором привязок по умолчанию, которые Hilt может внедрить в качестве зависимостей в ваши собственные привязки. Обратите внимание, что эти привязки соответствуют общим типам активности и фрагментов, а не какому-либо конкретному подклассу. Это связано с тем, что Hilt использует одно определение компонента действия для внедрения всех действий. Каждое действие имеет отдельный экземпляр этого компонента.

Android-компонент Привязки по умолчанию
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application , Activity
FragmentComponent Application , Activity , Fragment
ViewComponent Application , Activity , View
ViewWithFragmentComponent Application , Activity , Fragment , View
ServiceComponent Application , Service

Привязка контекста приложения также доступна с помощью @ApplicationContext . Например:

Котлин

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

Ява

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

Привязка контекста активности также доступна с помощью @ActivityContext . Например:

Котлин

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }

Ява

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

Внедрение зависимостей в классы, не поддерживаемые Hilt

Hilt поддерживает наиболее распространенные классы Android. Однако вам может потребоваться выполнить внедрение полей в классы, которые Hilt не поддерживает.

В этих случаях вы можете создать точку входа, используя аннотацию @EntryPoint . Точка входа — это граница между кодом, управляемым Hilt, и кодом, который им не управляет. Это момент, когда код впервые попадает в граф объектов, которыми управляет Hilt. Точки входа позволяют Hilt использовать код, который Hilt не может обеспечить зависимостями в графе зависимостей.

Например, Hilt не поддерживает напрямую поставщиков контента . Если вы хотите, чтобы поставщик контента использовал Hilt для получения некоторых зависимостей, вам необходимо определить интерфейс с аннотацией @EntryPoint для каждого типа привязки, который вам нужен, и включить квалификаторы. Затем добавьте @InstallIn чтобы указать компонент, в котором необходимо установить точку входа, следующим образом:

Котлин

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}

Ява

public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(SingletonComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

Чтобы получить доступ к точке входа, используйте соответствующий статический метод из EntryPointAccessors . Параметр должен быть либо экземпляром компонента, либо объектом @AndroidEntryPoint , который действует как держатель компонента. Убедитесь, что компонент, который вы передаете в качестве параметра, и статический метод EntryPointAccessors соответствуют классу Android в аннотации @InstallIn интерфейса @EntryPoint :

Котлин

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

Ява

public class ExampleContentProvider extends ContentProvider {

  @Override
  public Cursor query(...) {
    Context appContext = getContext().getApplicationContext();
    ExampleContentProviderEntryPoint hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint.class);
    AnalyticsService analyticsService = hiltEntryPoint.analyticsService();
  }
}

В этом примере вы должны использовать ApplicationContext для получения точки входа, поскольку точка входа установлена ​​в SingletonComponent . Если бы привязка, которую вы хотели получить, находилась в ActivityComponent , вместо этого вы бы использовали ActivityContext .

Рукоять и кинжал

Hilt построен на основе библиотеки внедрения зависимостей Dagger и обеспечивает стандартный способ включения Dagger в приложение Android.

В отношении Dagger цели Hilt заключаются в следующем:

  • Упростить инфраструктуру, связанную с Dagger, для приложений Android.
  • Создать стандартный набор компонентов и областей действия для упрощения настройки, удобства чтения и совместного использования кода между приложениями.
  • Обеспечить простой способ предоставления различных привязок для различных типов сборок, таких как тестирование, отладка или выпуск.

Поскольку операционная система Android создает экземпляры многих собственных классов фреймворка, использование Dagger в приложении Android требует от вас написания значительного количества шаблонов. Hilt сокращает стандартный код, используемый при использовании Dagger в приложении Android. Hilt автоматически генерирует и предоставляет следующее:

  • Компоненты для интеграции классов платформы Android с Dagger, которые в противном случае вам пришлось бы создавать вручную.
  • Аннотации области действия для использования с компонентами, которые Hilt генерирует автоматически.
  • Предопределенные привязки для представления классов Android, таких как Application или Activity .
  • Предопределенные квалификаторы для представления @ApplicationContext и @ActivityContext .

Код Dagger и Hilt может сосуществовать в одной кодовой базе. Однако в большинстве случаев лучше всего использовать Hilt для управления всем использованием Dagger на Android. Чтобы перенести проект, использующий Dagger, на Hilt, см. руководство по миграции и лабораторную работу «Миграция вашего приложения Dagger на Hilt» .

Дополнительные ресурсы

Чтобы узнать больше о Hilt, посетите следующие дополнительные ресурсы.

Образцы

Кодлабы

Блоги

,

Hilt — это библиотека внедрения зависимостей для Android, которая упрощает шаблонное внедрение зависимостей вручную в ваш проект. Выполнение внедрения зависимостей вручную требует от вас создания каждого класса и его зависимостей вручную, а также использования контейнеров для повторного использования и управления зависимостями.

Hilt предоставляет стандартный способ использования внедрения зависимостей в вашем приложении, предоставляя контейнеры для каждого класса Android в вашем проекте и автоматически управляя их жизненными циклами. Hilt построен на основе популярной библиотеки DI Dagger и обеспечивает корректность времени компиляции, производительность во время выполнения, масштабируемость и поддержку Android Studio , которую обеспечивает Dagger. Для получения дополнительной информации см. Рукоять и Кинжал .

В этом руководстве объясняются основные концепции Hilt и созданных им контейнеров. Он также включает демонстрацию того, как загрузить существующее приложение для использования Hilt.

Добавление зависимостей

Сначала добавьте плагин hilt-android-gradle-plugin в корневой файл build.gradle вашего проекта:

классный

plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.51.1' apply false
}

Котлин

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

Затем примените плагин Gradle и добавьте эти зависимости в файл app/build.gradle :

классный

...
plugins {
  id 'kotlin-kapt'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

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

// Allow references to generated code
kapt {
  correctErrorTypes true
}

Котлин

plugins {
  id("kotlin-kapt")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.51.1")
  kapt("com.google.dagger:hilt-android-compiler:2.51.1")
}

// Allow references to generated code
kapt {
  correctErrorTypes = true
}

Hilt использует функции Java 8 . Чтобы включить Java 8 в вашем проекте, добавьте следующее в файл app/build.gradle :

классный

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

Котлин

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

Класс применения рукояти

Все приложения, использующие Hilt, должны содержать класс Application , помеченный @HiltAndroidApp .

@HiltAndroidApp запускает генерацию кода Hilt, включая базовый класс для вашего приложения, который служит контейнером зависимостей на уровне приложения.

Котлин

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

Ява

@HiltAndroidApp
public class ExampleApplication extends Application { ... }

Этот сгенерированный компонент Hilt прикрепляется к жизненному циклу объекта Application и предоставляет ему зависимости. Кроме того, это родительский компонент приложения, а это означает, что другие компоненты могут получить доступ к предоставляемым им зависимостям.

Внедрение зависимостей в классы Android

Как только Hilt настроен в вашем классе Application и доступен компонент уровня приложения, Hilt может предоставлять зависимости другим классам Android, имеющим аннотацию @AndroidEntryPoint :

Котлин

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

Ява

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

В настоящее время Hilt поддерживает следующие классы Android:

  • Application (с помощью @HiltAndroidApp )
  • ViewModel (с помощью @HiltViewModel )
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

Если вы аннотируете класс Android с помощью @AndroidEntryPoint , вам также необходимо аннотировать классы Android, которые от него зависят. Например, если вы аннотируете фрагмент, вы также должны аннотировать все действия, в которых вы используете этот фрагмент.

@AndroidEntryPoint создает отдельный компонент Hilt для каждого класса Android в вашем проекте. Эти компоненты могут получать зависимости от соответствующих родительских классов, как описано в разделе «Иерархия компонентов» .

Чтобы получить зависимости от компонента, используйте аннотацию @Inject для внедрения поля:

Котлин

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Ява

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

Классы, которые внедряет Hilt, могут иметь другие базовые классы, которые также используют внедрение. Этим классам не нужна аннотация @AndroidEntryPoint , если они абстрактные.

Дополнительные сведения о том, в какой обратный вызов жизненного цикла внедряется класс Android, см. в разделе Время жизни компонентов .

Определить привязки рукоятки

Чтобы выполнить внедрение полей, Hilt необходимо знать, как предоставить экземпляры необходимых зависимостей из соответствующего компонента. Привязка содержит информацию, необходимую для предоставления экземпляров типа в качестве зависимости.

Одним из способов предоставления информации о привязке в Hilt является внедрение конструктора . Используйте аннотацию @Inject в конструкторе класса, чтобы сообщить Hilt, как предоставлять экземпляры этого класса:

Котлин

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Ява

public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

Параметры аннотированного конструктора класса являются зависимостями этого класса. В этом примере AnalyticsAdapter имеет зависимость AnalyticsService . Следовательно, Hilt также должен знать, как предоставлять экземпляры AnalyticsService .

Модули рукояти

Иногда тип невозможно внедрить с помощью конструктора. Это может произойти по нескольким причинам. Например, вы не можете внедрить интерфейс в конструктор. Вы также не можете внедрить в конструктор тип, которым вы не владеете, например класс из внешней библиотеки. В этих случаях вы можете предоставить Hilt информацию о привязке, используя модули Hilt .

Модуль Hilt — это класс, помеченный @Module . Как и модуль Dagger , он сообщает Hilt, как предоставлять экземпляры определенных типов. В отличие от модулей Dagger, вы должны пометить модули Hilt с помощью @InstallIn чтобы сообщить Hilt, в каком классе Android каждый модуль будет использоваться или установлен.

Зависимости, которые вы предоставляете в модулях Hilt, доступны во всех сгенерированных компонентах, связанных с классом Android, в котором вы устанавливаете модуль Hilt.

Внедрение экземпляров интерфейса с помощью @Binds

Рассмотрим пример AnalyticsService . Если AnalyticsService является интерфейсом, вы не можете внедрить его в конструктор. Вместо этого предоставьте Hilt информацию о привязке, создав абстрактную функцию с аннотацией @Binds внутри модуля Hilt.

Аннотация @Binds сообщает Hilt, какую реализацию использовать, когда необходимо предоставить экземпляр интерфейса.

Аннотированная функция предоставляет Hilt следующую информацию:

  • Тип возвращаемого значения функции сообщает Hilt, экземпляры какого интерфейса предоставляет функция.
  • Параметр функции сообщает Hilt, какую реализацию предоставить.

Котлин

interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

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

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Ява

public interface AnalyticsService {
  void analyticsMethods();
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
  ...
  @Inject
  AnalyticsServiceImpl(...) {
    ...
  }
}

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

Модуль Hilt AnalyticsModule помечен аннотацией @InstallIn(ActivityComponent.class) поскольку вы хотите, чтобы Hilt внедрил эту зависимость в ExampleActivity . Эта аннотация означает, что все зависимости в AnalyticsModule доступны во всех действиях приложения.

Внедрить экземпляры с помощью @Provides

Интерфейсы — не единственный случай, когда вы не можете внедрить тип в конструктор. Внедрение в конструктор также невозможно, если вы не являетесь владельцем класса, поскольку он взят из внешней библиотеки (такие классы, как Retrofit , OkHttpClient или базы данных Room ), или если экземпляры необходимо создавать с помощью шаблона компоновщика .

Рассмотрим предыдущий пример. Если вы не являетесь владельцем класса AnalyticsService напрямую, вы можете указать Hilt, как предоставлять экземпляры этого типа, создав функцию внутри модуля Hilt и аннотировав эту функцию @Provides .

Аннотированная функция предоставляет Hilt следующую информацию:

  • Тип возвращаемого значения функции сообщает Hilt, экземпляры какого типа предоставляет функция.
  • Параметры функции сообщают Hilt зависимости соответствующего типа.
  • Тело функции сообщает Hilt, как предоставить экземпляр соответствующего типа. Hilt выполняет тело функции каждый раз, когда ему необходимо предоставить экземпляр этого типа.

Котлин

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Ява

@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Предоставьте несколько привязок для одного и того же типа.

В тех случаях, когда вам нужно, чтобы Hilt предоставил различные реализации одного и того же типа в качестве зависимостей, вы должны предоставить Hilt несколько привязок. Вы можете определить несколько привязок для одного и того же типа с помощью квалификаторов .

Квалификатор — это аннотация, которую вы используете для идентификации конкретной привязки для типа, если для этого типа определено несколько привязок.

Рассмотрим пример. Если вам нужно перехватить вызовы AnalyticsService , вы можете использовать объект OkHttpClient с перехватчиком . Для других услуг вам может потребоваться перехват вызовов другим способом. В этом случае вам нужно указать Hilt, как предоставить две разные реализации OkHttpClient .

Сначала определите квалификаторы, которые вы будете использовать для аннотирования методов @Binds или @Provides :

Котлин

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

Ява

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

Затем Hilt необходимо знать, как предоставить экземпляр типа, соответствующего каждому квалификатору. В этом случае вы можете использовать модуль Hilt с @Provides . Оба метода имеют один и тот же тип возвращаемого значения, но квалификаторы помечают их как две разные привязки:

Котлин

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

Ява

@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient(
    AuthInterceptor authInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient(
    OtherInterceptor otherInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(otherInterceptor)
                   .build();
  }
}

Вы можете ввести конкретный тип, который вам нужен, аннотируя поле или параметр соответствующим квалификатором:

Котлин

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

Ява

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    @AuthInterceptorOkHttpClient OkHttpClient okHttpClient
  ) {
      return new Retrofit.Builder()
                  .baseUrl("https://example.com")
                  .client(okHttpClient)
                  .build()
                  .create(AnalyticsService.class);
  }
}

// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
  }
}

// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

Рекомендуется, если вы добавляете квалификатор к типу, добавлять квалификаторы ко всем возможным способам предоставления этой зависимости. Если оставить базовую или общую реализацию без квалификатора, это чревато ошибками и может привести к тому, что Hilt внедрит неправильную зависимость.

Предопределенные квалификаторы в Hilt

Hilt предоставляет некоторые предопределенные квалификаторы. Например, поскольку вам может понадобиться класс Context либо из приложения, либо из действия, Hilt предоставляет квалификаторы @ApplicationContext и @ActivityContext .

Предположим, что классу AnalyticsAdapter из примера нужен контекст действия. Следующий код демонстрирует, как предоставить контекст активности AnalyticsAdapter :

Котлин

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

Ява

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}

Другие предопределенные привязки, доступные в Hilt, см. в разделе Привязки компонентов по умолчанию .

Сгенерированные компоненты для классов Android

Для каждого класса Android, в котором вы можете выполнять внедрение полей, существует связанный компонент Hilt, на который вы можете сослаться в аннотации @InstallIn . Каждый компонент Hilt отвечает за внедрение своих привязок в соответствующий класс Android.

Предыдущие примеры продемонстрировали использование ActivityComponent в модулях Hilt.

Hilt предоставляет следующие компоненты:

Компонент рукояти Инжектор для
SingletonComponent Application
ActivityRetainedComponent Н/Д
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View с аннотацией @WithFragmentBindings
ServiceComponent Service

Срок службы компонентов

Hilt автоматически создает и уничтожает экземпляры сгенерированных классов компонентов в соответствии с жизненным циклом соответствующих классов Android.

Сгенерированный компонент Создано в Разрушен в
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()

Области компонентов

По умолчанию все привязки в Hilt не ограничены . Это означает, что каждый раз, когда ваше приложение запрашивает привязку, Hilt создает новый экземпляр нужного типа.

В этом примере каждый раз, когда Hilt предоставляет AnalyticsAdapter в качестве зависимости от другого типа или посредством внедрения поля (как в ExampleActivity ), Hilt предоставляет новый экземпляр AnalyticsAdapter .

Однако Hilt также позволяет ограничить привязку конкретным компонентом. Hilt создает привязку с ограниченной областью только один раз для каждого экземпляра компонента, областью действия которого является привязка, и все запросы к этой привязке используют один и тот же экземпляр.

В таблице ниже перечислены аннотации области действия для каждого сгенерированного компонента:

Android-класс Сгенерированный компонент Объем
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View с аннотацией @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

В этом примере, если вы привязываете AnalyticsAdapter к ActivityComponent с помощью @ActivityScoped , Hilt предоставляет один и тот же экземпляр AnalyticsAdapter на протяжении всего срока действия соответствующего действия:

Котлин

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

Ява

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

Предположим, что AnalyticsService имеет внутреннее состояние, которое требует, чтобы каждый раз использовался один и тот же экземпляр — не только в ExampleActivity , но и в любом месте приложения. В этом случае уместно ограничить область действия AnalyticsService SingletonComponent . В результате всякий раз, когда компоненту необходимо предоставить экземпляр AnalyticsService , он каждый раз предоставляет один и тот же экземпляр.

В следующем примере показано, как определить область привязки к компоненту в модуле Hilt. Область привязки должна соответствовать области действия компонента, в котором она установлена, поэтому в этом примере вы должны установить AnalyticsService в SingletonComponent вместо ActivityComponent :

Котлин

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Ява

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent.class)
public class AnalyticsModule {

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

Чтобы узнать больше об областях действия компонентов Hilt, см. раздел «Область действия в Android и Hilt» .

Иерархия компонентов

Установка модуля в компонент позволяет получить доступ к его привязкам как к зависимости от других привязок в этом компоненте или в любом дочернем компоненте ниже него в иерархии компонентов:

ViewWithFragmentComponent находится в разделе FragmentComponent. FragmentComponent и ViewComponent находятся в разделе ActivityComponent. ActivityComponent находится в разделе ActivityRetainedComponent. ViewModelComponent находится в разделе ActivityRetainedComponent. ActivityRetainedComponent и ServiceComponent находятся в SingletonComponent.
Рисунок 1. Иерархия компонентов, которые генерирует Hilt.

Компонент привязки по умолчанию

Каждый компонент рукояти поставляется с набором привязков по умолчанию, которые рукоятки могут вводить в качестве зависимостей в ваши собственные пользовательские привязки. Обратите внимание, что эти привязки соответствуют общей активности и типам фрагментов, а не к какому -либо конкретному подклассу. Это связано с тем, что Хилт использует одно определение компонента активности для введения всех действий. Каждое действие имеет различный экземпляр этого компонента.

Компонент Android Привязки по умолчанию
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application , Activity
FragmentComponent Application , Activity , Fragment
ViewComponent Application , Activity , View
ViewWithFragmentComponent Application , Activity , Fragment , View
ServiceComponent Application , Service

Привязка контекста приложения также доступна с использованием @ApplicationContext . Например:

Котлин

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

Ява

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

Привязка контекста активности также доступна с использованием @ActivityContext . Например:

Котлин

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }

Ява

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

Введите зависимости в классах, не поддерживаемых рукояти

Руководитель поставляется с поддержкой наиболее распространенных классов Android. Тем не менее, вам, возможно, придется выполнить инъекцию полевых участков в классах, которые не поддерживают.

В этих случаях вы можете создать точку входа, используя аннотацию @EntryPoint . Точка входа - это граница между кодом, который управляется рукоятиком и кодом, которого нет. Это тот момент, когда код сначала входит в график объектов, которым управляет Хилт. Точки въезда позволяют рукояти использовать код, который рукоятка не удается обеспечить зависимости в графике зависимостей.

Например, рукоятка не поддерживает поставщиков контента . Если вы хотите, чтобы поставщик контента использовал рукоятку для получения некоторых зависимостей, вам необходимо определить интерфейс, который аннотирован @EntryPoint для каждого типа привязки, который вы хотите, и включить квалификаторы. Затем добавьте @InstallIn чтобы указать компонент, в котором можно установить точку входа следующим образом:

Котлин

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}

Ява

public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(SingletonComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

Чтобы получить доступ к точке входа, используйте соответствующий статический метод из EntryPointAccessors . Параметр должен быть либо экземпляром компонента, либо объектом @AndroidEntryPoint , который действует как держатель компонента. Убедитесь, что компонент, который вы проходите в виде параметра, и статический метод EntryPointAccessors соответствует классу Android в аннотации @InstallIn на интерфейсе @EntryPoint :

Котлин

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

Ява

public class ExampleContentProvider extends ContentProvider {

  @Override
  public Cursor query(...) {
    Context appContext = getContext().getApplicationContext();
    ExampleContentProviderEntryPoint hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint.class);
    AnalyticsService analyticsService = hiltEntryPoint.analyticsService();
  }
}

В этом примере необходимо использовать ApplicationContext для извлечения точки входа, потому что точка входа установлена ​​в SingletonComponent . Если бы привязки, которую вы хотели получить, было в ActivityComponent , вы бы вместо этого использовали ActivityContext .

Рукоять и кинжал

Руководство строится поверх библиотеки инъекций зависимостей кинжала , обеспечивая стандартный способ включения кинжала в приложение для Android.

Что касается кинжала, цели рукояти следующие:

  • Чтобы упростить инфраструктуру, связанную с кинжалом для приложений для Android.
  • Чтобы создать стандартный набор компонентов и областей для облегчения настройки, читаемости и обмена кодами между приложениями.
  • Чтобы обеспечить простой способ обеспечить различные привязки к различным типам сборки, таким как тестирование, отладка или выпуск.

Поскольку операционная система Android интенсирует многие свои собственные фреймворк -классы, использование кинжала в приложении Android требует, чтобы вы написали значительное количество котельки. Руководитель уменьшает код шволю, который участвует в использовании кинжала в приложении Android. Рукоять автоматически генерирует и обеспечивает следующее:

  • Компоненты для интеграции классов Android Framework с кинжалом, которые вам в противном случае понадобится для создания вручную.
  • Аннотации по применению для использования с компонентами, которые рукоятка генерирует автоматически.
  • Предопределенные привязки для представления классов Android, таких как Application или Activity .
  • Предопределенные квалификаторы для представления @ApplicationContext и @ActivityContext .

Код кинжала и рукояти может сосуществовать в той же кодовой базе. Тем не менее, в большинстве случаев лучше всего использовать рукоятку, чтобы управлять всем вашим использованием кинжала на Android. Чтобы мигрировать проект, который использует кинжал, чтобы рукоять, см. Руководство по миграции и переносить приложение для вашего кинжала в CodeLab .

Дополнительные ресурсы

Чтобы узнать больше о рукояти, см. Следующие дополнительные ресурсы.

Образцы

Коделабс

Блоги