4. 定义自定义 Hilt 作用域:@BleConnectionScoped 注解创建与绑定
BLE 连接的典型生命周期是:设备扫描 → 建立连接 → 服务发现 → 数据读写 → 断开连接。Hilt 标准作用域(如 @ActivityScoped、@FragmentScoped)无法精确映射到这个生命周期——Activity 可能会在连接保持期间重建,Fragment 可能被替换但 BLE 连接仍然存活。为此,你需要定义一个自定义 Hilt 作用域 @BleConnectionScoped,让同一连接期间获取的全部依赖实例共享同一个对象。
原理:Hilt 自定义作用域的契约
Hilt 的自定义作用域本质是一个带有 @Scope 元注解的注解,与一个用 @DefineComponent 绑定的自定义组件配对。当组件被创建时,Hilt 会为该作用域缓存所有绑定;当组件被销毁时,缓存一并清除。对于 BLE 场景,你需要在连接建立时创建组件,在断开时销毁组件。
| 概念 | 作用 | 与 BLE 关系 |
|---|---|---|
@Scope | 标记为作用域注解 | 声明生命周期边界 |
@DefineComponent | 定义自定义组件 | BLE 连接组件容器 |
@EntryPoint | 暴露非 Hilt 类的依赖入口 | 让未用 Hilt 注入的类也能获取依赖 |
实现流程:注解 → 组件 → 绑定
- 创建作用域注解
在annotation包下定义@BleConnectionScoped。 - 定义自定义组件
使用@DefineComponent和@EntryPoint创建BleConnectionComponent。 - 将作用域与组件关联
在组件上加@BleConnectionScoped注解。 - 配置安装位置
通过@InstallIn将提供依赖的模块绑定到该组件。 - 管理组件生命周期
在 BLE 连接管理器中显式创建和销毁组件。
代码实现
4.1 定义 @BleConnectionScoped
// annotation/BleConnectionScoped.kt
import javax.inject.Scope
@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class BleConnectionScoped
注解保留期必须设置为 RUNTIME,因为 Hilt 在运行时读取注解来判断是否复用实例。
4.2 定义 BleConnectionComponent
// di/component/BleConnectionComponent.kt
import dagger.hilt.DefineComponent
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import com.example.ble.connection.BleConnectionManager
@BleConnectionScoped
@DefineComponent
@EntryPoint
@InstallIn(SingletonComponent::class)
interface BleConnectionComponent {
fun inject(manager: BleConnectionManager)
}
@DefineComponent:声明这是一个 Hilt 自定义组件,Hilt 会生成对应的组件实现类。@EntryPoint:使该组件可以被非 Hilt 容器管理的对象(如普通 Android Service 或 Application 中手动创建的 BLE 连接对象)访问。@InstallIn(SingletonComponent::class):将自定义组件安装到SingletonComponent下,使其能访问全局单例依赖。
4.3 将模块绑定到 BleConnectionComponent
// di/module/BleConnectionModule.kt
import dagger.Module
import dagger.Provides
import com.example.ble.BleDevice
import com.example.ble.BleGattService
@Module
@InstallIn(BleConnectionComponent::class)
object BleConnectionModule {
@BleConnectionScoped
@Provides
fun provideBleDevice(): BleDevice {
return BleDevice() // 实际应通过连接建立时传入
}
@BleConnectionScoped
@Provides
fun provideBleGattService(device: BleDevice): BleGattService {
return device.getGattService()
}
}
使用 @BleConnectionScoped 标注提供方法,Hilt 会在同一 BleConnectionComponent 生命周期内缓存这些实例。
4.4 在连接管理器中创建/销毁组件
// connection/BleConnectionManager.kt
import dagger.hilt.EntryPoints
import com.example.di.component.BleConnectionComponent
import com.example.di.component.DaggerBleConnectionComponent
class BleConnectionManager {
private var connectionComponent: BleConnectionComponent? = null
fun onConnected() {
connectionComponent = DaggerBleConnectionComponent.builder()
.build()
// 注入依赖到本管理器
connectionComponent?.inject(this)
}
fun onDisconnected() {
connectionComponent = null // 销毁组件,释放缓存
}
// 需要获取 BleDevice 时通过 component 访问
fun getBleDevice(): BleDevice {
return EntryPoints.get(connectionComponent!!, BleConnectionComponent::class.java)
.let { /* 通过 EntryPoint 暴露具体方法 */ }
}
}
⚠️ 注意:只有在调用
DaggerBleConnectionComponent.builder().build() 后,BleConnectionComponent 才会生效。务必在连接建立时构建,断开时置为 null,否则可能造成内存泄漏。
内联 SVG 框架图:@BleConnectionScoped 作用域与组件绑定关系
调试与验证
- 检查组件是否被编译
在app/build/generated/source/目录下搜索DaggerBleConnectionComponent,确保 Hilt 正确生成了实现类。 - 日志验证实例复用
在@Provides方法内打印实例哈希值,确认同一连接期间重复调用EntryPoints.get()返回同一对象。
// 临时调试代码
@BleConnectionScoped
@Provides
fun provideBleDevice(): BleDevice {
val device = BleDevice()
Log.d("BleScope", "BleDevice created: ${System.identityHashCode(device)}")
return device
}
常见错误与解决方案
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
java.lang.IllegalStateException: Hilt component not created | 未在连接建立时调用 DaggerBleConnectionComponent.builder().build() | 确保 onConnected() 中构建组件 |
| 作用域失效,每次获取都是新实例 | @Provides 方法上漏标 @BleConnectionScoped | 为所有需要共享的绑定添加作用域注解 |
| 注入到非 Hilt 类的依赖为 null | 未通过 @EntryPoint 暴露接口或未正确调用 EntryPoints.get() | 在 BleConnectionComponent 中声明 @EntryPoint 方法 |
| 构建时报错:“cannot be found” | @InstallIn 的目标组件不存在或包名错误 | 确认 BleConnectionComponent 已正确注解,检查 import |
性能与稳定性优化
- 延迟初始化:
BleConnectionComponent仅在连接建立时构建,避免在 Application 启动时提前实例化。 - 作用域隔离:不同 BLE 连接创建不同的
BleConnectionComponent实例,防止状态污染。 - 内存泄漏防范:在
onDisconnected()中及时清理组件引用,Android Profiler 可检查对应对象是否被回收。 - 线程安全:若 BLE 连接状态变化发生在不同线程,对
connectionComponent的读写需加synchronized或使用AtomicReference。
⚡ 关键点回顾:自定义 Hilt 作用域
@BleConnectionScoped 通过 @DefineComponent 组件实现生命周期绑定。开发的核心是确保组件的创建/销毁时机与 BLE 连接状态一一对应,并在 @Provides 方法上显式标注作用域,否则作用域退化为无状态工厂。