1. 依赖注入基础回顾:Dagger / Hilt / Koin 核心概念、组件作用域、依赖图构建
1.1 依赖注入要解决什么问题
依赖注入(Dependency Injection, DI)是一种设计模式,将对象的创建与使用分离。在 BLE 开发中,扫描器、连接管理器、GATT 服务等组件存在复杂的依赖关系,手动构造会带来以下问题:
- 紧耦合:组件之间直接通过
new创建,难以替换测试桩或 mock 对象。 - 生命周期混乱:BLE 组件需要与 Activity / Service 生命周期绑定,手动管理极易泄漏。
- 重复样板代码:每个类都需要手动传入构造参数,代码冗余且容易遗漏。
依赖注入框架通过依赖图自动装配对象,开发者只需声明“我需要什么”和“如何提供”,框架在编译时或运行时完成注入。
1.2 三大框架核心概念对比
| 框架 | 核心机制 | 声明方式 | 生成时期 | 组件作用域 | 适用场景 |
|---|---|---|---|---|---|
| Dagger | 编译时注解 + APT 生成代码 | @Module / @Provides / @Component |
编译时 | 自定义 @Scope 注解 |
大型项目,追求极致性能 |
| Hilt | 基于 Dagger 的 Android 封装 | @HiltViewModel / @InstallIn / @Module |
编译时 | 预定义 @Singleton / @ActivityScoped / @FragmentScoped |
Android 最佳实践 + BLE 组件 |
| Koin | 运行时 DSL + Kotlin 协程 | module { } / single / factory |
运行时 | 作用域通过 DSL 嵌套控制 | 轻量项目、KMM 多平台 |
选择建议: 本课程后续 BLE 示例主要基于 Hilt(Android 官方推荐),同时提供 Koin 的备用实现。Dagger 作为底层原理讲解。
1.3 组件作用域与生命周期
作用域(Scope)决定了依赖实例在容器中的存活时间。错误的 scope 会导致 BLE 连接泄漏或回调失效。
- Singleton(单例):整个应用进程一个实例,适用于蓝牙适配器
BluetoothAdapter。 - ActivityScoped:随 Activity 创建/销毁,适用于扫描回调
LeScanCallback。 - FragmentScoped:随 Fragment 生命周期,适用于设备列表 UI 绑定。
- 自定义 Scoped:例如
@ConnectionScoped用于管理每个 BLE 连接的生命周期。
// Hilt 中声明作用域示例
@Module
@InstallIn(SingletonComponent::class)
object BleModule {
@Provides
@Singleton
fun provideBluetoothAdapter(context: Context): BluetoothAdapter {
return BluetoothAdapter.getDefaultAdapter()
?: throw IllegalStateException("Bluetooth not supported")
}
@Provides
@ActivityScoped
fun provideLeScanner(adapter: BluetoothAdapter): BluetoothLeScanner {
return adapter.bluetoothLeScanner
}
}
1.4 依赖图的构建机制
依赖图是一个有向无环图(DAG)。框架通过以下步骤构造:
- 收集模块:扫描所有
@Module(或 Koin 的module {})中的提供者声明。 - 解析依赖关系:每个提供者的参数作为图的入边,返回值作为出边。
- 检测循环:编译时(Dagger/Hilt)或运行时(Koin)报告循环依赖。
- 实例化顺序:按拓扑序依次创建对象,并注入到目标类。
- 作用域管理:每个 scope 对应一个子图,父图对象可注入子图,反之不行。
BLE 开发注意: 蓝牙适配器一般是全局单例,但
BluetoothGatt 对象需要按连接单独创建,建议使用 @ConnectionScoped 自定义 scope 来管理每个连接的生命周期。
1.5 三种框架的声明式注入写法
以下代码分别展示如何声明一个 BLE 扫描器实例(以 BluetoothLeScanner 为例):
// ---------- Dagger ----------
@Module
class BleModule {
@Provides
@Singleton
fun provideLeScanner(adapter: BluetoothAdapter): BluetoothLeScanner {
return adapter.bluetoothLeScanner
}
}
@Component(modules = [BleModule::class])
@Singleton
interface AppComponent {
fun inject(activity: MainActivity)
}
// ---------- Hilt ----------
@Module
@InstallIn(SingletonComponent::class)
object BleHiltModule {
@Provides
@Singleton
fun provideLeScanner(@ApplicationContext context: Context): BluetoothLeScanner {
val adapter = BluetoothAdapter.getDefaultAdapter()
return adapter.bluetoothLeScanner
}
}
// ---------- Koin ----------
val bleModule = module {
single { BluetoothAdapter.getDefaultAdapter()?.bluetoothLeScanner }
}
// 在 Application 中启动 Koin
startKoin {
modules(bleModule)
}
常见错误 #1:循环依赖 — 例如 BLE 扫描器依赖于连接管理器,而连接管理器又依赖于扫描器。Dagger/Hilt 会在编译时报错:
常见错误 #2:未绑定的提供者 — 如果某个依赖在模块中没有对应的
Dependency cycle detected。解决方法:使用 @Lazy 或拆分为多层模块。常见错误 #2:未绑定的提供者 — 如果某个依赖在模块中没有对应的
@Provides 或 single,框架会抛出 UnsatisfiedDependencyException。务必确保所有依赖都已声明。