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

Android Kotlin:PagingSource中invalidate为final无法重写问题求助

解决PagingSource中invalidate方法重写冲突问题

我刚接触Android开发,参考GitHub上一个用Retrofit调用Deezer API的项目,在自己项目里实现时,编写PagingSource类遇到了问题:重写invalidate方法时提示‘PagingSource中的invalidate是final方法,无法重写’;去掉override修饰符后又提示‘invalidate隐藏了父类PagingSource的成员,需要override修饰符’。

我的PagingSource类代码

package com.example.myapplication.data.remote.paging

import androidx.paging.PagingSource
import com.example.myapplication.data.models.ResponseData
import com.example.myapplication.data.models.album.Album
import com.example.myapplication.data.models.artist.Artist
import com.example.myapplication.data.models.tracks.Track
import com.example.myapplication.data.remote.paging.PagingDataSource
import com.example.myapplication.data.services.ServiceType
import retrofit2.HttpException
import java.io.IOException

abstract class PagingRepository(
    private val query: String,
    private val serviceType: ServiceType,
    private val pagingDataSource: PagingDataSource
) : PagingSource<Int, ResponseData>() {

    companion object {
        private val inMemoryCache =
            mutableListOf<ResponseData>() // (*) we don't care about the object type
        private val queryCache = mutableListOf<String>()
    }
    
    private val startPage = 0
    private val limit = 25
    
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ResponseData> {
        val position = params.key ?: startPage
        val apiQuery = query
        return try {
    
            val oldQuery = queryCache.find {
                apiQuery.equals(it, true)
            }
    
            if (oldQuery != null && position == startPage) {
                val validatedResults = resultsValidatedAndSorted(apiQuery)
                LoadResult.Page(
                    data = validatedResults,
                    prevKey = null,
                    nextKey = null
                )
            } else {
                val response = when (serviceType) {
                    ServiceType.Artists -> pagingDataSource.fetchArtist(apiQuery, position * limit, limit)
                    ServiceType.Albums -> pagingDataSource.fetchAlbum(apiQuery, position * limit, limit)
                    ServiceType.Tracks -> pagingDataSource.fetchTrack(apiQuery, position * limit, limit)
                }
                val body = response.body()!!
                val results = response.body()!!.data
    
                queryCache.add(apiQuery)
    
                val shelled = results.map { ResponseData(query = apiQuery, data = it) }
    
                inMemoryCache.addAll(
                    shelled
                )
    
                LoadResult.Page(
                    data = if (position * limit <= body.total) shelled else emptyList(),
                    prevKey = if (position == startPage) null else position - 1,
                    nextKey = if (shelled.isEmpty() || position * limit > body.total) null else position + 1
                )
            }
    
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            return LoadResult.Error(exception)
        }
    }
    
    private fun resultsValidatedAndSorted(query: String): List<ResponseData> {
        return inMemoryCache.filter {
            it.query.contains(query, true)
        }
    }
    
    override fun invalidate()
    {
        super.invalidate()
        inMemoryCache.clear()
        queryCache.clear()
    }

}

错误提示 : 'invalidate' in 'PagingSource' is final and cannot be overridden

我的gradle配置文件

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.google.gms.google-services'
    id 'kotlin-kapt'
    id 'kotlin-android'

    id 'com.google.dagger.hilt.android'
    id 'androidx.navigation.safeargs.kotlin'
    id 'kotlin-parcelize'

}

android {
    namespace 'com.example.myapplication'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 33
        targetSdk 33
        versionCode 1
        versionName "1.0"
       // buildToolsVersion "30.0.2"
        def secureProps = new Properties()
        if (file("../secure.properties").exists()) {
            file("../secure.properties")?.withInputStream { secureProps.load(it) }
        }
        vectorDrawables.useSupportLibrary = true
        buildConfigField "String", "BASE_URL", (secureProps.getProperty("BASE_URL") ?: "")
        buildConfigField "String", "SECRET", (secureProps.getProperty("SECRET") ?: "")
    
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"] // opt in for experimental functions
        jvmTarget = '1.8'
    }
    buildFeatures {
        viewBinding = true
        dataBinding = true
    }

}

dependencies {
    implementation 'androidx.work:work-runtime-ktx:2.8.1'
    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'com.google.firebase:firebase-firestore-ktx:24.4.5'
    implementation platform('com.google.firebase:firebase-bom:31.3.0')
    implementation 'com.google.firebase:firebase-analytics-ktx:21.2.1'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    //paging
    implementation 'androidx.paging:paging-common-ktx:3.1.1'
    implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha10'
    //navigation
    def nav_version ="2.5.3"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    //coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
    // - - ViewModel
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    //glide
    implementation 'com.github.bumptech.glide:glide:4.15.1'
    kapt 'com.github.bumptech.glide:compiler:4.15.1'
    //bottomNavigation
    implementation 'com.google.android.material:material:1.8.0'
    //splashscreen
    implementation 'androidx.core:core-splashscreen:1.0.0'
    // -- retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
    // Lifecycle
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
    //implementation "androidx.lifecycle:lifecycle-runtime:2.6.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
    
    // HILT
    def hilt_version = "2.45"
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-compiler:$hilt_version"
    
    "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    // Timber
    implementation 'com.jakewharton.timber:timber:5.0.1'
    // ExoPlayer
    api "com.google.android.exoplayer:exoplayer-core:2.18.5"
    api "com.google.android.exoplayer:exoplayer-ui:2.18.5"
    api "com.google.android.exoplayer:extension-mediasession:2.18.5"
    
    // Firebase
    implementation 'com.google.firebase:firebase-firestore:24.4.5'
    
    // Firebase Storage KTX
    implementation 'com.google.firebase:firebase-storage-ktx:20.1.0'
    
    // Firebase Coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4'
    
    
    
    implementation 'androidx.media:media:1.6.0'
    
    implementation 'com.google.firebase:firebase-analytics-ktx:21.2.1'
    // Coil Image Loading Library
    implementation('io.coil-kt:coil:1.1.0')
    // Subtitle supported collapsing toolbar
    implementation 'com.hendraanggrian.material:collapsingtoolbarlayout-subtitle:1.1.0'
    
    // Flow Binding
    implementation 'io.github.reactivecircus.flowbinding:flowbinding-android:0.10.2'
    
    implementation('androidx.preference:preference-ktx:1.1.1')

}
kapt {
    correctErrorTypes = true
}

问题解决方法

1. 统一Paging依赖版本

你的Paging依赖版本不一致:paging-common-ktx用了3.1.1,而paging-runtime-ktx用了3.0.0-alpha10,这会导致API不兼容。在Paging 3的正式版本中,invalidate()已被标记为final方法,无法重写;旧alpha版本中该方法是非final的,所以参考的旧项目可以重写,但新版本不允许。

修改gradle中的Paging依赖,保持版本一致:

//paging
implementation 'androidx.paging:paging-common-ktx:3.1.1'
implementation 'androidx.paging:paging-runtime-ktx:3.1.1'

2. 替换重写invalidate的实现

移除重写的invalidate()方法,改用registerInvalidatedCallback监听失效事件,在回调中清除缓存:

abstract class PagingRepository(
    private val query: String,
    private val serviceType: ServiceType,
    private val pagingDataSource: PagingDataSource
) : PagingSource<Int, ResponseData>() {

    companion object {
        private val inMemoryCache = mutableListOf<ResponseData>()
        private val queryCache = mutableListOf<String>()
    }

    init {
        // 注册失效回调,在PagingSource失效时清除缓存
        registerInvalidatedCallback {
            inMemoryCache.clear()
            queryCache.clear()
        }
    }

    // ... 其他原有代码保持不变 ...

    // 移除原来的override invalidate方法
}

3. 额外优化建议

  • 伴生对象的静态缓存会在所有PagingRepository实例间共享,可能导致不同查询的数据互相干扰,建议将缓存改为实例变量,或者用查询字符串作为key区分不同缓存。
  • 避免用!!强制解包response.body(),改用空安全处理防止空指针:
    val body = response.body() ?: return LoadResult.Error(NullPointerException("接口返回内容为空"))
    

内容的提问来源于stack exchange,提问作者Vũ Minh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 04:47:18