何為分頁緩存?
顧名思義,分頁緩存就是邊分頁邊緩存,分頁通常使用下拉刷新控件實現,而緩存通常說的是指磁盤緩存,即保存到數據庫中,數據庫本身也是一個索引文件。
為什么緩存還要分頁?
在很大一部分場景下,緩存都是需要分頁的,不要問我為什么,問就是不懂規矩,哈哈。你家的接口一次性把整張表的數據全部都返回給你嗎?先不說用戶耗多少流量的問題,如果大家都一次性把表的數據都給你,那爬蟲何以生存?分分鐘就把你的數據全都弄走了,要知道,互聯網世界,數據就是資源,數據就是錢啊!
怎么使用下拉刷新分頁?
重復的問題不會再講,看我之前的文章Android低代碼開發 - 直接創建一個下拉刷新列表界面。下拉刷新和上拉加載都是用來分頁加載數據的。下拉刷新往往只加載第一頁的數據,而上拉加載則是加載下一頁的數據,如果有。
下拉刷新怎么和緩存結合起來?
這個問題問的好。本篇提到的緩存一律指數據庫緩存,而非內存緩存。當然我的dcache庫https://github.com/dora4/dcache-android 支持內存緩存,會簡單帶過。我以緩存系統通知列表為例。
package com.dorachat.dorachat.repositoryimport android.content.Context
import com.dorachat.dorachat.common.AppConfig.Companion.PRODUCT_NAME
import com.dorachat.dorachat.http.ApiResult
import com.dorachat.dorachat.http.service.HomeService
import com.dorachat.dorachat.model.SystemNotification
import dora.cache.data.adapter.ListResultAdapter
import dora.cache.data.fetcher.OnLoadStateListener
import dora.cache.factory.DatabaseCacheHolderFactory
import dora.cache.repository.DoraPageDatabaseCacheRepository
import dora.cache.repository.ListRepository
import dora.http.DoraListCallback
import dora.http.retrofit.RetrofitManager.getService
import retrofit2.Callback
import javax.inject.Inject@ListRepository
class SysNotificationRepository @Inject constructor(context: Context) :DoraPageDatabaseCacheRepository<SystemNotification>(context) {override fun onLoadFromNetwork(callback: DoraListCallback<SystemNotification>,listener: OnLoadStateListener?) {getService(HomeService::class.java).getSystemNotificationList(PRODUCT_NAME).enqueue(ListResultAdapter<SystemNotification, ApiResult<SystemNotification>>(callback)as Callback<ApiResult<MutableList<SystemNotification>>>)}override fun createCacheHolderFactory(): DatabaseCacheHolderFactory<SystemNotification> {return DatabaseCacheHolderFactory(SystemNotification::class.java)}
}
緩存倉庫,使用@ListRepository標記緩存的數據結構是列表類型的。使用咱們的DoraPageDatabaseCacheRepository,進行分頁數據的緩存。如果你不使用框架內置的ORM框架,則需要先整合你的ORM框架,具體看我專欄的其他文章。這個類就是為了告訴框架,你要緩存的數據結構是一個List類型的SystemNotification,而且會一頁一頁給到,請幫我緩存好了。我們知道list模式的BaseRepository使用流程是,先在activity設置一個數據監聽器,repository.getListLiveData().observe(this, observer),然后改變一下參數,repository.setXxx(),調用一下repository.fetchListData()。一旦調用抓取數據的函數,就會調用onLoadFromNetwork()方法,加載最新參數的數據,這邊的數據監聽器就會被回調,同時將數據緩存一份到數據庫中,以備無網絡的時候使用。附上DoraPageDatabaseCacheRepository的源碼你就明白了。
package dora.cache.repositoryimport android.content.Context
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import dora.cache.data.fetcher.OnLoadStateListener
import dora.db.builder.Condition
import dora.db.builder.QueryBuilder
import dora.db.table.OrmTableabstract class DoraPageDatabaseCacheRepository<T : OrmTable>(context: Context): DoraDatabaseCacheRepository<T>(context) {private var pageNo: Int = 0private var pageSize: Int = 10fun getPageNo(): Int {return pageNo}fun getPageSize(): Int {return pageSize}fun isLastPage(totalSize: Int) : Boolean {val lastPage = if (totalSize % pageSize == 0) totalSize / pageSize - 1 else totalSize / pageSizereturn lastPage == pageNo}fun observeData(owner: LifecycleOwner, adapter: AdapterDelegate<T>) {getListLiveData().observe(owner) {if (pageNo == 0) {adapter.setList(it)} else {adapter.addData(it)}}}interface AdapterDelegate<T> {fun setList(data: MutableList<T>)fun addData(data: MutableList<T>)}/*** 下拉刷新回調,可結合[setPageSize]使用。*/fun onRefresh(listener: OnLoadStateListener) {pageNo = 0fetchListData(listener = listener)}/*** 下拉刷新高階函數,可結合[setPageSize]使用。*/@JvmOverloadsfun onRefresh(block: ((Boolean) -> Unit)? = null) {pageNo = 0fetchListData(listener = object : OnLoadStateListener {override fun onLoad(state: Int) {block?.invoke(state == OnLoadStateListener.SUCCESS)}})}/*** 上拉加載回調,可結合[setPageSize]使用。*/fun onLoadMore(listener: OnLoadStateListener) {pageNo++fetchListData(listener = listener)}/*** 上拉加載高階函數,可結合[setPageSize]使用。*/@JvmOverloadsfun onLoadMore(block: ((Boolean) -> Unit)? = null) {pageNo++fetchListData(listener = object : OnLoadStateListener {override fun onLoad(state: Int) {block?.invoke(state == OnLoadStateListener.SUCCESS)}})}open fun setPageSize(pageSize: Int): DoraPageDatabaseCacheRepository<T> {this.pageSize = pageSizereturn this}open fun setCurrentPage(pageNo: Int, pageSize: Int): DoraPageDatabaseCacheRepository<T> {this.pageNo = pageNothis.pageSize = pageSizereturn this}override fun query(): Condition {val start = pageNo * pageSizereturn QueryBuilder.create().limit(start, pageSize).toCondition()}/*** 沒網的情況下直接加載緩存數據。*/override fun selectData(ds: DataSource): Boolean {var isLoaded = falseif (!isNetworkAvailable) {isLoaded = ds.loadFromCache(DataSource.CacheType.DATABASE)}return if (isNetworkAvailable) {try {ds.loadFromNetwork()true} catch (e: Exception) {Log.e(TAG, e.toString())isLoaded}} else isLoaded}
}
在這個分頁緩存倉庫類中,定義了分頁的一些參數,如第幾頁?每頁幾條數?onRefresh()和onLoadMore()完全就是為了配合下拉刷新控件而設計的。不會使用dcache庫的,是不是看源碼也能知道怎么調用?observeData()方法是不是就是我們剛才講到的數據監聽器,或者說觀察者。那么就來看一下調用層面的代碼吧。
package com.dorachat.dorachat.ui.activity.adminimport android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import com.dorachat.dorachat.ChatAppimport com.dorachat.dorachat.R
import com.dorachat.dorachat.common.AppConfig.Companion.ACTION_UPDATE_SYS_NOTIFICATION
import com.dorachat.dorachat.common.AppConfig.Companion.PRODUCT_NAME
import com.dorachat.dorachat.common.AppConfig.Companion.REQUEST_CODE_ADD_SYS_NOTIFICATION
import com.dorachat.dorachat.common.AppConfig.Companion.REQUEST_CODE_UPDATE_SYS_NOTIFICATION
import com.dorachat.dorachat.common.IntentKeys.Companion.KEY_SYSTEM_NOTIFICATION
import com.dorachat.dorachat.databinding.ActivitySysNotificationBinding
import com.dorachat.dorachat.di.component.DaggerUserComponent
import com.dorachat.dorachat.http.service.HomeService
import com.dorachat.dorachat.model.SystemNotification
import com.dorachat.dorachat.model.request.ReqSysNotification
import com.dorachat.dorachat.repository.SysNotificationRepository
import com.dorachat.dorachat.ui.adapter.SysNotificationAdapter
import dora.cache.repository.DoraPageDatabaseCacheRepository
import dora.dagger.DaggerBaseActivity
import dora.http.DoraHttp
import dora.http.DoraHttp.net
import dora.http.retrofit.RetrofitManager
import dora.util.IntentUtils
import dora.util.ViewUtils
import dora.widget.DoraAlertDialog
import dora.widget.Tips
import dora.widget.pull.SwipeLayout
import javax.inject.Injectclass SysNotificationActivity : DaggerBaseActivity<ActivitySysNotificationBinding>() {@Inject lateinit var notificationRepository: SysNotificationRepositoryprivate val adapter = SysNotificationAdapter()override fun getLayoutId(): Int {return R.layout.activity_sys_notification}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (resultCode == Activity.RESULT_OK) {if (requestCode == REQUEST_CODE_ADD_SYS_NOTIFICATION ||requestCode == REQUEST_CODE_UPDATE_SYS_NOTIFICATION) {notificationRepository.onRefresh()}}}override fun onInjectDaggerComponent() {DaggerUserComponent.builder().appComponent(ChatApp.appComponent).build().inject(this)}@RequiresApi(Build.VERSION_CODES.O)override fun initData(savedInstanceState: Bundle?, binding: ActivitySysNotificationBinding) {binding.tvSysNotificationAdd.setOnClickListener {IntentUtils.startActivityForResult(SysNotificationEditorActivity::class.java, REQUEST_CODE_ADD_SYS_NOTIFICATION)}notificationRepository.observeData(this, object : DoraPageDatabaseCacheRepository.AdapterDelegate<SystemNotification> {override fun addData(data: MutableList<SystemNotification>) {adapter.addData(data)mBinding.emptyLayout.showContent()}override fun setList(data: MutableList<SystemNotification>) {adapter.setList(data)mBinding.emptyLayout.showContent()}})binding.slSysNotificationList.setOnSwipeListener(object : SwipeLayout.OnSwipeListener {override fun onRefresh(swipeLayout: SwipeLayout) {}override fun onLoadMore(swipeLayout: SwipeLayout) {if (!notificationRepository.isLastPage(notificationRepository.getTotalSize())) {notificationRepository.onLoadMore {swipeLayout.loadMoreFinish(if (it) SwipeLayout.SUCCEED else SwipeLayout.FAIL)}}}})ViewUtils.configRecyclerView(binding.recyclerView).adapter = adapternotificationRepository.onRefresh()adapter.addChildClickViewIds(R.id.ll_sys_notification_list, R.id.btn_delete)adapter.setOnItemChildClickListener { _, view, position ->when (view.id) {R.id.ll_sys_notification_list -> {val item = adapter.getItem(position)IntentUtils.startActivityForResultWithSerializable(this@SysNotificationActivity,SysNotificationEditorActivity::class.java,ACTION_UPDATE_SYS_NOTIFICATION,REQUEST_CODE_UPDATE_SYS_NOTIFICATION,KEY_SYSTEM_NOTIFICATION, item)}R.id.btn_delete -> {DoraAlertDialog(this).show(R.string.confirm_delete) {themeColorResId(R.color.colorPrimary)positiveListener {val item = adapter.getItem(position)net {val req = ReqSysNotification(productName = PRODUCT_NAME,id = item.id)val ok = DoraHttp.result {RetrofitManager.getService(HomeService::class.java).removeSystemNotification(req.toRequestBody())}?.dataif (ok == true) {adapter.removeAt(position)Tips.showSuccess(R.string.deleted_successfully)}}}}}}}}
}
這里用到了Dagger的依賴注入,不在本篇的討論范圍。DoraPageDatabaseCacheRepository.AdapterDelegate告訴適配器,你就在我的回調里面更新數據就好了。
在哪里去找緩存庫相關源碼?
https://github.com/dora4/dcache-android 緩存庫
https://github.com/dora4/dview-swipe-layout 下拉刷新控件
https://github.com/dora4/dview-empty-layout 空態頁面