Repository 接口实现类:UserRepositoryImpl

4.1 原理与设计

在 Hilt 驱动的 Repository 层中,接口 UserRepository 定义了数据访问契约,而实现类 UserRepositoryImpl 则承担真正的业务逻辑。核心设计原则是:实现类不直接创建数据源实例,而是通过 Hilt 的依赖注入框架自动获取。

  • 构造函数注入(Constructor Injection):Hilt 推荐使用 @Inject 注解标记构造器,框架会自动提供构造函数所需的所有参数。
  • 数据源抽象:UserRepositoryImpl 依赖 UserLocalDataSource 和 UserRemoteDataSource 两个接口(或抽象类),具体实现由 Hilt 模块提供。
  • 单一职责:Repository 只负责协调本地与远程数据源,不关心数据源的创建和生命周期管理。
关键收益: 通过注入方式,Repository 实现类可脱离具体数据源实现进行单元测试。只需 mock 数据源接口即可验证 Repository 逻辑。

4.2 实现流程

  1. 定义 Repository 接口(已在第3章完成):UserRepository 包含 getUser(id: String) 等方法。
  2. 创建实现类UserRepositoryImpl,使用 @Inject 注解构造函数。
  3. 在构造函数参数中声明数据源依赖:Hilt 将自动从 DI 容器中查找并注入 UserLocalDataSource 和 UserRemoteDataSource 的实例。
  4. 实现接口方法:通常采用“先尝试本地,再回退远程”的策略(Cache-First)。
  5. 添加 @Singleton 作用域(可选):如需全局单例,可在类上添加 @Singleton。

4.3 代码实现

以下展示 UserRepositoryImpl 的完整代码:

package com.example.app.data.repository

import com.example.app.data.local.UserLocalDataSource
import com.example.app.data.remote.UserRemoteDataSource
import com.example.app.domain.model.User
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class UserRepositoryImpl @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) : UserRepository {

    override suspend fun getUser(id: String): Result<User> {
        // 优先从本地获取
        val localResult = localDataSource.getUser(id)
        if (localResult.isSuccess) {
            return localResult
        }

        // 本地没有或失败,从远程获取
        val remoteResult = remoteDataSource.getUser(id)
        if (remoteResult.isSuccess) {
            // 同步到本地
            remoteResult.getOrNull()?.let { user ->
                localDataSource.saveUser(user)
            }
        }
        return remoteResult
    }

    override suspend fun saveUser(user: User): Result<Unit> {
        // 同时保存到本地和远程
        val localResult = localDataSource.saveUser(user)
        val remoteResult = remoteDataSource.saveUser(user)
        return if (localResult.isSuccess && remoteResult.isSuccess) {
            Result.success(Unit)
        } else {
            Result.failure(Exception("Failed to save user"))
        }
    }
}
注意: 如果两个数据源返回类型不同(例如本地返回 Flow,远程返回 suspend),需要在 Repository 层做适配转换。本例统一使用 suspend 函数简化演示。

4.4 依赖注入关系图

下面 SVG 展示了 UserRepositoryImpl 如何通过 Hilt 容器获取数据源依赖:

UserRepositoryImpl 依赖注入关系 Hilt DI 容器 @Module / @Provides UserLocalDataSource 实现 提供本地数据源实例(Room / DataStore) @Module / @Provides UserRemoteDataSource 实现 提供远程数据源实例(Retrofit) @Singleton UserRepositoryImpl @Inject constructor(…) 说明: Hilt 自动匹配 @Inject 构造函数中的参数类型, 从 @Module 提供的绑定中选取对应的实现注入。

4.5 调试与验证

编译项目后,Hilt 会生成 UserRepositoryImpl_Factory 工厂类。可通过以下方式验证注入是否成功:

  • 查看生成的 Dagger 组件:检查 build/generated/source/hilt/ 目录下的 UserRepositoryImpl_Factory.java,确认构造函数参数类型正确。
  • 单元测试:使用 @HiltAndroidTest 启动测试。在 @BindValue 字段中提供 Mock 数据源,然后断言 UserRepositoryImpl 行为。
  • 日志输出:在 UserRepositoryImpl 构造函数中添加临时日志,观察应用启动时是否打印。

4.6 常见错误与解决

错误现象原因解决方案
编译错误 Dagger does not know how to provide UserLocalDataSource数据源接口未在 Hilt 模块中提供绑定在 @Module 中为 UserLocalDataSource 添加 @Provides 或 @Binds 方法
运行时 NullPointerException 在 localDataSource 上数据源实现类未标注 @Singleton 或 @Inject 构造函数确认实现类(如 UserLocalDataSourceImpl)带有 @Inject 构造器
Hilt 注解处理器未生成代码未应用 Hilt Gradle 插件或 annotation processor 依赖丢失检查 build.gradle:应用 dagger.hilt.android.plugin,添加 hilt-android-compiler 依赖
Scope 冲突(Singleton vs ViewScoped)UserRepositoryImpl 和某个数据源作用域不一致统一作用域,一般 Repository 使用 @Singleton,数据源也使用 @Singleton

4.7 性能与稳定性优化

  • 延时初始化: 如果数据源初始化开销大(如数据库连接、网络客户端),可在 Repository 中注入 Lazy<UserDataSource> 实现按需创建。
  • 线程安全: 数据源中如有可变状态(如本地缓存),使用 @Reusable 或 @Singleton 确保多线程安全访问。
  • 避免内存泄漏: Repository 一般不持有 Activity/Fragment 引用;只注入上下文时使用 @ApplicationContext。
  • 编译优化: 使用 @Reusable 替代 @Singleton 对不要求全局唯一的依赖,减少不必要的单例生成。
陷阱提示: 不要在 UserRepositoryImpl 构造函数中进行阻塞操作(如文件读写、网络请求)。Hilt 负责构造对象,若构造器耗时过长会导致应用启动延迟。所有耗时操作应在接口方法中以 suspend 或 callback 形式处理。

4.8 小结