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 @ViewModelScoped ViewModelComponent 子组件可注入父组件绑定 父组件不能访问子组件绑定 作用域不一致会导致编译错误
作用域注解对应 Hilt 组件生命周期典型使用场景
@SingletonSingletonComponentApplication 运行期间全局蓝牙适配器、扫描器
@ActivityScopedActivityComponentActivity 创建到销毁蓝牙连接会话、回调监听器
@FragmentScopedFragmentComponentFragment 创建到销毁GATT 回调、UI 数据聚合器
@ViewModelScopedViewModelComponentViewModel 存活期BLE 扫描状态、设备列表

实现流程:定义绑定并指定作用域

  1. 在 @Module 中使用 @Provides 或 @Binds 定义依赖,并用作用域注解标记方法。
  2. 通过 @InstallIn 将模块安装到对应组件中(作用域必须与组件匹配)。
  3. 在目标 @AndroidEntryPoint 类中使用 @Inject 字段或构造注入。
  4. 构建项目,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 对象引发状态混乱。