使用Hilt提供PagingSource遭遇复用异常的原因求助
问题场景
我尝试用Dagger-Hilt提供ListRemotePagingSource实例,相关代码如下:
RemoteDatasourceModule.kt
@Module @InstallIn(SingletonComponent::class) object RemoteDataSourceModule { @Provides fun provideListRemotePagingSource( api: ApiService, ): ListRemotePagingSource { return ListRemotePagingSource(api = ApiService) } }
ListRepositoryImpl.kt
class ListRepositoryImpl @Inject constructor( private val remotePagingSource: ListRemotePagingSource ): ListRepository { override fun getListPagingData(): Flow<PagingData<Content>> { return Pager( config = PagingConfig( pageSize = DEFAULT_PAGE_SIZE, enablePlaceholders = false ), pagingSourceFactory = { remotePagingSource } ).flow } companion object { const val DEFAULT_PAGE_SIZE = 20 } }
调用PagingDataAdapter.refresh()时触发异常:
java.lang.IllegalStateException: An instance of PagingSource was re-used when Pager expected to create a new instance. Ensure that the pagingSourceFactory passed to Pager always returns a new instance of PagingSource.
我修改代码为直接在pagingSourceFactory中创建新实例后问题解决,但疑惑:明明没给provideListRemotePagingSource()加@Singleton注解,为什么Hilt还是复用了同一个实例?
原因分析
你的理解确实有误,Hilt的作用域规则不是仅靠@Singleton注解单独决定的,核心在于Module所安装的Component的作用域:
- 当你把
RemoteDataSourceModule安装在SingletonComponent::class中时,这个Component本身就属于@Singleton作用域,生命周期和应用一致。 - Hilt的规则是:如果一个绑定(即
@Provides方法)没有显式指定作用域,但所属的Module安装在一个带作用域的Component里,那么这个绑定会自动继承该Component的作用域。 - 换句话说,你的
provideListRemotePagingSource()虽然没加@Singleton,但因为Module挂靠在SingletonComponent下,所以它提供的ListRemotePagingSource实例默认是单例的——每次注入都会拿到同一个对象。
而Paging 3的Pager有硬性要求:每次调用refresh()时,pagingSourceFactory必须返回全新的PagingSource实例(因为PagingSource内部维护了分页状态,复用会导致状态混乱、数据不一致),这就直接触发了报错。
解决方案
有两种常见的可行方案:
1. 直接在工厂方法中创建新实例(你已采用的方式)
在pagingSourceFactory的lambda中直接初始化ListRemotePagingSource,确保每次触发分页操作都生成新对象:
class ListRepositoryImpl @Inject constructor( private val api: ApiService ): ListRepository { override fun getListPagingData(): Flow<PagingData<Content>> { return Pager( config = PagingConfig( pageSize = DEFAULT_PAGE_SIZE, enablePlaceholders = false ), pagingSourceFactory = { ListRemotePagingSource(api) } ).flow } companion object { const val DEFAULT_PAGE_SIZE = 20 } }
2. 调整Hilt绑定避免单例
如果仍想通过Hilt管理ListRemotePagingSource,可以修改Module的安装Component,比如将其安装在ViewModelComponent或ActivityComponent这类生命周期更短的Component中(这些Component的作用域不是单例),同时确保@Provides方法不添加@Singleton或其他全局作用域注解。这样每次注入时Hilt会生成新的实例:
@Module @InstallIn(ViewModelComponent::class) // 改用ViewModel作用域的Component object RemoteDataSourceModule { @Provides fun provideListRemotePagingSource( api: ApiService, ): ListRemotePagingSource { return ListRemotePagingSource(api = api) } }
不过需要注意:PagingSource的生命周期应该由Pager控制,所以直接在工厂方法中创建实例是更贴合Paging设计的做法。
总结
核心误区是忽略了Hilt中Component作用域对绑定实例的影响——即使@Provides方法没加@Singleton,只要Module安装在SingletonComponent里,实例就会被当作单例缓存。而PagingSource的特性要求每次都创建新实例,所以要么直接在工厂方法中初始化,要么调整Hilt的绑定作用域避免单例。
内容的提问来源于stack exchange,提问作者CodingBruceLee

