Repository 接口实现类:UserRepositoryImpl
4.1 原理与设计
在 Hilt 驱动的 Repository 层中,接口 UserRepository 定义了数据访问契约,而实现类 UserRepositoryImpl 则承担真正的业务逻辑。核心设计原则是:实现类不直接创建数据源实例,而是通过 Hilt 的依赖注入框架自动获取。
- 构造函数注入(Constructor Injection):Hilt 推荐使用
@Inject注解标记构造器,框架会自动提供构造函数所需的所有参数。 - 数据源抽象:
UserRepositoryImpl依赖UserLocalDataSource和UserRemoteDataSource两个接口(或抽象类),具体实现由 Hilt 模块提供。 - 单一职责:Repository 只负责协调本地与远程数据源,不关心数据源的创建和生命周期管理。
关键收益: 通过注入方式,Repository 实现类可脱离具体数据源实现进行单元测试。只需 mock 数据源接口即可验证 Repository 逻辑。
4.2 实现流程
- 定义 Repository 接口(已在第3章完成):
UserRepository包含getUser(id: String)等方法。 - 创建实现类
UserRepositoryImpl,使用@Inject注解构造函数。 - 在构造函数参数中声明数据源依赖:Hilt 将自动从 DI 容器中查找并注入
UserLocalDataSource和UserRemoteDataSource的实例。 - 实现接口方法:通常采用“先尝试本地,再回退远程”的策略(Cache-First)。
- 添加
@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 容器获取数据源依赖:
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 形式处理。