Chèn phần phụ thuộc bằng Hilt

Hilt là một thư viện chèn phần phụ thuộc cho Android, giúp giảm bớt mã nguyên mẫu trong quá trình thực hiện thao tác chèn phần phụ thuộc thủ công vào dự án của bạn. Để chèn phần phụ thuộc theo cách thủ công, bạn cần tự tay tạo từng lớp và các phần phụ thuộc tương ứng, đồng thời dùng vùng chứa để sử dụng lại cũng như quản lý các phần phụ thuộc.

Hilt cung cấp cách thức tiêu chuẩn để sử dụng DI trong ứng dụng của bạn bằng cách cung cấp các vùng chứa cho mọi lớp Android trong dự án, đồng thời tự động quản lý vòng đời của các vùng chứa đó. Hilt được xây dựng dựa trên thư viện DI phổ biến Dagger để hưởng lợi từ độ chính xác của thời gian biên dịch, hiệu suất trong thời gian chạy, khả năng có thể mở rộng và Hỗ trợ Android Studio mà Dagger cung cấp. Để biết thêm thông tin chi tiết, vui lòng xem nội dung Hilt và Dagger.

Hướng dẫn này giải thích các khái niệm cơ bản về Hilt và các vùng chứa đã tạo. Nội dung này cũng bao gồm một hướng dẫn cách khởi động một ứng dụng hiện có để sử dụng Hilt.

Thêm phần phụ thuộc

Trước tiên, hãy thêm trình bổ trợ hilt-android-gradle-plugin vào tệp build.gradle gốc trong dự án của bạn:

Groovy

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

Kotlin

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

Sau đó, áp dụng trình bổ trợ Gradle và thêm các phần phụ thuộc này vào tệp app/build.gradle của bạn:

Groovy

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

android {
  ...
}

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

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

Kotlin

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

android {
  ...
}

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

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

Hilt sử dụng các tính năng của Java 8. Để bật Java 8 trong dự án, hãy thêm đoạn mã sau vào tệp app/build.gradle:

Groovy

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

Kotlin

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

Lớp ứng dụng Hilt

Tất cả ứng dụng sử dụng Hilt phải chứa một lớp Application được chú thích bằng @HiltAndroidApp.

@HiltAndroidApp kích hoạt việc tạo mã của Hilt, bao gồm một lớp cơ sở cho ứng dụng của bạn, đóng vai trò là vùng chứa phần phụ thuộc cấp ứng dụng.

Kotlin

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

Java

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

Thành phần Hilt đã tạo này được gắn vào vòng đời của đối tượng Application và cung cấp các phần phụ thuộc tương ứng. Ngoài ra, đây là thành phần mẹ của ứng dụng, nghĩa là các thành phần khác có thể truy cập vào các phần phụ thuộc mà thành phần Hilt cung cấp.

Chèn phần phụ thuộc vào các lớp Android

Sau khi được thiết lập trong lớp Application và có sẵn một thành phần cấp ứng dụng, Hilt có thể cung cấp phần phụ thuộc cho các lớp Android khác với chú thích @AndroidEntryPoint:

Kotlin

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

Java

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

Hilt hiện hỗ trợ các lớp Android sau:

  • Application (bằng cách sử dụng @HiltAndroidApp)
  • ViewModel (bằng cách sử dụng @HiltViewModel)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

Nếu chú thích một lớp Android bằng @AndroidEntryPoint, thì bạn cũng phải chú giải các lớp Android phụ thuộc vào lớp đó. Chẳng hạn như nếu bạn chú thích một mảnh, thì bạn cũng phải chú thích mọi hoạt động mà bạn sử dụng mảnh đó.

@AndroidEntryPoint tạo một thành phần Hilt riêng lẻ cho từng lớp Android trong dự án. Các thành phần này có thể nhận được phần phụ thuộc từ các lớp cha tương ứng như mô tả trong Hệ phân cấp thành phần.

Để lấy các phần phụ thuộc từ một thành phần, hãy sử dụng chú thích @Inject để thực hiện thao tác chèn trường:

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

Các lớp do Hilt chèn vào có thể có các lớp cơ sở khác cũng sử dụng tính năng chèn. Các lớp đó không cần chú thích @AndroidEntryPoint nếu chúng mang tính trừu tượng.

Để tìm hiểu thêm về phương thức gọi lại trong vòng đời mà một lớp Android được chèn vào, vui lòng xem phần Vòng đời của thành phần.

Xác định các liên kết Hilt

Để thực hiện thao tác chèn trường, Hilt cần biết cách cung cấp thực thể của các phần phụ thuộc cần thiết từ thành phần tương ứng. Liên kết chứa thông tin cần thiết để cung cấp các phiên bản của một loại dưới dạng phần phụ thuộc.

Một cách để cung cấp thông tin liên kết cho Hilt là sử dụng tính năng chèn hàm khởi tạo. Hãy sử dụng chú thích @Inject trên hàm khởi tạo của một lớp để cho Hilt biết cách cung cấp các bản sao của lớp đó:

Kotlin

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

Java

public class AnalyticsAdapter {

  private final AnalyticsService service;

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

Các tham số của một hàm khởi tạo có chú thích của một lớp là các phần phụ thuộc của lớp đó. Trong ví dụ, AnalyticsAdapterAnalyticsService là phần phụ thuộc. Do đó, Hilt cũng phải biết cách cung cấp các bản sao của AnalyticsService.

Mô-đun Hilt

Đôi khi không thể chèn một loại vào hàm khởi tạo. Điều này có thể xảy ra vì nhiều lý do. Ví dụ như bạn không thể chèn một giao diện vào hàm khởi tạo. Bạn cũng không thể chèn một loại mà bạn không sở hữu vào hàm khởi tạo, chẳng hạn như một lớp từ thư viện bên ngoài. Trong những trường hợp này, bạn có thể cung cấp cho Hilt thông tin liên kết bằng cách dùng mô-đun Hilt.

Mô-đun Hilt là một lớp được chú thích bằng @Module. Giống như một mô-đun Dagger, mô-đun này sẽ thông báo cho Hilt cách cung cấp các phiên bản của một số loại nhất định. Không giống như mô-đun Dagger, bạn phải chú thích các mô-đun Hilt bằng @InstallIn để cho Hilt biết mỗi mô-đun sẽ được sử dụng hoặc cài đặt trong lớp Android nào.

Các phần phụ thuộc mà bạn cung cấp trong mô-đun Hilt có sẵn trong tất cả các thành phần được tạo liên kết với lớp Android nơi bạn cài đặt mô-đun Hilt.

Chèn thực thể giao diện bằng @Binds

Hãy xem xét ví dụ về AnalyticsService. Nếu AnalyticsService là một giao diện, thì bạn không thể dùng nó để chèn hàm khởi tạo. Thay vào đó, hãy cung cấp thông tin liên kết cho Hilt bằng cách tạo một hàm trừu tượng có chú thích bằng @Binds bên trong mô-đun Hilt.

Chú thích @Binds sẽ cho Hilt biết phương thức triển khai nào được sử dụng khi cần cung cấp một bản sao của giao diện.

Hàm được chú thích sẽ cung cấp các thông tin sau cho Hilt:

  • Loại dữ liệu trả về của hàm cho Hilt biết bản sao của giao diện mà hàm cung cấp.
  • Tham số hàm cho Hilt biết phương thức triển khai nào cần cung cấp.

Kotlin

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
}

Java

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

Mô-đun Hilt AnalyticsModule được chú thích bằng @InstallIn(ActivityComponent.class) vì bạn muốn Hilt chèn phần phụ thuộc đó vào ExampleActivity. Chú thích này có nghĩa là tất cả phần phụ thuộc trong AnalyticsModule đều có trong mọi hoạt động của ứng dụng.

Chèn thực thể bằng @Provides

Giao diện không phải là trường hợp duy nhất nơi bạn không thể chèn hàm khởi tạo. Bạn cũng không thể chèn hàm khởi tạo nếu bạn không sở hữu lớp đó vì nó đến từ thư viện bên ngoài (chẳng hạn như các lớp Retrofit, OkHttpClient hoặc Cơ sở dữ liệu phòng), hoặc nếu bản sao phải được tạo bằngmẫu trình tạo .

Hãy xem xét ví dụ trước. Nếu không trực tiếp sở hữu lớp AnalyticsService, thì bạn có thể cho Hilt biết cách cung cấp các bản sao của loại này bằng cách tạo một hàm bên trong mô-đun Hilt và chú thích hàm đó bằng @Provides.

Hàm được chú thích cung cấp các thông tin sau cho Hilt:

  • Loại dữ liệu trả về của hàm cho Hilt biết loại mà hàm cung cấp các bản sao.
  • Các tham số hàm cho Hilt biết các phần phụ thuộc của loại tương ứng.
  • Phần nội dung hàm cho Hilt biết cách cung cấp một bản sao của loại tương ứng. Hilt thực thi phần nội dung hàm mỗi khi cần cung cấp một phiên bản của loại đó.

Kotlin

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

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

Cung cấp nhiều liên kết cho cùng một loại

Trong trường hợp bạn cần Hilt cung cấp các cách triển khai khác nhau của cùng một loại làm phần phụ thuộc, bạn phải cung cấp cho Hilt nhiều đường liên kết. Bạn có thể xác định nhiều liên kết cho cùng một loại bằng bộ hạn định.

Bộ hạn định là chú thích mà bạn dùng để xác định một mối liên kết cụ thể cho một loại khi loại đó có nhiều liên kết được xác định.

Hãy xem xét ví dụ. Nếu cần chặn các lệnh gọi đến AnalyticsService, bạn có thể sử dụng đối tượng OkHttpClient bằng một trình chặn. Đối với các dịch vụ khác, bạn có thể cần chặn lệnh gọi theo một cách khác. Trong trường hợp đó, bạn cần cho Hilt biết cách cung cấp hai cách triển khai OkHttpClient khác nhau.

Trước tiên, hãy xác định những bộ hạn định bạn sẽ sử dụng để chú thích các phương thức @Binds hoặc @Provides:

Kotlin

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

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

Java

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

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

Sau đó, Hilt cần biết cách cung cấp một bản sao của loại tương ứng với từng bộ hạn định. Trong trường hợp này, bạn có thể sử dụng mô-đun Hilt với @Provides. Cả hai phương thức đều có cùng một loại dữ liệu trả về, nhưng những bộ hạn định này gắn nhãn chúng dưới dạng hai liên kết khác nhau:

Kotlin

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

Java

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

Bạn có thể chèn loại cụ thể mà bạn cần bằng cách chú thích trường hoặc tham số bằng bộ hạn định tương ứng:

Kotlin

// 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
}

Java

// 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;
  ...
}

Phương pháp hay nhất là nếu bạn thêm bộ hạn định vào một loại, hãy thêm bộ hạn định vào bằng mọi cách có thể để cung cấp phần phụ thuộc đó. Nếu bạn rời khỏi việc triển khai cơ sở hoặc triển khai chung mà không có bộ hạn định thì rất dễ xảy ra lỗi, ngoài ra còn có thể dẫn đến tình trạng chèn sai phần phụ thuộc.

Bộ hạn định được xác định trước trong Hilt

Hilt cung cấp một số bộ hạn định được xác định trước. Chẳng hạn như vì bạn có thể cần lớp Context từ ứng dụng hoặc hoạt động, nên Hilt sẽ cung cấp bộ hạn định @ApplicationContext@ActivityContext.

Giả sử lớp AnalyticsAdapter từ ví dụ cần ngữ cảnh của hoạt động. Đoạn mã sau đây minh hoạ cách cung cấp ngữ cảnh hoạt động cho AnalyticsAdapter:

Kotlin

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

Java

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

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

Để biết các liên kết được xác định trước khác có sẵn trong Hilt, vui lòng xem nội dung phần Liên kết mặc định của thành phần.

Các thành phần đã tạo cho lớp Android

Đối với mỗi lớp Android mà bạn có thể thực hiện thao tác chèn trường, có một thành phần Hilt đi kèm mà bạn có thể tham khảo trong chú thích @InstallIn. Mỗi thành phần Hilt chịu trách nhiệm chèn các liên kết vào lớp Android tương ứng.

Các ví dụ trước cho thấy cách sử dụng ActivityComponent trong các mô-đun Hilt.

Hilt cung cấp các thành phần sau:

Thành phần Hilt Trình chèn cho
SingletonComponent Application
ActivityRetainedComponent Không áp dụng
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View được chú thích bằng @WithFragmentBindings
ServiceComponent Service

Vòng đời của thành phần

Hilt tự động tạo và huỷ các phiên bản của lớp thành phần được tạo theo vòng đời của các lớp Android tương ứng.

Thành phần đã tạo Được tạo ở Bị hủy ở
SingletonComponent Application#onCreate() Application đã huỷ bỏ
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent Đã tạo ViewModel Đã huỷ bỏ ViewModel
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() Đã huỷ bỏ View
ViewWithFragmentComponent View#super() Đã hủy bỏ View
ServiceComponent Service#onCreate() Service#onDestroy()

Các phạm vi thành phần

Theo mặc định, tất cả các đường liên kết trong Hilt đều không có phạm vi. Tức là mỗi khi ứng dụng yêu cầu liên kết, Hilt sẽ tạo một bản sao mới của loại cần thiết.

Ở ví dụ này, mỗi khi Hilt cung cấp AnalyticsAdapter dưới dạng một phần phụ thuộc vào một loại khác hoặc thông qua tính năng chèn trường (như trong ExampleActivity), Hilt sẽ cung cấp một thực thể mới của AnalyticsAdapter.

Tuy nhiên, Hilt cũng cho phép đưa liên kết vào một thành phần cụ thể. Hilt chỉ tạo liên kết theo phạm vi một lần cho mỗi thực thể của thành phần mà liên kết đó được xác định phạm vi và mọi yêu cầu cho liên kết đó đều chia sẻ cùng một thực thể.

Bảng dưới đây liệt kê các chú thích phạm vi cho từng thành phần được tạo:

Lớp Android Thành phần đã tạo Phạm vi
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View được chú thích bằng @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

Trong ví dụ, nếu bạn đặt phạm vi của AnalyticsAdapter thành ActivityComponent bằng @ActivityScoped, thì Hilt sẽ cung cấp cùng một phiên bản của AnalyticsAdapter trong suốt thời gian của hoạt động tương ứng:

Kotlin

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

Java

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

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

Giả sử AnalyticsService có trạng thái nội bộ yêu cầu sử dụng cùng một phiên bản mọi lúc — không chỉ trong ExampleActivity mà còn ở mọi nơi trong ứng dụng. Trong trường hợp này, bạn nên đặt phạm vi của AnalyticsService thành SingletonComponent. Theo đó, bất cứ khi nào thành phần cần cung cấp một phiên bản của AnalyticsService, thành phần đó sẽ cung cấp cùng một phiên bản mọi lúc.

Ví dụ sau minh họa cách xác định phạm vi liên kết với một thành phần trong mô-đun Hilt. Phạm vi của liên kết phải khớp với phạm vi của thành phần nơi nó được cài đặt, vì vậy trong ví dụ này, bạn phải cài đặt AnalyticsService trong SingletonComponent thay vì ActivityComponent:

Kotlin

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

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

Để tìm hiểu thêm về phạm vi thành phần trong Hilt, vui lòng xem nội dung phần Xác định phạm vi trong Android và Hilt.

Hệ phân cấp thành phần

Việc cài đặt mô-đun vào một thành phần cho phép các liên kết của mô-đun đó được truy cập dưới dạng phần phụ thuộc của các liên kết khác trong thành phần đó hoặc trong bất kỳ thành phần con nào bên dưới nó trong hệ thống phân cấp thành phần:

ViewWithFragmentComponent nằm trong FragmentComponent. FragmentComponent và ViewComponent nằm trong ActivityComponent. ActivityComponent nằm trong ActivityRetainedComponent. ViewModelComponent nằm trong ActivityRetainedComponent. ActivityRetainedComponent và ServiceComponent nằm trong SingletonComponent.
Hình 1. Hệ phân cấp các thành phần mà Hilt tạo ra.

Liên kết mặc định của thành phần

Mỗi thành phần Hilt đi kèm với một tập hợp các liên kết mặc định mà Hilt có thể chèn dưới dạng phần phụ thuộc vào các liên kết tuỳ chỉnh của riêng bạn. Lưu ý là các liên kết này tương ứng với hoạt động và các loại mảnh chung chứ không phải bất kỳ lớp con cụ thể nào. Điều này là do Hilt sử dụng một định nghĩa thành phần hoạt động duy nhất để chèn tất cả các hoạt động. Mỗi hoạt động có một bản sao khác của thành phần này.

Thành phần Android Các liên kết mặc định
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

Liên kết ngữ cảnh ứng dụng cũng có sẵn bằng cách sử dụng @ApplicationContext. Ví dụ:

Kotlin

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

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

Java

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;
  }
}

Bạn cũng có thể liên kết ngữ cảnh hoạt động bằng cách sử dụng @ActivityContext. Ví dụ:

Kotlin

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

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

Java

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;
  }
}

Chèn các phần phụ thuộc vào các lớp không được Hilt hỗ trợ

Hilt đi kèm với tính năng hỗ trợ cho các lớp phổ biến nhất trên Android. Tuy nhiên, bạn có thể cần chèn trường trong các lớp mà Hilt không hỗ trợ.

Trong những trường hợp đó, bạn có thể tạo một điểm truy cập bằng cách sử dụng chú thích @EntryPoint. Điểm truy cập là ranh giới giữa mã do Hilt quản lý và mã không phải do Hilt quản lý. Đây là điểm đầu tiên mã nhập vào biểu đồ của các đối tượng mà Hilt quản lý. Điểm truy cập cho phép Hilt sử dụng mã mà Hilt không quản lý để cung cấp các phần phụ thuộc trong biểu đồ phần phụ thuộc.

Chẳng hạn như Hilt không trực tiếp hỗ trợ các nhà cung cấp nội dung. Nếu muốn nhà cung cấp nội dung sử dụng Hilt để lấy một số phần phụ thuộc, thì bạn cần xác định giao diện được chú thích bằng @EntryPoint cho từng loại liên kết mà bạn muốn và đưa bộ hạn định vào. Sau đó, hãy thêm @InstallIn để chỉ định thành phần cần cài đặt điểm truy cập như sau:

Kotlin

class ExampleContentProvider : ContentProvider() {

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

  ...
}

Java

public class ExampleContentProvider extends ContentProvider {

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

Để truy cập vào một điểm truy cập, hãy sử dụng phương thức tĩnh thích hợp từ EntryPointAccessors. Tham số này phải là bản sao của thành phần hoặc đối tượng @AndroidEntryPoint, đóng vai trò là chủ thể thành phần. Hãy đảm bảo là thành phần bạn truyền dưới dạng tham số và phương thức tĩnh EntryPointAccessors đều khớp với lớp Android trong chú thích @InstallIn trên giao diện @EntryPoint:

Kotlin

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

Java

public class ExampleContentProvider extends ContentProvider {

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

Trong ví dụ này, bạn phải sử dụng ApplicationContext để truy xuất điểm truy cập vì điểm truy cập đó được cài đặt trong SingletonComponent. Nếu liên kết mà bạn muốn truy xuất nằm trong ActivityComponent, bạn nên dùng ActivityContext.

Hilt và Dagger

Hilt được xây dựng dựa trên thư viện chèn phần phụ thuộc Dagger, cung cấp cách thức tiêu chuẩn để tích hợp Dagger vào ứng dụng Android.

Đối với Dagger, mục tiêu của Hilt sẽ như sau:

  • Đơn giản hóa cơ sở hạ tầng liên quan đến Dagger cho các ứng dụng Android.
  • Tạo một nhóm thành phần và phạm vi tiêu chuẩn để dễ dàng thiết lập, dễ đọc và chia sẻ mã giữa các ứng dụng.
  • Mang đến phương thức dễ dàng để cung cấp nhiều mối liên kết cho nhiều loại bản dựng, chẳng hạn như kiểm thử, gỡ lỗi hoặc phát hành.

Vì hệ điều hành Android tạo bản sao của nhiều lớp khung cho riêng nó, nên việc sử dụng Dagger trong ứng dụng Android sẽ yêu cầu bạn viết một lượng lớn các mã nguyên mẫu. Hilt sẽ giúp giảm mã nguyên mẫu có liên quan đến việc sử dụng Dagger trong ứng dụng Android. Hilt tự động tạo và cung cấp các mục sau:

  • Các thành phần để tích hợp lớp khung Android với Dagger mà bạn cần phải tạo theo cách thủ công nếu không có các thành phần này.
  • Chú thích phạm vi để sử dụng với các thành phần mà Hilt tạo tự động.
  • Các đường liên kết được xác định trước để đại diện cho các lớp Android, chẳng hạn như Application hoặc Activity.
  • Bộ hạn định xác định trước đại diện cho @ApplicationContext@ActivityContext.

Mã Dagger và Hilt có thể cùng tồn tại trong cùng một cơ sở mã. Tuy nhiên, trong hầu hết các trường hợp, bạn nên sử dụng Hilt để quản lý toàn bộ việc sử dụng Dagger trên Android. Để di chuyển một dự án sử dụng Dagger sang Hilt, vui lòng xem hướng dẫn di chuyểnLớp học lập trình về di chuyển ứng dụng Dagger sang Hilt.

Tài nguyên khác

Để tìm hiểu thêm về Hilt, vui lòng xem các tài nguyên bổ sung sau đây:

Mẫu

Lớp học lập trình

Blog