来自  资质荣誉 2019-10-10 08:27 的文章
当前位置: 澳门太阳娱乐手机登录 > 资质荣誉 > 正文

Android架构组件

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

Paging Library(分页加载库)用于逐步从数据源加载信息,而不会耗费过多的设备资源或者等待太长的时间。

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

总体概览

一个常见的需求是获取很多数据,但是同时也只展示其中的一小部分数据。这就会引起很多问题,比如浪费资源和流量等。
现有的Android APIs可以提供分页加载的功能,但是也带来了显著的限制和缺点:

  • CursorAdapter,使得从数据库加载数据到ListView变得非常容易。但是这是在主线程中查询数据库,并且分页的内容使用低效的 Cursor返回。更多使用CursorAdapter带来的问题参考Large Database Queries on Android。
  • AsyncListUtil提供基于位置的( position-based)分页加载到 RecyclerView中,但是无法使用不基于位置(non-positional)的分页加载,而且还强制把null作为占位符。

paging library就是为了解决上述的问题而提出的。

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

提供的类

DataSource
数据源。根据你想要访问数据的方式,可以有两种子类可供选择:

  • KeyedDataSource用于加载从第N到N+1数据。
  • TiledDataSource 用于从任意位置的分页数据。

例如使用 Room persistence library 就可以自动创建返回 TiledDataSource类型的数据:

@Query("select * from users WHERE age > :age order by name DESC, id ASC")
TiledDataSource<User> usersOlderThan(int age);

PagedList

从DataSource获取指定数量的数据,并且可以制定预取多少数据。这样可以最大程度减少加载数据的时间。这个类可以提供更新信息给其他类比如RecyclerView.Adapter来更新 RecyclerView的UI。

PagedListAdapter

这个类是 RecyclerView.Adapter的一个实现类,用于当数据加载完毕时,通知 RecyclerView数据已经到达。 RecyclerView就可以把数据填充进来,取代原来的占位元素。
PagedListAdapter使用后台线程来计算 PagedList 的改变。

LivePagedListProvider

从数据源中产生 LiveData<PagedList>。此外如果使用的是 Room persistence library,DAO还能使用 TiledDataSource生成 LivePagedListProvider。示例代码:

@Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC")
public abstract LivePagedListProvider<Integer, User> usersOlderThan(int age);

通过上面的类的配合使用,paging library从后台线程获取数据流,再在UI线程中展示。例如,当新的item别插入到数据库, DataSource被更新, LivePagedListProvider在后台线程产生了新的 PagedList

详细的流程如下图:

图片 1

paging-threading.gif

新生成的 PagedList在主线程中被发送到PagedListAdapter, PagedListAdapter在后台线程使用 DiffUtil计算新的list和原来的list的差距。当比较完毕, PagedListAdapter调用RecyclerView.Adapter.notifyItemInserted()来通知数据刷新。
下面的示例代码展示了这些类如何配合工作:

@Dao
interface UserDao {
    @Query("SELECT * FROM user ORDER BY lastName ASC")
    public abstract LivePagedListProvider<Integer, User> usersByLastName();
}

class MyViewModel extends ViewModel {
    public final LiveData<PagedList<User>> usersList;
    public MyViewModel(UserDao userDao) {
        usersList = userDao.usersByLastName().create(
                /* initial load position */ 0,
                new PagedList.Config.Builder()
                        .setPageSize(50)
                        .setPrefetchDistance(50)
                        .build());
    }
}

class MyActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        UserAdapter<User> adapter = new UserAdapter();
        viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));
        recyclerView.setAdapter(adapter);
    }
}

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
    public UserAdapter() {
        super(DIFF_CALLBACK);
    }
    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = getItem(position);
        if (user != null) {
            holder.bindTo(user);
        } else {
            // Null defines a placeholder item - PagedListAdapter will automatically invalidate
            // this row when the actual object is loaded from the database
            holder.clear();
        }
    }
    public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // User properties may have changed if reloaded from the DB, but ID is fixed
            return oldUser.getId() == newUser.getId();
        }
        @Override
        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // NOTE: if you use equals, your object must properly override Object#equals()
            // Incorrectly returning false here will result in too many animations.
            return oldUser.equals(newUser);
        }
    }
}

好了,整个Android Architecture Components系列都介绍完了,感谢各位大佬的阅读。

整个系列的后两篇文章相对介绍的比较粗糙一点,主要还是因为时间比较紧张,还有其实Room和Paging Library更像是一个第三方库,主要还是需要大家去写一遍感受可能会更清晰一点。

PS.鉴于大家的都建议给一个整体框架的demo,这里可以提供一个更好的方案:Google Android Architecture Components,这是Google官方提供的样例可以用来参考。

相关文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)

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

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

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

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

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

启动Android Studio 3并创建一个名为空活动的新项目 MainActivity。一定要检查包含Kotlin支持

图片 2Android Studio创建项目屏幕

创建新项目后,在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如何查找库。确保在添加项目后记得同步项目。

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

一个唯一的ID人的名字另外,包括一个toString( 简单返回的方法name。

import android.arch.persistence.room.Entityimport android.arch.persistence.room.PrimaryKey @Entity(tableName = "persons")data class Person( @PrimaryKey val id: String, val name: String) { override fun toString() = name}

如您所知,我们需要使用Room库来访问应用程序的数据,我们需要数据访问对象。在我们自己的案例中,我们创建了一个 PersonDao。

import android.arch.lifecycle.LiveDataimport android.arch.paging.DataSourceimport android.arch.persistence.room.Daoimport android.arch.persistence.room.Deleteimport android.arch.persistence.room.Insertimport android.arch.persistence.room.Query @Daointerface 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,并可以触发进一步加载loadAround。我们Factory在DataSource类中调用静态方法,该方法用作工厂(创建对象而不必指定将要创建的对象的确切类)DataSource。此静态方法有两种数据类型:

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

这是我们的Room数据库类的AppDatabase样子:

import android.arch.persistence.db.SupportSQLiteDatabaseimport android.arch.persistence.room.Databaseimport android.arch.persistence.room.Roomimport android.arch.persistence.room.RoomDatabaseimport android.content.Contextimport androidx.work.OneTimeWorkRequestBuilderimport androidx.work.WorkManagerimport com.chikeandroid.pagingtutsplus.utils.DATABASE_NAMEimport 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 { instance ?: buildDatabase.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 val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker> WorkManager.getInstance()?.enqueue } }) .build() } }}

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

为了让我们以生命周期意识的方式存储,观察和提供数据,我们需要一个ViewModel。我们的PersonsViewModel,它扩展了AndroidViewModel类,是要作为我们的ViewModel

import android.app.Applicationimport android.arch.lifecycle.AndroidViewModelimport android.arch.lifecycle.LiveDataimport android.arch.paging.DataSourceimport android.arch.paging.LivePagedListBuilderimport android.arch.paging.PagedListimport com.chikeandroid.pagingtutsplus.data.AppDatabaseimport 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>。

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

RecyclerView.Adapter用于从PagedLista中的s 呈现分页数据的基类RecyclerView。所以我们创建了一个PersonAdapter扩展 PagedListAdapter。

import android.arch.paging.PagedListAdapterimport android.content.Contextimport android.support.v7.widget.RecyclerViewimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.TextViewimport com.chikeandroid.pagingtutsplus.Rimport com.chikeandroid.pagingtutsplus.data.Personimport 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 if (person == null) { holderPerson.clear() } else { holderPerson.bind } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder { return PersonViewHolder(LayoutInflater.from.inflate(R.layout.item_person, parent, false)) } class PersonViewHolder (view: View) : RecyclerView.ViewHolder { 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.DiffUtilimport 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 { var tvName: TextView = view.name fun bind(person: Person) { tvName.text = person.name } fun clear() { tvName.text = null } }}

在我们onCreate()的MainActivity,我们只是做了以下:

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

import android.arch.lifecycle.Observerimport android.arch.lifecycle.ViewModelProvidersimport android.os.Bundleimport android.support.v7.app.AppCompatActivityimport android.support.v7.widget.RecyclerViewimport com.chikeandroid.pagingtutsplus.adapter.PersonAdapterimport 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.get(PersonsViewModel::class.java) val adapter = PersonAdapter findViewById<RecyclerView>(R.id.name_list).adapter = adapter subscribeUi } private fun subscribeUi(adapter: PersonAdapter) { viewModel.getPersonLiveData().observe(this, Observer { names -> if (names != null) adapter.submitList }}

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

图片 3教程结果截图

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

如果您正在使用或想要在项目中使用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,性能优化等技术教程!

本文由澳门太阳娱乐手机登录发布于 资质荣誉,转载请注明出处:Android架构组件

关键词: