Hilt 组件作用域基础:@Singleton、@ActivityScoped、@FragmentScoped、@ViewModelScoped
Hilt 的作用域注解用于限定依赖对象的生命周期,让对象与特定的 Android 组件(Application、Activity、Fragment、ViewModel)保持绑定。正确使用作用域可以控制对象创建次数、避免内存泄漏,并让依赖图与组件树对齐。
原理:Hilt 组件层次与作用域
Hilt 按 Android 组件层级创建了对应的 DI 组件:SingletonComponent、ActivityComponent、FragmentComponent、ViewModelComponent。每个组件都有明确的父级关系和生命周期。作用域注解将绑定限制在所属组件内部,子组件可以访问父组件提供的依赖,但父组件无法访问子组件的绑定。
| 作用域注解 | 对应 Hilt 组件 | 生命周期 | 典型使用场景 |
|---|---|---|---|
@Singleton | SingletonComponent | Application 运行期间 | 全局蓝牙适配器、扫描器 |
@ActivityScoped | ActivityComponent | Activity 创建到销毁 | 蓝牙连接会话、回调监听器 |
@FragmentScoped | FragmentComponent | Fragment 创建到销毁 | GATT 回调、UI 数据聚合器 |
@ViewModelScoped | ViewModelComponent | ViewModel 存活期 | BLE 扫描状态、设备列表 |
实现流程:定义绑定并指定作用域
- 在
@Module中使用@Provides或@Binds定义依赖,并用作用域注解标记方法。 - 通过
@InstallIn将模块安装到对应组件中(作用域必须与组件匹配)。 - 在目标
@AndroidEntryPoint类中使用@Inject字段或构造注入。 - 构建项目,Hilt 生成对应组件类,作用域生效。
代码示例
Singleton 作用域(AppModule)
@Module
@InstallIn(SingletonComponent.class)
public class AppModule {
@Provides
@Singleton
public BluetoothManager provideBluetoothManager(@ApplicationContext Context context) {
return (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
}
}
Activity 作用域(ActivityModule)
@Module
@InstallIn(ActivityComponent.class)
public class ActivityModule {
@Provides
@ActivityScoped
public BleScanner provideBleScanner(BluetoothLeScanner scanner) {
return new BleScanner(scanner);
}
}
Fragment 作用域(FragmentModule)
@Module
@InstallIn(FragmentComponent.class)
public class FragmentModule {
@Provides
@FragmentScoped
public GattCallback provideGattCallback() {
return new GattCallback();
}
}
ViewModel 作用域(ViewModel 中使用)
@HiltViewModel
public class BleViewModel extends ViewModel {
private final BleScanner scanner;
@Inject
public BleViewModel(BleScanner scanner) {
this.scanner = scanner;
}
// ViewModel 本身已经是 ViewModelComponent 的子组件
// 这里注入的 scanner 如果标记了 @ViewModelScoped,则与 ViewModel 同生命周期
}
关键规则
- 作用域注解必须与
@InstallIn的目标组件一致:@Singleton→SingletonComponent;@ActivityScoped→ActivityComponent等。 - 在子组件(如 Fragment)中只能注入父组件(如 Activity)中已标记作用域的绑定,不能反向。
@ViewModelScoped只能用于 Hilt ViewModel 内部,且需配合@HiltViewModel。
常见错误与调试
错误:作用域与组件不匹配
// 错误示例:@Singleton 绑定安装在 ActivityComponent
@Module
@InstallIn(ActivityComponent.class)
public class WrongModule {
@Provides
@Singleton // 编译错误:作用域不匹配
public MyService provideService() { ... }
}
// 报错信息:@Singleton 无法应用于 ActivityComponent
调试方法:
- 查看 Hilt 在
build/generated/source/下生成的Dagger*Component类,确认作用域绑定是否正确生成。 - 如果编译期报错 “cannot be provided without an @Provides-annotated method” 且确认方法存在,检查作用域是否矛盾。
- 使用
@Qualifier区分同一类型的不同作用域实例。
性能与稳定性优化
最佳实践
- @Singleton:用于重量级对象(如
BluetoothManager、BluetoothLeScanner),避免重复创建;注意不要持有 Activity Context 导致泄漏。 - @ActivityScoped:适合整个 Activity 内唯一的实例(如 BLE 连接管理器),Activity 销毁时自动释放。
- @FragmentScoped:用于 Fragment 内临时状态,如扫描列表适配器。只在 Fragment 存活期间存在,减少内存占用。
- @ViewModelScoped:与 ViewModel 绑定,即使旋转屏幕也不会重新创建,适合保存扫描结果、设备缓存。
- 在 BLE 场景中,建议将蓝牙适配器作为
@Singleton提供,将BluetoothGatt连接会话以@ActivityScoped管理,避免多 Activity 共享 GATT 对象引发状态混乱。