• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

Android架构组件:使用带有Room的分页库

在本教程中,我将向您展示如何在 android 应用程序中将 Android 架构组件中的分页库与 Room 支持的数据库一起使用。 

您将学习如何使用 Paging 库从 Room-backed 数据库中有效地加载大型数据集——在recyclerview中滚动时为您的用户提供更流畅的体验。 

先决条件

为了能够遵循本教程,您需要:

  • Android Studio  3.1.3 或更高版本

  • kotlin 插件 1.2.51 或更高版本

  • 对 Android 架构组件(尤其是 Room 数据库)Livedata有基本的了解

可以在我们的 GitHub 存储库中找到本教程的示例项目, 以便您轻松跟进。

什么是分页库?

Paging 库是添加到Architecture Components的另一个库。该库有助于有效管理RecyclerView. 根据官方文档:

分页库使您可以更轻松地在应用程序的 RecyclerView.

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

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

既然您已经看到了 Paging 库的介绍,您可能会问,为什么要使用它?以下是您应该考虑使用它来加载大型数据集的一些原因RecyclerView。 

  • 它不请求不需要的数据。这个库只请求用户可见的数据——当用户滚动列表时。 

  • 节省用户的电池并消耗更少的带宽。因为它只请求需要的数据,这节省了一些设备资源。 

处理大量数据时效率不高,因为底层数据源检索所有数据,即使只有该数据的一个子集将显示给用户。在这种情况下,我们应该考虑对数据进行分页。 

1. 创建一个Android Studio项目

启动您的 Android Studio 3 并创建一个新项目,其中包含一个名为 MainActivity. 确保选中Include Kotlin support。 

Android架构组件:使用带有Room的分页库  第1张

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对象列表的 a 。另一个是 getAllPaged(),它返回一个DataSource.Factory。 

根据官方文档,DataSource该类是:

用于将快照数据页面加载到 PagedList.

APagedList是一种List用于在 Android 中显示分页数据的特殊类型: 

A PagedList是一个 List ,它从一个 DataSource. 可以使用 访问项目 get(int),并且可以使用 触发进一步加载 loadAround(int)。

我们在类中调用了Factory静态方法,它作为. 这个静态方法接受两种数据类型:DataSourceDataSource

  • 标识 中的项目的键DataSource。请注意,对于 Room 查询,页面是有编号的——因此我们将Integer其用作页面标识符类型。使用 Paging 库可以有“键控”页面,但 Room 目前不提供。 

  • s加载的列表中的项目或实体 (POJO) 的类型DataSource。

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. 创建视图模型

为了让我们的 UI 以生命周期意识的方式存储、观察和提供数据,我们需要一个ViewModel. PersonsViewModel扩展类的ourAndroidViewModel将用作 our 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包含对象PagedList的 一个Person。因为这是 a LiveData,所以我们的 UI(Activity或Fragment)将通过调用 getter 方法来观察这些数据getPersonsLiveData()。 

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

然后我们创建一个LivePagedListBuilder. 以下是官方文档中关于 a 的内容LivePagedListBuilder: 

生成器 LiveData<PagedList>,给定 a DataSource.Factory和 a PagedList.Config。

我们提供它的构造函数 aDataSource.Factory作为第一个参数,页面大小作为第二个参数(在我们自己的例子中,页面大小为 50)。通常,您应该选择一个大于您可能一次向用户显示的最大数量的大小。最后,我们调用build()构造并返回给我们一个LiveData<PagedList>. 

7. 创建 PagedListAdapter

为了PagedList在 a 中显示我们的数据RecyclerView,我们需要一个PagedListAdapter. 这是官方文档中对此类的明确定义:

RecyclerView.AdapterPagedList用于在 a 中呈现来自 s 的 分页数据的基类 RecyclerView。

所以我们创建了一个PersonAdapterextends  PagedListAdapter。

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
        }

    }
}

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

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

请注意,您PagedListAdapter 需要将它传递 DiffUtil.ItemCallback给PageListAdapter构造函数。DiffUtil是一个RecyclerView实用程序类,可以计算两个列表之间的差异,并输出一个更新操作列表,将第一个列表转换为第二个列表。ItemCallback是一个内部抽象静态类(inside 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. 显示结果

在我们onCreate()的 of ourMainActivity中,我们简单地执行了以下操作:

  • 使用实用程序类初始化我们的 viewModel 字段ViewModelProviders

  • 创建一个实例 PersonAdapter

  • 配置我们的RecyclerView

  • 绑定 PersonAdapter到RecyclerView

  • 观察并 通过调用将对象LiveData提交给PagedListPersonAdaptersubmitList()

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)
        })
    }
}

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

Android架构组件:使用带有Room的分页库  第2张在滚动时,Room 可以通过一次加载 50 个项目并将它们提供给我们PersonAdapter的PagingListAdapter. 但请注意,并非所有数据源都会快速加载。加载速度还取决于 Android 设备的处理能力。 

9.与 RxJava 集成

如果您在项目中使用或想要使用 RxJava,分页库包含另一个有用的工件:  RxPagedListBuilder. 您使用这个工件而不是 LivePagedListBuilderRxJava 支持。 

您只需创建 的实例 ,并提供与-the和页面大小RxPagedListBuilder相同的参数 。 然后,您分别 调用 or返回一个or  。LivePagedListBuilderDataSource.FactorybuildObservable()buildflowable()ObservableFlowablePagedList

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

结论

在本教程中,您学习了如何轻松地将 Android 架构组件(  Android Jetpack的一部分)中的 Paging 组件与 Room 一起使用。这有助于我们有效地从本地数据库加载大型数据集,从而在滚动浏览RecyclerView. 


文章目录
  • 先决条件
  • 什么是分页库?
  • 那么为什么要使用分页库呢?
  • 1. 创建一个Android Studio项目
  • 2. 添加架构组件
  • 3. 创建实体
  • 4. 创建 DAO
  • 5. 创建数据库
  • 6. 创建视图模型
  • 7. 创建 PagedListAdapter
  • 8. 显示结果
  • 9.与 RxJava 集成
  • 结论