You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Hilt提供PagingSource遭遇复用异常的原因求助

为什么未加@Singleton的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:45:27