Android体系结构组件:在房间中使用分页库

在本教程中,我将向您展示如何将Android Architecture组件中的Paging库与Android应用中由Room支持的数据库一起使用。

您将学习如何使用分页库从Room支持的数据库中有效加载大型数据集,从而在滚动RecyclerView时为用户提供更流畅的体验。

先决条件

要遵循本教程,您需要:

您可以在我们的GitHub存储库中找到本教程的示例项目,因此您可以轻松地进行后续操作。

什么是分页库?

Paging库是另一个添加到Architecture Components的库。 该库有助于有效地管理RecyclerView大数据集的加载和显示。 根据官方文档:

分页库使您可以更轻松地在应用程序的RecyclerView逐渐优雅地加载数据。

如果您的Android应用程序的任何部分将显示来自本地或远程数据源的大型数据集,但一次仅显示其中一部分,那么您应该考虑使用Paging库。 这将有助于提高您的应用程序的性能!

那么为什么要使用分页库?

既然您已经了解了Paging库的介绍,您可能会问,为什么要使用它? 有一些原因使您考虑在RecyclerView中加载大型数据集时使用它。

  • 它不请求不需要的数据。 当用户滚动列表时,该库仅请求用户可见的数据。
  • 节省用户的电池并消耗更少的带宽。 因为它仅请求所需的数据,所以可以节省一些设备资源。

当处理大量数据时,这将是无效的,因为底层数据源将检索所有数据,即使仅将一部分数据显示给用户。 在这种情况下,我们应该考虑分页数据。

1.创建一个Android Studio项目

启动您的Android Studio 3并创建一个名为MainActivity的空活动的新项目。 确保选中包括Kotlin支持

Android Studio创建项目屏幕

2.添加架构组件

创建新项目后,在build.gradle中添加以下依赖 在本教程中,我们使用最新的Paging库版本1.0.1,而Room是1.1.1(在撰写本文时)。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "android.arch.persistence.room:runtime:1.1.1"
    kapt "android.arch.persistence.room:compiler:1.1.1"
    implementation "android.arch.paging:runtime:1.0.1"
    implementation "com.android.support:recyclerview-v7:27.1.1"
}

这些工件可以在Google的Maven存储库中找到。

allprojects {
    repositories {
        google()
        jcenter()
    }
}

通过添加依赖关系,我们教会了Gradle如何查找该库。 确保记住添加项目后同步项目。

3.创建实体

创建一个新的Kotlin数据类Person 。 为简单起见,我们的Person实体只有两个字段:

  • 唯一ID( id
  • 人名( name

另外,包括一个toString(方法,该方法仅返回name

import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey

@Entity(tableName = "persons")
data class Person(
        @PrimaryKey val id: String,
        val name: String
) {
    override fun toString() = name
}

4.创建DAO

如您所知,要让我们通过Room库访问应用程序的数据,我们需要数据访问对象(DAO)。 对于我们自己,我们创建了一个PersonDao

import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query

@Dao
interface PersonDao {

    @Query("SELECT * FROM persons")
    fun getAll(): LiveData<List<Person>>

    @Query("SELECT * FROM persons")
    fun getAllPaged(): DataSource.Factory<Int, Person>

    @Insert
    fun insertAll(persons: List<Person>)

    @Delete
    fun delete(person: Person)
}

PersonDao类中,我们有两个@Query方法。 其中之一是getAll() ,它返回一个LiveData ,其中包含一个Person对象列表。 另一个是getAllPaged() ,它返回一个DataSource.Factory

根据官方文档, DataSource类是:

用于将快照数据页面加载到PagedList基类。

PagedList是一种特殊的List用于显示Android中的分页数据:

PagedList是一个List,它从DataSource以块(页面)的形式加载其数据。 可以使用get(int)访问项目,并可以使用loadAround(int)触发进一步的加载。

我们所谓的Factory在静态方法DataSource类,作为一个工厂(而无需指定确切类将要创建的对象的创建对象)的DataSource 。 此静态方法采用两种数据类型:

  • 标识DataSource项目的键。 请注意,对于“房间”查询,页面已编号,因此我们将Integer用作页面标识符类型。 可以使用Paging库来“锁定”页面,但是Room目前不提供该功能。
  • DataSource加载的列表中的项目或实体(POJO)的类型。

5.创建数据库

这是我们的Room数据库类AppDatabase外观:

import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.chikeandroid.pagingtutsplus.utils.DATABASE_NAME
import com.chikeandroid.pagingtutsplus.workers.SeedDatabaseWorker

@Database(entities = [Person::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun personDao(): PersonDao

    companion object {

        // For Singleton instantiation
        @Volatile private var instance: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance
                        ?: buildDatabase(context).also { instance = it }
            }
        }

        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                    .addCallback(object : RoomDatabase.Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
                            WorkManager.getInstance()?.enqueue(request)
                        }
                    })
                    .build()
        }
    }
}

在这里,我们创建了一个数据库实例,并使用新的WorkManager API将其预填充了数据。 请注意,预先填充的数据只是1,000个名称的列表(请参阅提供的示例源代码以了解更多信息)。

6.创建ViewModel

为了使我们的UI以关注生命周期的方式存储,观察和提供数据,我们需要一个ViewModel 。 我们的PersonsViewModel扩展了AndroidViewModel类,它将用作ViewModel

import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import com.chikeandroid.pagingtutsplus.data.AppDatabase
import com.chikeandroid.pagingtutsplus.data.Person

class PersonsViewModel constructor(application: Application)
    : AndroidViewModel(application) {

    private var personsLiveData: LiveData<PagedList<Person>>

    init {
        val factory: DataSource.Factory<Int, Person> =
        AppDatabase.getInstance(getApplication()).personDao().getAllPaged()

        val pagedListBuilder: LivePagedListBuilder<Int, Person>  = LivePagedListBuilder<Int, Person>(factory,
                50)
        personsLiveData = pagedListBuilder.build()
    }

    fun getPersonsLiveData() = personsLiveData
}

在此类中,我们只有一个字段称为personsLiveData 。 该字段只是一个LiveData ,其中包含Person对象的PagedList 。 因为这是一个LiveData ,所以我们的UI( ActivityFragment )将通过调用getter方法getPersonsLiveData()来观察此数据。

我们在init块内初始化了personsLiveData 。 在此块内,我们通过调用PersonDao对象的AppDatabase单例来获取DataSource.Factory 。 当我们得到这个对象时,我们调用getAllPaged()

然后,我们创建一个LivePagedListBuilder 。 这是官方文档中关于LivePagedListBuilder

给定DataSource.FactoryPagedList.Config LiveData<PagedList>生成器。

我们为其构造函数提供一个DataSource.Factory作为第一个参数,并将页面大小作为第二个参数(在我们自己的情况下,页面大小将为50)。 通常,您应该选择一个大于您一次可以显示给用户的最大数字的大小。 最后,我们调用build()构造并返回一个LiveData<PagedList>

7.创建PagedListAdapter

要在RecyclerView显示我们的PagedList数据,我们需要一个PagedListAdapter 。 这是来自官方文档的此类的清晰定义:

RecyclerView.Adapter基类,用于在RecyclerView显示来自PagedList的分页数据。

因此,我们创建了一个扩展PagedListAdapterPersonAdapter

import android.arch.paging.PagedListAdapter
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.chikeandroid.pagingtutsplus.R
import com.chikeandroid.pagingtutsplus.data.Person
import kotlinx.android.synthetic.main.item_person.view.*

class PersonAdapter(val context: Context) : PagedListAdapter<Person, PersonAdapter.PersonViewHolder>(PersonDiffCallback()) {

    override fun onBindViewHolder(holderPerson: PersonViewHolder, position: Int) {
        var person = getItem(position)

        if (person == null) {
            holderPerson.clear()
        } else {
            holderPerson.bind(person)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
        return PersonViewHolder(LayoutInflater.from(context).inflate(R.layout.item_person,
                parent, false))
    }


    class PersonViewHolder (view: View) : RecyclerView.ViewHolder(view) {

        var tvName: TextView = view.name

        fun bind(person: Person) {
            tvName.text = person.name
        }

        fun clear() {
            tvName.text = null
        }

    }
}

RecyclerView.Adapter任何其他子类一样,使用PagedListAdapter 。 换句话说,您必须实现onCreateViewHolder()onBindViewHolder()

要扩展PagedListAdapter抽象类,您必须在其构造函数中提供PageLists的类型(这应该是一个普通的旧Java类:POJO),以及一个扩展将由适配器使用的ViewHolder的类。 在我们的例子中,我们分别给它PersonPersonViewHolder作为第一个和第二个参数。

需要注意的是PagedListAdapter需要你传递一个DiffUtil.ItemCallbackPageListAdapter构造。 DiffUtilRecyclerView实用程序类,它可以计算两个列表之间的差异,并输出将第一个列表转换为第二个列表的更新操作列表。 ItemCallback是一个内部抽象静态类(在DiffUtil内部),用于计算列表中两个非空项目之间的差异。

具体来说,我们将PersonDiffCallback给我们的PagedListAdapter构造函数。

import android.support.v7.util.DiffUtil
import com.chikeandroid.pagingtutsplus.data.Person

class PersonDiffCallback : DiffUtil.ItemCallback<Person>() {

    override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: Person?, newItem: Person?): Boolean {
        return oldItem == newItem
    }
}

因为我们正在实现DiffUtil.ItemCallback ,所以我们必须实现两个方法: areItemsTheSame()areContentsTheSame()

  • areItemsTheSame来检查两个对象是否表示同一项目。 例如,如果您的商品具有唯一的ID,则此方法应检查其ID的相等性。 如果两个项目表示相同的对象,则此方法返回true否则,返回false
  • areContentsTheSame来检查两个项目是否具有相同的数据。 如果项目的内容相同,则此方法返回true否则,则返回false

我们的PersonViewHolder内部类只是一个典型的RecyclerView.ViewHolder 。 它负责根据需要将模型中的数据绑定到列表中一行的小部件中。

class PersonAdapter(val context: Context) : PagedListAdapter<Person, PersonAdapter.PersonViewHolder>(PersonDiffCallback()) {
    
    // ... 
    
    class PersonViewHolder (view: View) : RecyclerView.ViewHolder(view) {

        var tvName: TextView = view.name

        fun bind(person: Person) {
            tvName.text = person.name
        }

        fun clear() {
            tvName.text = null
        }

    }
}

8.显示结果

MainActivity onCreate()中,我们简单地执行了以下操作:

  • 使用实用工具类ViewModelProviders初始化我们的viewModel字段
  • 创建一个PersonAdapter实例
  • 配置我们的RecyclerView
  • PersonAdapter绑定到RecyclerView
  • 观察LiveData并通过调用submitList()PagedList对象提交给PersonAdapter
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.RecyclerView
import com.chikeandroid.pagingtutsplus.adapter.PersonAdapter
import com.chikeandroid.pagingtutsplus.viewmodels.PersonsViewModel

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: PersonsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProviders.of(this).get(PersonsViewModel::class.java)

        val adapter = PersonAdapter(this)
        findViewById<RecyclerView>(R.id.name_list).adapter = adapter

        subscribeUi(adapter)
    }

    private fun subscribeUi(adapter: PersonAdapter) {
        viewModel.getPersonLiveData().observe(this, Observer { names ->
            if (names != null) adapter.submitList(names)
        })
    }
}

最后,当您运行该应用程序时,结果如下:

教程结果截图

滚动时,Room可以通过一次加载50个项目并将其提供给我们的PersonAdapter (这是PagingListAdapter的子类)来防止出现间隙。 但是请注意,并非所有数据源都会快速加载。 加载速度还取决于Android设备的处理能力。

9.与RxJava集成

如果您在项目中使用或希望使用RxJava,则分页库包含另一个有用的工件: RxPagedListBuilder 。 您可以使用此工件而不是LivePagedListBuilder来获得RxJava支持。

您只需创建一个实例RxPagedListBuilder ,提供相同的参数,你会为LivePagedListBuilder -the DataSource.Factory和页面大小。 然后调用buildObservable()buildFlowable()返回一个ObservableFlowable为您PagedList分别。

要为数据加载工作显式提供Scheduler ,请调用setter方法setFetchScheduler() 。 要还提供用于传递结果的Scheduler (例如AndroidSchedulers.mainThread() ),只需调用setNotifyScheduler() 。 默认情况下, setNotifyScheduler()默认为UI线程,而setFetchScheduler()默认为I / O线程池。

结论

在本教程中,您学习了如何轻松地将Room中的Android架构组件(属于Android Jetpack的一部分)中的Paging组件使用。 这有助于我们从本地数据库有效地加载大型数据集,从而在滚动浏览RecyclerView的列表时实现更流畅的用户体验。

我强烈建议您查看官方文档,以了解有关Android中的Paging库的更多信息。

翻译自: https://code.tutsplus.com/tutorials/android-architecture-components-using-the-paging-library-with-room--cms-31535

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值