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

如何处理Room生成的分页代码抛出的运行时错误?

解决Room Paging 2中FTS搜索语法错误的异常捕获问题

我刚遇到过几乎一模一样的问题!用Paging 2结合Room FTS搜索时,错误的MATCH表达式会在Paging的后台磁盘IO线程抛出异常,不管是在toLiveData()声明处还是observe()外面加try/catch都没用——因为异常根本不在主线程或者LiveData创建的线程抛。

核心问题在于:Paging 2的LimitOffsetDataSource(Room默认返回的DataSource类型)会在后台线程执行count和查询操作,这些操作抛出的异常会直接崩掉线程,而默认没有任何异常拦截机制。我们需要自己包装DataSource,在底层操作里加入异常捕获。

步骤1:自定义异常安全的DataSource包装类

我们需要创建一个LimitOffsetDataSource的子类,把所有可能抛出SQL异常的方法用try/catch包裹,同时提供一个回调来传递捕获到的异常:

import android.database.sqlite.SQLiteException
import androidx.paging.LimitOffsetDataSource
import androidx.paging.DataSource

// 包装LimitOffsetDataSource,添加异常捕获
class SafeLimitOffsetDataSource<T>(
    private val originalDataSource: LimitOffsetDataSource<T>,
    private val errorCallback: (Throwable) -> Unit
) : LimitOffsetDataSource<T>() {

    // 捕获count操作的异常
    override fun countItems(): Int {
        return try {
            originalDataSource.countItems()
        } catch (e: SQLiteException) {
            errorCallback(e)
            0 // 返回0避免Paging继续加载
        }
    }

    // 捕获分页加载的异常
    override fun loadRange(startPosition: Int, loadCount: Int): List<T> {
        return try {
            originalDataSource.loadRange(startPosition, loadCount)
        } catch (e: SQLiteException) {
            errorCallback(e)
            emptyList()
        }
    }

    // 捕获初始加载的异常
    override fun loadInitial(
        params: LoadInitialParams,
        callback: LoadInitialCallback<T>
    ) {
        try {
            originalDataSource.loadInitial(params, callback)
        } catch (e: SQLiteException) {
            errorCallback(e)
            callback.onResult(emptyList(), 0, 0) // 传递空列表终止加载
        }
    }
}

// 包装DataSource.Factory,用来创建上面的SafeLimitOffsetDataSource
class SafeDataSourceFactory<Key, Value>(
    private val originalFactory: DataSource.Factory<Key, Value>,
    private val errorCallback: (Throwable) -> Unit
) : DataSource.Factory<Key, Value>() {
    override fun create(): DataSource<Key, Value> {
        // Room返回的是LimitOffsetDataSource,所以强制转换是安全的
        val originalSource = originalFactory.create() as LimitOffsetDataSource<Value>
        return SafeLimitOffsetDataSource(originalSource, errorCallback)
    }
}

步骤2:在Repository层引入异常处理

修改你的BookRepository,让它返回包装后的DataSource.Factory,并接受一个异常回调:

class BookRepository(private val paragraphDao: ParagraphDao) {
    fun filtered(search: String, errorCallback: (Throwable) -> Unit): DataSource.Factory<Int, String> {
        // 用SafeDataSourceFactory包装DAO返回的原始Factory
        return SafeDataSourceFactory(paragraphDao.filtered(search), errorCallback)
    }
}

步骤3:在ViewModel中接收并传递异常

在SearchViewModel里,添加一个LiveData来传递错误信息,让UI可以观察到异常:

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList

class SearchViewModel(search: String, repo: BookRepository) : ViewModel() {
    val paragraphs = LivePagedListBuilder(
        repo.filtered(search) { error ->
            // 把异常传递到UI线程
            searchError.postValue(error)
        },
        PagedList.Config.Builder()
            .setPageSize(15)
            .setEnablePlaceholders(false) // 可选,避免空数据时的占位符问题
            .build()
    ).build()

    // 用来通知UI搜索错误的LiveData
    val searchError = MutableLiveData<Throwable>()
}

步骤4:在Fragment中观察错误并处理

最后,在你的Fragment里观察searchError LiveData,给用户显示友好提示:

vm.searchError.observe(viewLifecycleOwner) { error ->
    Toast.makeText(
        requireContext(),
        "搜索表达式格式错误:${error.message}",
        Toast.LENGTH_SHORT
    ).show()
    // 可选:清空适配器内容
    adapter.submitList(null)
}

额外说明

  • 如果你担心强制转换LimitOffsetDataSource不安全,可以先判断类型再处理,不过Room针对分页查询返回的确实是这个类型。
  • 如果你能升级到Paging 3,会有更优雅的错误处理方式(比如PagingData.catch()操作符),但如果还在用稳定版的Paging 2,上面的方案完全可行。
  • 也可以提前对用户输入的搜索表达式做简单校验(比如转义FTS的特殊字符,避免无效的语法),但配合异常捕获能覆盖所有边缘情况。

内容的提问来源于stack exchange,提问作者CommonsWare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 22:57:49