Android架构组件:使用带有会议室的分页库

在本教程中,我将向您展示如何在Android应用程序中使用Android架构组件中的Paging库和Room支持的数据库。

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

什么是分页库?

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

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

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

那么为什么要使用寻呼库?

既然您已经看过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。
A PagedList是一种List在Android中显示分页数据的特殊类型:

A PagedList是一个List,它从a中以块(页)加载其数据DataSource。可以使用,访问项目get(int),并可以触发进一步加载loadAround(int)。
我们Factory在DataSource类中调用静态方法,该方法用作工厂(创建对象而不必指定将要创建的对象的确切类)DataSource。此静态方法有两种数据类型:

标识项目的关键DataSource。请注意,对于Room查询,页面是编号的 - 因此我们将其Integer用作页面标识符类型。使用Paging库可以使用“键控”页面,但Room目前不提供。
DataSources 加载的列表中的项目或实体(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

为了让我们以生命周期意识的方式存储,观察和提供数据,我们需要一个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持有一个PagedList的 Person对象。因为这是一个LiveData,我们的UI(Activity或Fragment)将通过调用getter方法来观察这些数据getPersonsLiveData()。

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

然后我们创建一个LivePagedListBuilder。以下是官方文档中关于a的说明LivePagedListBuilder:

生成器LiveData<PagedList>,给定a DataSource.Factory和a PagedList.Config。
我们提供构造函数a DataSource.Factory作为第一个参数,页面大小作为第二个参数(在我们自己的情况下,页面大小将为50)。通常,您应该选择一个大于您可能一次向用户显示的最大数量的大小。最后,我们打电话build()给我们构建并返回给我们LiveData<PagedList>。

7. 创建PagedListAdapter

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

RecyclerView.Adapter用于从PagedLista中的s 呈现分页数据的基类RecyclerView。
所以我们创建了一个PersonAdapter扩展 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是一个内部抽象静态类(内部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()的MainActivity,我们只是做了以下:

viewModel 使用实用程序类初始化我们的 字段ViewModelProviders
创建一个实例 PersonAdapter
配置我们的 RecyclerView
绑定 PersonAdapter到RecyclerView
观察LiveData并 通过调用将PagedList对象提交给PersonAdaptersubmitList()

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。您使用此工件而不是 LivePagedListBuilderRxJava支持。

您只需创建一个实例 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知识,或者获取相关资料请加入Android技术开发交流2群:935654177。本群可免费获取Gradle,RxJava,小程序,Hybrid,移动架构,NDK,React Native,性能优化等技术教程!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容