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 注入的类也能获取依赖

实现流程:注解 → 组件 → 绑定

  1. 创建作用域注解
    在 annotation 包下定义 @BleConnectionScoped。
  2. 定义自定义组件
    使用 @DefineComponent 和 @EntryPoint 创建 BleConnectionComponent。
  3. 将作用域与组件关联
    在组件上加 @BleConnectionScoped 注解。
  4. 配置安装位置
    通过 @InstallIn 将提供依赖的模块绑定到该组件。
  5. 管理组件生命周期
    在 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 作用域与组件绑定关系

@BleConnectionScoped 自定义作用域绑定示意图 @BleConnectionScoped 作用域与组件绑定关系 @BleConnectionScoped BleConnectionComponent @InstallIn SingletonComponent BleConnectionModule @Provides @BleConnectionScoped BleConnectionManager 说明: • @BleConnectionScoped 标记作用域边界。 • BleConnectionComponent 是实际缓存依赖的容器,与连接生命周期一致。 • 模块通过 @InstallIn 指定挂载到该组件,@Provides 方法标注作用域以保证单例。

调试与验证

  1. 检查组件是否被编译
    在 app/build/generated/source/ 目录下搜索 DaggerBleConnectionComponent,确保 Hilt 正确生成了实现类。
  2. 日志验证实例复用
    在 @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 方法上显式标注作用域,否则作用域退化为无状态工厂。