Dependency Injection พร้อม Hilt

Hilt เป็นไลบรารีการแทรกทรัพยากร Dependency สำหรับ Android ที่ลด Boilerplate ของการแทรกทรัพยากร Dependency ด้วยตนเองในโปรเจ็กต์ของคุณ การใช้ทรัพยากร Dependency ด้วยตนเอง injection ต้องการให้คุณสร้าง ทุกคลาสและทรัพยากร Dependency ทั้งหมดด้วยตนเอง และเพื่อนําคอนเทนเนอร์ไปใช้ซ้ำและ จัดการการอ้างอิง

Hilt ให้วิธีมาตรฐานในการใช้ DI ในแอปพลิเคชันของคุณโดยให้ คอนเทนเนอร์ของคลาส Android ทุกคลาสในโปรเจ็กต์ รวมถึงการจัดการวงจร โดยอัตโนมัติ Hilt สร้างขึ้นจากไลบรารี DI ยอดนิยม นักวิเคราะห์เพื่อใช้ประโยชน์จาก ความถูกต้องของเวลาคอมไพล์ ประสิทธิภาพของรันไทม์ ความสามารถในการปรับขนาด และ Android Studio การสนับสนุน ที่ Dagger มอบให้ สำหรับข้อมูลเพิ่มเติม โปรดดู Hilt และ กริช

คู่มือนี้จะอธิบายแนวคิดพื้นฐานของ Hilt และคอนเทนเนอร์ที่สร้างขึ้น ทั้งนี้ ยังมีการสาธิตวิธีเปิดเครื่องแอปที่มีอยู่เพื่อใช้งาน Hilt ด้วย

การเพิ่มทรัพยากร Dependency

ก่อนอื่น ให้เพิ่มปลั๊กอิน hilt-android-gradle-plugin ลงในรูทของโปรเจ็กต์ ไฟล์ build.gradle:

ดึงดูด

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
}

จากนั้นใช้ปลั๊กอิน Gradle และเพิ่มการอ้างอิงเหล่านี้ใน app/build.gradle ไฟล์:

ดึงดูด

...
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 {
  id("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 ใช้ฟีเจอร์ของ Java 8 วิธีเปิดใช้ Java 8 in เพิ่มค่าต่อไปนี้ลงในไฟล์ app/build.gradle

ดึงดูด

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

คลาสแอปพลิเคชัน Hilt

แอปทั้งหมดที่ใช้ Hilt ต้องมีองค์ประกอบ ชั้นเรียน Application ที่มีคำอธิบายประกอบ @HiltAndroidApp

@HiltAndroidApp จะทริกเกอร์การสร้างโค้ดของ Hilt รวมถึงคลาสฐานสำหรับ แอปพลิเคชันของคุณที่ทำหน้าที่เป็นคอนเทนเนอร์ทรัพยากร Dependency ระดับแอปพลิเคชัน

Kotlin

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

Java

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

คอมโพเนนต์ Hilt ที่สร้างขึ้นนี้แนบอยู่กับออบเจ็กต์ Application และช่วยมอบทรัพยากร Dependency ให้ นอกจากนี้ โมเดลยังอยู่ภายใต้ ซึ่งหมายความว่าคอมโพเนนต์อื่นๆ สามารถเข้าถึง Dependency ที่มีให้

แทรกทรัพยากร Dependency ลงในคลาส Android

เมื่อตั้งค่า Hilt ในชั้นเรียน Application และระดับแอปพลิเคชันแล้ว คอมโพเนนต์พร้อมใช้งาน Hilt สามารถให้ทรัพยากร Dependency ไปยังคลาส Android อื่นๆ ได้ ที่มีคำอธิบายประกอบ @AndroidEntryPoint:

Kotlin

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

Java

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

ปัจจุบัน Hilt สนับสนุนคลาส Android ต่อไปนี้

  • Application (โดยใช้ @HiltAndroidApp)
  • ViewModel (โดยใช้ @HiltViewModel)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

หากใส่คำอธิบายประกอบให้ชั้นเรียน Android ด้วย @AndroidEntryPoint คุณจะต้อง ใส่คำอธิบายประกอบคลาส Android ที่ต้องใช้ เช่น หากใส่คำอธิบายประกอบ Fragment คุณต้องใส่คำอธิบายประกอบของกิจกรรมที่คุณใช้ ส่วนย่อย

@AndroidEntryPoint สร้างคอมโพเนนต์ Hilt 1 คอมโพเนนต์สำหรับ Android แต่ละเครื่อง ในชั้นเรียนของคุณ คอมโพเนนต์เหล่านี้จะรับทรัพยากร Dependency จาก คลาสระดับบนที่เกี่ยวข้องตามที่อธิบายไว้ในคอมโพเนนต์ ลำดับชั้น

หากต้องการรับทรัพยากร Dependency จากคอมโพเนนต์ ให้ใช้คำอธิบายประกอบ @Inject ในการดำเนินการ การแทรกฟิลด์:

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

คลาสที่ Hilt แทรกอาจมีคลาสพื้นฐานอื่นๆ ที่ใช้การแทรกด้วย ชั้นเรียนเหล่านั้นไม่ต้องใช้คำอธิบายประกอบ @AndroidEntryPoint หาก ที่เป็นนามธรรม

หากต้องการดูข้อมูลเพิ่มเติมว่ามีการแทรก Callback สำหรับวงจรการใช้งานที่มีการแทรกคลาส Android เข้ามา ดูอายุการใช้งานของคอมโพเนนต์

กำหนดการเชื่อมโยง Hilt

ในการดำเนินการแทรกฟิลด์ Hilt จำเป็นต้องทราบวิธีระบุอินสแตนซ์ของ ทรัพยากร Dependency ที่จำเป็นจากคอมโพเนนต์ที่เกี่ยวข้อง การเชื่อมโยงประกอบด้วย ข้อมูลที่จำเป็นต่อการระบุอินสแตนซ์ของประเภทเป็นทรัพยากร Dependency

วิธีหนึ่งในการให้ข้อมูลการเชื่อมโยงกับ Hilt คือการแทรกตัวสร้าง ใช้ คำอธิบายประกอบ @Inject ในเครื่องมือสร้างคลาสเพื่อบอก Hilt ระบุอินสแตนซ์ของคลาสดังกล่าว เช่น

Kotlin

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

Java

public class AnalyticsAdapter {

  private final AnalyticsService service;

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

พารามิเตอร์ของตัวสร้างที่มีคำอธิบายประกอบของคลาสคือทรัพยากร Dependency ของ ชั้นเรียนนั้น ในตัวอย่างนี้ AnalyticsAdapter มี AnalyticsService เป็น การพึ่งพา ด้วยเหตุนี้ Hilt จึงต้องรู้วิธีแสดงตัวอย่าง AnalyticsService

โมดูล Hilt

บางครั้งไม่สามารถแทรกประเภทตัวสร้าง กรณีนี้อาจเกิดขึ้นได้กับ เหตุผล เช่น คุณไม่สามารถสร้างอินเทอร์เฟซได้ และคุณยัง ไม่สามารถแทรกประเภทที่คุณไม่ได้เป็นเจ้าของ เช่น คลาสจาก ไลบรารีภายนอก ในกรณีเหล่านี้ คุณสามารถระบุข้อมูลการเชื่อมโยงของ Hilt ได้ ได้โดยใช้โมดูล Hit

โมดูล Hilt เป็นคลาสที่มีการใส่คำอธิบายประกอบเป็น @Module เหมือนกัญชา โมดูล บอก Hilt ถึงวิธีแสดงอินสแตนซ์บางประเภท ซึ่งแตกต่างจากโมดูล Dagger คุณต้องใส่คำอธิบายประกอบให้กับโมดูล Hilt ด้วย @InstallIn เพื่อบอก Hilt ว่า Android แต่ละโมดูลจะถูกใช้หรือติดตั้ง

ทรัพยากร Dependency ที่คุณระบุในโมดูล Hilt จะมีอยู่ในทุกรายการที่สร้างขึ้น ที่เกี่ยวข้องกับคลาส Android ที่คุณติดตั้ง โมดูล Hilt

แทรกอินสแตนซ์อินเทอร์เฟซด้วย @Binds

ลองดูตัวอย่าง AnalyticsService หาก AnalyticsService เป็นอินเทอร์เฟซ ก็จะไม่สามารถแทรกโค้ดได้ ให้ใช้การเชื่อมโยง Hilt แทน โดยการสร้างฟังก์ชันนามธรรมที่มีคำอธิบายประกอบด้วย @Binds ภายใน โมดูล Hilt

คำอธิบายประกอบ @Binds จะบอก Hilt ว่าควรใช้การใช้งานใดเมื่อจำเป็นต้อง เป็นอินสแตนซ์ของอินเทอร์เฟซ

ฟังก์ชันที่มีคำอธิบายประกอบจะให้ข้อมูลต่อไปนี้แก่ Hilt

  • ประเภทผลลัพธ์ของฟังก์ชันจะบอก Hilt ว่าฟังก์ชันนั้นให้อินเทอร์เฟซใด
  • พารามิเตอร์ฟังก์ชันจะบอกให้ Hilt ทราบว่าควรติดตั้งใช้งานแบบใด

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

โมดูล Hilt AnalyticsModule มีคำอธิบายประกอบด้วย @InstallIn(ActivityComponent.class) เพราะคุณต้องการให้ Hilt แทรก ที่พึ่งพิงกับ ExampleActivity หมายเหตุนี้หมายความว่า ทรัพยากร Dependency ใน AnalyticsModule มีอยู่ในกิจกรรมทั้งหมดของแอป

แทรกอินสแตนซ์ด้วย @Provides

อินเทอร์เฟซไม่ได้เป็นเพียงกรณีเดียวที่คุณไม่สามารถสร้างการแทรกประเภท การแทรกตัวสร้างเป็นไปไม่ได้เช่นกัน หากคุณไม่ใช่เจ้าของชั้นเรียนเนื่องจาก มาจากไลบรารีภายนอก (ชั้นเรียนต่างๆ เช่น การปรับปรุง OkHttpClient หรือฐานข้อมูลห้อง) หรือหากอินสแตนซ์ต้อง สร้างขึ้นด้วยเครื่องมือสร้าง รูปแบบ

ลองดูตัวอย่างก่อนหน้านี้ หากคุณไม่ได้เป็นเจ้าของ AnalyticsService โดยตรง คุณสามารถบอก Hilt ถึงวิธีจัดเตรียมอินสแตนซ์ประเภทนี้โดยสร้าง ภายในโมดูล Hilt และใส่คำอธิบายประกอบฟังก์ชันนั้นด้วย @Provides

ฟังก์ชันที่มีคำอธิบายประกอบจะให้ข้อมูลต่อไปนี้แก่ Hilt

  • ประเภทการแสดงผลฟังก์ชันจะบอก Hilt ว่าฟังก์ชันแสดงอินสแตนซ์ประเภทใด
  • พารามิเตอร์ฟังก์ชันจะบอก Hilt ถึงทรัพยากร Dependency ของประเภทที่เกี่ยวข้อง
  • ส่วนเนื้อหาของฟังก์ชันจะบอก Hilt เกี่ยวกับวิธีระบุอินสแตนซ์ของ ประเภท Hilt จะเรียกใช้เนื้อหาของฟังก์ชันทุกครั้งที่ต้องการระบุ อินสแตนซ์ของประเภทนั้นๆ ได้

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

ทำการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกัน

ในกรณีที่คุณต้องการใช้ Hilt เพื่อแสดงการติดตั้งใช้งานที่แตกต่างกัน เป็นประเภทการอ้างอิง คุณต้องระบุ Hilt ที่มีการเชื่อมโยงหลายรายการ คุณสามารถ กำหนดการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกันด้วยตัวระบุ

ตัวระบุคือข้อมูลเสริมที่คุณใช้เพื่อระบุการเชื่อมโยงที่เฉพาะเจาะจงสำหรับ เมื่อประเภทนั้นมีการกำหนดการเชื่อมโยงไว้หลายรายการ

ลองดูตัวอย่างนี้ หากต้องการโอนสายไปยัง AnalyticsService โปรด สามารถใช้ออบเจ็กต์ OkHttpClient ที่มี Interceptor สำหรับ ให้บริการอื่นๆ คุณอาจจะต้อง ดักฟังการโทรด้วยวิธีอื่นๆ ในนั้น คุณต้องบอก Hilt ถึงวิธีการติดตั้งใช้งาน 2 ประเภท OkHttpClient

ก่อนอื่น ให้กำหนดคำขยายที่จะใช้ในคำอธิบายประกอบ @Binds หรือ @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 {}

จากนั้น Hilt ต้องการทราบวิธีระบุอินสแตนซ์ของประเภทที่ตรงกับ กับตัวระบุแต่ละรายการ ในกรณีนี้ คุณใช้โมดูล Hilt กับ @Provides ได้ ทั้ง 2 วิธีมีประเภทการแสดงผลเหมือนกัน แต่ตัวระบุจะติดป้ายกำกับว่าเป็น 2 การเชื่อมโยงต่างๆ กัน:

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

คุณสามารถแทรกประเภทที่คุณต้องการโดยใส่คำอธิบายประกอบในช่องหรือ ที่มีตัวระบุที่เกี่ยวข้อง

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

แนวทางปฏิบัติแนะนำคือหากคุณเพิ่มคำขยายในประเภท ให้เพิ่มคำขยายในประเภท วิธีต่างๆ ที่เป็นไปได้ในการให้ทรัพยากร Dependency นั้น ออกจากฐานทัพหรือ การใช้งานโดยไม่มีตัวระบุจะเกิดข้อผิดพลาดได้ง่ายและอาจทำให้เกิด Hilt กำลังแทรกทรัพยากร Dependency ที่ไม่ถูกต้อง

ตัวระบุที่กำหนดไว้ล่วงหน้าใน Hilt

Hilt มีตัวระบุที่กำหนดไว้ล่วงหน้า ตัวอย่างเช่น คุณอาจต้องการ Context จากแอปพลิเคชันหรือกิจกรรม Hilt จะให้ รอบเพลย์ออฟ @ApplicationContext และ @ActivityContext

สมมติว่าคลาส AnalyticsAdapter จากตัวอย่างต้องการบริบทของ กิจกรรมนั้น โค้ดต่อไปนี้แสดงวิธีจัดเตรียมกิจกรรม บริบทสำหรับ 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;
  }
}

สำหรับการเชื่อมโยงที่กำหนดไว้ล่วงหน้าอื่นๆ ที่มีอยู่ใน 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 เป็นทรัพยากร Dependency ประเภทอื่นหรือผ่านการแทรกช่อง (เช่น ExampleActivity) Hilt จะให้ อินสแตนซ์ใหม่ของ AnalyticsAdapter

อย่างไรก็ตาม Hilt ยังอนุญาตให้กำหนดขอบเขตการเชื่อมโยงไปยังคอมโพเนนต์ที่เฉพาะเจาะจงได้ด้วย ฮิลต์ จะสร้างการเชื่อมโยงที่กำหนดขอบเขตเพียง 1 ครั้งต่ออินสแตนซ์ของคอมโพเนนต์ที่ ขอบเขตของการผูกข้อมูลจะจำกัดอยู่ และคำขอทั้งหมดสำหรับการเชื่อมโยงนั้นจะใช้อินสแตนซ์เดียวกัน

ตารางด้านล่างแสดงรายการคำอธิบายประกอบขอบเขตสำหรับคอมโพเนนต์ที่สร้างขึ้นแต่ละรายการ

คลาส 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 อินสแตนซ์เดียวกัน ตลอดระยะเวลาของกิจกรรมที่เกี่ยวข้อง:

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

สมมติว่า AnalyticsService มีสถานะภายในที่จำเป็นต้องใช้ อินสแตนซ์ที่จะใช้ทุกครั้ง ไม่ใช่เฉพาะใน ExampleActivity แต่ใช้กับที่ใดก็ได้ใน แอปนั้น ในกรณีนี้ คุณควรกำหนดขอบเขต AnalyticsService ให้กับ SingletonComponent ผลที่ได้คือเมื่อใดก็ตามที่คอมโพเนนต์ต้อง มีอินสแตนซ์ของ AnalyticsService ซึ่งมีอินสแตนซ์เดียวกันทุก

ตัวอย่างต่อไปนี้แสดงวิธีกำหนดขอบเขตการเชื่อมโยงกับคอมโพเนนต์ใน โมดูล Hilt ขอบเขตของการเชื่อมโยงต้องตรงกับขอบเขตของคอมโพเนนต์ในตำแหน่งที่ ติดตั้งไว้แล้ว ดังนั้นในตัวอย่างนี้ คุณต้องติดตั้ง AnalyticsService ใน SingletonComponent แทน 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);
  }
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับขอบเขตคอมโพเนนต์ Hilt ได้ที่การจำกัดขอบเขตใน Android และ Hilt

ลำดับชั้นขององค์ประกอบ

การติดตั้งโมดูลลงในคอมโพเนนต์อนุญาตให้เข้าถึงการเชื่อมโยงของโมดูลดังกล่าวเป็น การพึ่งพาการเชื่อมโยงอื่นๆ ในคอมโพเนนต์นั้นหรือในคอมโพเนนต์ย่อยใดๆ ด้านล่าง ในลำดับชั้นขององค์ประกอบ

วันที่ ViewWithFragmentComponent อยู่ใน FragmentComponent คอมโพเนนต์ส่วนย่อย
    และ ViewComponent จะอยู่ในส่วน ActivityComponent ActivityComponent อยู่ภายใต้
    ActivityProtectedComponent ViewModelComponent อยู่ภายใต้
    ActivityProtectedComponent ActivityProtectedComponent และ ServiceComponent
    อยู่ใน SingletonComponent
รูปที่ 1 ลำดับชั้นของคอมโพเนนต์ที่ฮิลต์ ก็สร้างขึ้นได้

การเชื่อมโยงเริ่มต้นของคอมโพเนนต์

คอมโพเนนต์ Hilt แต่ละรายการมาพร้อมกับชุดการเชื่อมโยงเริ่มต้นที่ Hilt สามารถแทรกได้ Dependency ลงในการเชื่อมโยงที่คุณกำหนดเอง โปรดทราบว่าการเชื่อมโยงเหล่านี้จะสอดคล้องกับ กิจกรรมทั่วไปและประเภทส่วนย่อย ไม่ใช่คลาสย่อยที่เฉพาะเจาะจงใดๆ นั่นเป็นเพราะ 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 ได้ด้วย เช่น

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

การเชื่อมโยงบริบทกิจกรรมยังใช้ @ActivityContext ได้ด้วย สำหรับ ตัวอย่าง:

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

แทรกทรัพยากร Dependency ในชั้นเรียนที่ Hilt ไม่รองรับ

Hilt มาพร้อมการรองรับชั้นเรียน Android ที่พบบ่อยที่สุด อย่างไรก็ตาม คุณอาจ จำเป็นต้องแทรกข้อมูลภาคสนามในชั้นเรียนที่ Hilt ไม่รองรับ

ในกรณีเหล่านั้น คุณสามารถสร้างจุดแรกเข้าโดยใช้ @EntryPoint หมายเหตุ จุดแรกเข้าคือขอบเขตระหว่างโค้ดที่จัดการโดย Hilt และโค้ดที่ไม่เกี่ยวข้อง ซึ่งเป็นจุดที่โค้ดจะป้อนลงในกราฟของ ที่ Hilt จัดการ จุดแรกเข้าช่วยให้ Hilt สามารถใช้โค้ดที่ Hilt ทำ ไม่สามารถระบุทรัพยากร Dependency ภายในกราฟทรัพยากร Dependency ได้

ตัวอย่างเช่น Hilt ไม่สนับสนุนเนื้อหา ผู้ให้บริการเครือข่าย หากต้องการคอนเทนต์ ที่จะใช้ Hilt เพื่อรับการอ้างอิงบางอย่าง คุณต้องกำหนดอินเทอร์เฟซ ที่มีคำอธิบายประกอบด้วย @EntryPoint สำหรับการเชื่อมโยงแต่ละประเภทที่คุณต้องการและ รวมถึงคำขยาย จากนั้นเพิ่ม @InstallIn เพื่อระบุคอมโพเนนต์ที่จะ ติดตั้งจุดแรกเข้าดังนี้

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

หากต้องการเข้าถึงจุดแรกเข้า ให้ใช้เมธอดแบบคงที่จาก EntryPointAccessors พารามิเตอร์ควรเป็นอินสแตนซ์คอมโพเนนต์หรือ ออบเจ็กต์ @AndroidEntryPoint ที่ทำหน้าที่เป็นเจ้าของคอมโพเนนต์ ตรวจสอบว่า ที่คอมโพเนนต์ที่คุณส่งเป็นพารามิเตอร์และ EntryPointAccessors แบบคงที่ ทั้ง 2 วิธีนี้ตรงกับคลาส Android ในคำอธิบายประกอบ @InstallIn ใน อินเทอร์เฟซ @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();
  }
}

ในตัวอย่างนี้ คุณต้องใช้ ApplicationContext เพื่อเรียกข้อมูลรายการ เนื่องจากจุดแรกเข้าได้รับการติดตั้งใน SingletonComponent หาก การเชื่อมโยงที่คุณต้องการดึงข้อมูลนั้นอยู่ใน ActivityComponent คุณจะ ให้ใช้ ActivityContext แทน

ฮิลต์และกริช

Hilt สร้างโดยมี Dagger ไลบรารี Dependency Injection ที่มีวิธีมาตรฐานในการรวม 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 to Hilt โปรดดูการย้ายข้อมูล คู่มือและการย้ายข้อมูล แอป Dagger เพื่อ Hilt Codelab

แหล่งข้อมูลเพิ่มเติม

หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับ Hilt โปรดดูแหล่งข้อมูลเพิ่มเติมต่อไปนี้

ตัวอย่าง

Codelab

บล็อก