分页缓存与下拉刷新的整合原理 - DoraPageDatabaseCacheRepository

何为分页缓存?

顾名思义,分页缓存就是边分页边缓存,分页通常使用下拉刷新控件实现,而缓存通常说的是指磁盘缓存,即保存到数据库中,数据库本身也是一个索引文件。

为什么缓存还要分页?

在很大一部分场景下,缓存都是需要分页的,不要问我为什么,问就是不懂规矩,哈哈。你家的接口一次性把整张表的数据全部都返回给你吗?先不说用户耗多少流量的问题,如果大家都一次性把表的数据都给你,那爬虫何以生存?分分钟就把你的数据全都弄走了,要知道,互联网世界,数据就是资源,数据就是钱啊!

怎么使用下拉刷新分页?

重复的问题不会再讲,看我之前的文章Android低代码开发 - 直接创建一个下拉刷新列表界面。下拉刷新和上拉加载都是用来分页加载数据的。下拉刷新往往只加载第一页的数据,而上拉加载则是加载下一页的数据,如果有。

下拉刷新怎么和缓存结合起来?

这个问题问的好。本篇提到的缓存一律指数据库缓存,而非内存缓存。当然我的dcache库https://github.com/dora4/dcache-android 支持内存缓存,会简单带过。我以缓存系统通知列表为例。

package com.dorachat.dorachat.repository

import 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.repository

import 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.OrmTable

abstract class DoraPageDatabaseCacheRepository<T : OrmTable>(context: Context)
    : DoraDatabaseCacheRepository<T>(context) {

    private var pageNo: Int = 0
    private var pageSize: Int = 10

    fun 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 / pageSize
        return 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 = 0
        fetchListData(listener = listener)
    }

    /**
     * 下拉刷新高阶函数,可结合[setPageSize]使用。
     */
    @JvmOverloads
    fun onRefresh(block: ((Boolean) -> Unit)? = null) {
        pageNo = 0
        fetchListData(listener = object : OnLoadStateListener {
            override fun onLoad(state: Int) {
                block?.invoke(state == OnLoadStateListener.SUCCESS)
            }
        })
    }

    /**
     * 上拉加载回调,可结合[setPageSize]使用。
     */
    fun onLoadMore(listener: OnLoadStateListener) {
        pageNo++
        fetchListData(listener = listener)
    }

    /**
     * 上拉加载高阶函数,可结合[setPageSize]使用。
     */
    @JvmOverloads
    fun 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 = pageSize
        return this
    }

    open fun setCurrentPage(pageNo: Int, pageSize: Int): DoraPageDatabaseCacheRepository<T> {
        this.pageNo = pageNo
        this.pageSize = pageSize
        return this
    }

    override fun query(): Condition {
        val start = pageNo * pageSize
        return QueryBuilder.create()
                .limit(start, pageSize)
                .toCondition()
    }

    /**
     * 没网的情况下直接加载缓存数据。
     */
    override fun selectData(ds: DataSource): Boolean {
        var isLoaded = false
        if (!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.admin

import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import com.dorachat.dorachat.ChatApp

import 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.Inject

class SysNotificationActivity : DaggerBaseActivity<ActivitySysNotificationBinding>() {

    @Inject lateinit var notificationRepository: SysNotificationRepository
    private 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 = adapter
        notificationRepository.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())
                                }?.data
                                if (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 空态页面

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dora丶Android

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值