Dependency Injection บน Android: Hilt vs Koin - คู่มือฉบับสมบูรณ์และคำถามสัมภาษณ์ 2026
เปรียบเทียบ Hilt และ Koin สำหรับ dependency injection บน Android พร้อมตัวอย่างโค้ดจริง benchmark ประสิทธิภาพ และคำถามสัมภาษณ์งาน อัปเดตสำหรับ Hilt 2.57 และ Koin 4.2

Dependency injection เป็นรูปแบบการออกแบบที่กำหนดวิธีที่ class รับ dependency ของตนเองแทนที่จะสร้างขึ้นมาเอง ในระบบนิเวศ Android ปี 2026 มี framework สองตัวที่ครองตลาด: Hilt (โซลูชัน compile-time จาก Google ที่สร้างบน Dagger) เวอร์ชัน 2.57.1 และ Koin (service locator น้ำหนักเบาที่ทำงาน ณ runtime) เวอร์ชัน 4.2.1 การเลือก framework ที่เหมาะสมมีผลกระทบต่อเวลา build ประสิทธิภาพการเริ่มต้นแอป ความสามารถในการทดสอบ และความเร็วในการ onboard สมาชิกใหม่ในทีม
Hilt ตรวจสอบ dependency graph ทั้งหมดในระหว่างการคอมไพล์ และทำให้ build ล้มเหลวหากขาด binding ใดก็ตาม ส่วน Koin จะ resolve dependency ณ runtime ผ่าน Kotlin DSL โดยจะตรวจพบข้อผิดพลาดการกำหนดค่าเฉพาะเมื่อ code path ที่เกี่ยวข้องถูกเรียกใช้งาน ความแตกต่างพื้นฐานนี้เป็นตัวขับเคลื่อน trade-off ทั้งหมดระหว่างสอง framework
กลไกการทำงานของ Hilt เบื้องหลัง
Hilt สร้าง component ของ Dagger ที่ถูก map กับ class ต่าง ๆ ในวงจรชีวิต Android Annotation @HiltAndroidApp จะทริกเกอร์การสร้างโค้ดอัตโนมัติในขั้นตอนคอมไพล์ โดยสร้าง factory และ provider สำหรับทุก binding ที่ประกาศไว้ KSP (Kotlin Symbol Processing) เข้ามาแทนที่ KAPT ในฐานะ annotation processor ที่แนะนำตั้งแต่ Hilt 2.48 ช่วยลดเวลาประมวลผล annotation ลงประมาณครึ่งหนึ่ง
การตั้งค่า Hilt โดยทั่วไปประกอบด้วยสามส่วน: module ที่ประกาศ binding, entry point (Activity, Fragment หรือ ViewModel) และ class Application ที่ถูกกำกับด้วย @HiltAndroidApp
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideUserRepository(
retrofit: Retrofit
): UserRepository {
return UserRepositoryImpl(retrofit.create(UserApi::class.java))
}
}@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_users.value = userRepository.getUsers()
}
}
}คอมไพเลอร์จะตรวจสอบว่า UserRepository มี provider ที่ถูกต้องก่อนที่แอปจะรัน หาก binding ขาดหาย จะปรากฏเป็น build error ไม่ใช่ runtime crash
การตั้งค่า Koin ด้วยแนวทาง Kotlin DSL
Koin ใช้แนวทางตรงกันข้าม: ไม่มีการสร้างโค้ดอัตโนมัติ ไม่มี annotation processing Dependency ถูกประกาศด้วย Kotlin ล้วนผ่าน DSL และ framework จะ resolve ณ runtime ผ่าน service registry ส่วนกลาง Koin 4.2 เปิดตัว lazy modules สำหรับการโหลดแบบขนานตอนเริ่มต้นแอป และ engine CoreResolverV2 ใหม่ที่ปรับปรุงการ resolve scope ให้เร็วขึ้น
val appModule = module {
// Retrofit instance แบบ singleton
single<Retrofit> {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// Repository แบบ singleton ที่ bind กับ interface
single<UserRepository> {
UserRepositoryImpl(get<Retrofit>().create(UserApi::class.java))
}
// ViewModel พร้อม repository ที่ถูก inject
viewModel { UserViewModel(get()) }
}class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}DSL ของ Koin อ่านได้อย่างเป็นธรรมชาติและไม่ต้องการ plugin annotation processing ในการกำหนดค่า Gradle สิ่งที่ต้องแลกคือ: ข้อผิดพลาดในการเขียน get() resolution จะปรากฏเฉพาะ ณ runtime เมื่อจุด injection นั้นถูกเรียกใช้งาน
Benchmark ประสิทธิภาพ: เวลา Build vs ต้นทุน Runtime
คำถามเรื่องประสิทธิภาพเกิดขึ้นเกือบทุกครั้งในการสัมภาษณ์งาน Android ตัวเลขที่เป็นรูปธรรมมีความหมายมากกว่าความคิดเห็น
| ตัวชี้วัด | Hilt 2.57 (KSP) | Koin 4.2 | |-----------|-----------------|----------| | Overhead จาก clean build | +8-15 วินาที (KSP code generation) | ~0 วินาที (ไม่มี code generation) | | Overhead จาก incremental build | +2-4 วินาที | ~0 วินาที | | เริ่มต้นแอป (50 binding) | ~0 ms (โค้ดถูกสร้างไว้ล่วงหน้า) | ~5-15 ms (resolve graph) | | เริ่มต้นแอป (500 binding) | ~0 ms | ~30-80 ms | | ผลกระทบต่อขนาด APK | +200-400 KB (generated code) | +100 KB (runtime library) | | การตรวจจับ binding ที่ขาด | Error ตอนคอมไพล์ | Crash ตอน runtime |
สำหรับโปรเจกต์ที่มี binding น้อยกว่า 100 รายการ overhead ของ Koin ณ runtime แทบจะสังเกตไม่ได้ เมื่อเกิน 300 binding ต้นทุนการเริ่มต้นจะเริ่มวัดได้ แม้ว่า lazy modules ใน Koin 4.2 จะช่วยบรรเทาปัญหานี้ด้วยการโหลด module แบบขนาน
การเปลี่ยนจาก KAPT เป็น KSP สำหรับการประมวลผล Hilt ช่วยลดเวลา annotation processing ได้ 40-60% โปรเจกต์ใดก็ตามที่ยังใช้ KAPT กับ Hilt ควรย้ายไปใช้ KSP ทันที Hilt รองรับ KSP ตั้งแต่เวอร์ชัน 2.48
กลยุทธ์การทดสอบสำหรับแต่ละ Framework
ความสามารถในการทดสอบคือจุดที่ความแตกต่างทางสถาปัตยกรรมแสดงผลลัพธ์จริง Hilt มี @TestInstallIn สำหรับสลับ module production ด้วย test double ณ compile-time ส่วน Koin มี loadKoinModules() สำหรับ override definition ณ runtime
// Module ทดสอบ Hilt — แทนที่ binding ของ AppModule ในการทดสอบ
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [AppModule::class]
)
object FakeAppModule {
@Provides
@Singleton
fun provideUserRepository(): UserRepository {
return FakeUserRepository() // Test double ในหน่วยความจำ
}
}// การทดสอบ Koin — override ณ runtime
class UserViewModelTest : KoinTest {
@Before
fun setUp() {
startKoin {
modules(
module {
single<UserRepository> { FakeUserRepository() }
viewModel { UserViewModel(get()) }
}
)
}
}
@Test
fun `loads users from repository`() {
val viewModel: UserViewModel = get()
viewModel.loadUsers()
assertEquals(3, viewModel.users.value.size)
}
@After
fun tearDown() {
stopKoin()
}
}การทดสอบด้วย Hilt จะตรวจจับการกำหนดค่า test module ที่ผิดพลาดได้ตั้งแต่ตอนคอมไพล์ ส่วนการทดสอบด้วย Koin ต้องการการจัดการ lifecycle อย่างรอบคอบ (startKoin/stopKoin) แต่ให้ความยืดหยุ่นมากกว่าสำหรับการ override module บางส่วน
พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
สถาปัตยกรรม Multi-Module: การขยาย DI ข้ามฟีเจอร์
โปรเจกต์ Android ขนาดใหญ่แบ่งฟีเจอร์ออกเป็น Gradle module ต่าง ๆ Framework DI ต้องรองรับขอบเขตระหว่าง module อย่างชัดเจน
สำหรับ Hilt แต่ละ feature module จะประกาศ @Module ของตัวเองพร้อม annotation @InstallIn จากนั้น Hilt จะรวม module ทั้งหมดเข้าเป็นลำดับชั้น component เดียวในขั้นตอนคอมไพล์ สถาปัตยกรรม MVVM ทำงานร่วมกับ scoped component ของ Hilt ได้อย่างเป็นธรรมชาติ
@Module
@InstallIn(ViewModelComponent::class)
object PaymentModule {
@Provides
fun providePaymentGateway(
retrofit: Retrofit // จัดเตรียมโดย module :app
): PaymentGateway {
return StripePaymentGateway(retrofit.create(PaymentApi::class.java))
}
}สำหรับ Koin แต่ละ feature module จะ export การประกาศ module {} ของตัวเอง และ app module จะโหลดทั้งหมดตอนเริ่มต้น Lazy modules ใน Koin 4.2 ช่วยให้โหลด feature module แบบ lazy ได้จนกว่าจะมีการเข้าถึงครั้งแรก
val paymentModule = module {
factory<PaymentGateway> {
StripePaymentGateway(get<Retrofit>().create(PaymentApi::class.java))
}
}
// app/MyApplication.kt — โหลดทุก feature module
startKoin {
modules(appModule, paymentModule, analyticsModule)
}Hilt บังคับการมองเห็น dependency ผ่าน component scope ในขณะที่ Koin อาศัยข้อตกลงร่วมกัน — module ใดก็ตามสามารถเข้าถึง definition ของ module อื่นได้ ซึ่งให้ความยืดหยุ่นแต่อาจนำไปสู่ coupling แบบแฝง
ข้อพิจารณาเรื่อง Kotlin Multiplatform
Koin รองรับ Kotlin Multiplatform (KMP) แบบ native DSL module {} เดียวกันใช้งานได้บน Android, iOS, Desktop และ Web target ส่วน Hilt รองรับเฉพาะ Android เท่านั้น เนื่องจากพึ่งพา component lifecycle เฉพาะ Android และ annotation processor ของ Dagger
สำหรับโปรเจกต์ที่ต้องการรองรับหลายแพลตฟอร์ม Koin เป็นตัวเลือกเดียวที่เป็นไปได้ระหว่างสองตัวนี้ Module business logic ที่ใช้ร่วมกันสามารถประกาศ dependency ครั้งเดียวแล้ว inject ข้ามทุก target ได้
คำถามสัมภาษณ์ที่พบบ่อยเกี่ยวกับ DI บน Android
คำถามเหล่านี้ปรากฏอย่างสม่ำเสมอในการสัมภาษณ์งานเทคนิค Android แต่ละคำตอบจะกระชับและมุ่งเน้นสิ่งที่ผู้สัมภาษณ์คาดหวัง
ถ: ความแตกต่างระหว่าง dependency injection แบบ compile-time และ runtime คืออะไร?
Compile-time DI (Hilt/Dagger) สร้างโค้ด injection ในระหว่างขั้นตอน build คอมไพเลอร์จะตรวจสอบ dependency graph ทั้งหมดเพื่อจับ binding ที่ขาดก่อน runtime ส่วน Runtime DI (Koin) จะ resolve dependency เมื่อถูกร้องขอครั้งแรก โดยใช้รูปแบบ service registry Compile-time DI ให้การเริ่มต้นแอปที่เร็วกว่าแต่ build ช้ากว่า ขณะที่ runtime DI ไม่มี overhead ในการ build แต่เลื่อนการตรวจจับข้อผิดพลาดออกไป
ถ: Hilt มี component scope อะไรบ้าง และ map กับ lifecycle ของ Android อย่างไร?
SingletonComponent มีอยู่ตลอดอายุการใช้งานแอป ActivityRetainedComponent อยู่รอดผ่านการเปลี่ยนแปลง configuration ViewModelComponent มี scope ตาม lifecycle ของ ViewModel ส่วน ActivityComponent, FragmentComponent และ ViewComponent จะตามวงจรชีวิตของ Android lifecycle owner ที่เกี่ยวข้อง สามารถสร้าง custom scope เพื่อขยายลำดับชั้นนี้ได้
ถ: Koin จัดการการ inject ViewModel ใน Jetpack Compose อย่างไร?
Koin มี koinViewModel() เป็นฟังก์ชัน Composable ที่สร้างหรือดึง ViewModel ที่มี scope ตาม ViewModelStoreOwner ที่ใกล้ที่สุด ตั้งแต่ Koin 4.2 เป็นต้นมา koinNavViewModel() จะ scope ViewModel ตาม entry ใน navigation graph เมื่อใช้การนำทาง Jetpack Compose
ถ: เมื่อไหร่ที่ Koin จะเป็นตัวเลือกที่ดีกว่า Hilt?
Koin เหมาะสมกว่าสำหรับโปรเจกต์ KMP (Hilt รองรับเฉพาะ Android) แอปขนาดเล็กถึงกลางที่เวลา build สำคัญกว่าเวลาเริ่มต้น และทีมที่ชอบ Kotlin DSL แบบชัดเจนมากกว่าการกำหนดค่าด้วย annotation โปรเจกต์ prototyping และ proof-of-concept ก็ได้ประโยชน์จากการตั้งค่า Koin ที่น้อยมาก
การเรียก Koin ว่า "dependency injection framework" นั้นไม่ถูกต้องในทางเทคนิค Koin เป็น service locator — dependency ถูกดึงมาผ่าน get() แทนที่จะถูกส่งเข้ามาผ่าน constructor injection ผู้สัมภาษณ์ที่เข้าใจความแตกต่างนี้จะคาดหวังให้ผู้สมัครยอมรับจุดนี้ Hilt/Dagger ทำ dependency injection อย่างแท้จริงผ่าน constructor call ที่ถูกสร้างขึ้นอัตโนมัติ
กรอบการตัดสินใจ: เลือกระหว่าง Hilt และ Koin
| ปัจจัย | Hilt | Koin |
|--------|------|------|
| การตรวจสอบ graph | Compile-time | Runtime |
| ผลกระทบต่อเวลา build | สูงกว่า (KSP codegen) | ไม่มี |
| ต้นทุนการเริ่มต้น | เกือบเป็นศูนย์ | เพิ่มตามขนาด graph |
| ความยากในการเรียนรู้ | สูงกว่า (แนวคิด Dagger) | ต่ำ (Kotlin DSL ล้วน) |
| รองรับ KMP | ไม่ | ใช่ |
| การรวมกับ Jetpack | ลึก (ทางการจาก Google) | ดี (ชุมชน) |
| ความสามารถในการขยายทีม | แข็งแกร่งกว่า (scope ถูกบังคับ) | ยืดหยุ่น (อิงตามข้อตกลง) |
| การทดสอบ | @TestInstallIn (compile-safe) | loadKoinModules (runtime) |
ไม่มี framework ใดที่เหนือกว่าในทุกกรณี การเลือกขึ้นอยู่กับขนาดโปรเจกต์ ประสบการณ์ของทีม และแพลตฟอร์มเป้าหมาย
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
สรุป
- Hilt 2.57 พร้อม KSP มอบการตรวจสอบ dependency graph ณ compile-time และต้นทุนการเริ่มต้นเกือบเป็นศูนย์ เป็นตัวเลือกที่แข็งแกร่งกว่าสำหรับโปรเจกต์ Android แพลตฟอร์มเดียวขนาดใหญ่ที่ต้องการความน่าเชื่อถือสูง
- Koin 4.2 พร้อม lazy modules และ
CoreResolverV2ลดช่องว่างด้านประสิทธิภาพการเริ่มต้น พร้อมรักษาความไม่มี overhead ในการ build และความเข้ากันได้กับ KMP อย่างเต็มที่ - ทั้งสอง framework รองรับสถาปัตยกรรม multi-module แต่ Hilt บังคับขอบเขต scope ณ compile-time ขณะที่ Koin อาศัยข้อตกลงร่วมกันของทีม
- สำหรับการเตรียมตัวสัมภาษณ์งาน Android การเข้าใจ trade-off ระหว่าง compile-time กับ runtime และความสามารถในการอธิบายว่าแต่ละ framework เหมาะสมเมื่อไหร่ถือเป็นพื้นฐานที่คาดหวัง
- การย้ายระหว่าง framework ไม่ใช่เรื่องง่าย การเลือกครั้งแรกควรคำนึงถึงแนวทางแพลตฟอร์มและการขยายตัวในระยะยาวของโปรเจกต์
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Kotlin 2.3 สำหรับ Android: Name-Based Destructuring, KMP และคำถามสัมภาษณ์ 2026
คำถามสัมภาษณ์ Kotlin 2.3 สำหรับนักพัฒนา Android ในปี 2026 ครอบคลุม name-based destructuring, KMP, context parameters, Flow และ coroutines พร้อมตัวอย่างโค้ด

Jetpack Compose: แอนิเมชันขั้นสูงทีละขั้นตอน
คู่มือฉบับสมบูรณ์เกี่ยวกับแอนิเมชันขั้นสูงใน Compose: ทรานซิชัน AnimatedVisibility, Animatable, ท่าทาง และประสิทธิภาพสำหรับอินเทอร์เฟซ Android ที่ลื่นไหล

20 คำถามสัมภาษณ์ Jetpack Compose ยอดนิยมประจำปี 2026
20 คำถามสัมภาษณ์ Jetpack Compose ที่พบบ่อยที่สุด: recomposition, การจัดการ state, navigation, ประสิทธิภาพ และ pattern สถาปัตยกรรม พร้อมตัวอย่างโค้ดละเอียด