Paging 2 库概述   Android Jetpack 的一部分。

Paging 库可帮助您一次加载和显示少量数据。按需加载部分数据可减少网络带宽和系统资源的使用。

本指南提供了该库的几个概念示例,以及其工作原理概述。要查看此库功能的完整示例,请尝试来自 其他资源 部分的代码实验室和示例。

设置

要将 Paging 组件导入您的 Android 应用,请将以下依赖项添加到应用的 build.gradle 文件中

Groovy

dependencies {
  def paging_version = "2.1.2"

  implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx
}

Kotlin

dependencies {
  val paging_version = "2.1.2"

  implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx
}

库架构

本节描述并展示了 Paging 库的主要组件。

PagedList

Paging 库的关键组件是 PagedList 类,它加载应用数据的块或页面。当需要更多数据时,它会被分页到现有的 PagedList 对象中。如果任何加载的数据发生更改,则会从 LiveData 或基于 RxJava2 的对象发出的可观察数据持有者中发出 PagedList 的新实例。随着 PagedList 对象的生成,您的应用 UI 会呈现其内容,同时尊重 UI 控制器的 生命周期

以下代码片段显示了如何配置应用的视图模型以使用 PagedList 对象的 LiveData 持有者加载和呈现数据

Kotlin

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

Java

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    // Creates a PagedList object with 50 items per page.
    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
                concertDao.concertsByDate(), 50).build();
    }
}

数据

每个 PagedList 实例都从其对应的 DataSource 对象加载应用程序数据的最新快照。数据从应用程序的后端或数据库流入 PagedList 对象。

以下示例使用 Room 持久性库 来组织应用程序的数据,但如果您想使用其他方式存储数据,您也可以 提供自己的数据源工厂

Kotlin

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

Java

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a
    // PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

要了解有关如何将数据加载到 PagedList 对象的更多信息,请参阅有关如何 加载分页数据 的指南。

UI

PagedList 类与 PagedListAdapter 一起使用,将项目加载到 RecyclerView 中。这些类协同工作,在加载内容时获取并显示内容,预取视野外的内容并为内容更改设置动画。

要了解更多信息,请参阅有关如何 显示分页列表 的指南。

支持不同的数据架构

Paging 库支持以下数据架构

  • 仅从后端服务器提供服务。
  • 仅存储在设备上的数据库中。
  • 其他来源的组合,使用设备上的数据库作为缓存。

图 1 显示了在每种架构场景中数据如何流动。在仅网络或仅数据库的解决方案中,数据直接流向应用程序的 UI 模型。如果您使用组合方法,则数据从您的后端服务器流入设备上的数据库,然后流向应用程序的 UI 模型。每隔一段时间,每个数据流的终点都会用完要加载的数据,此时它会向提供数据的组件请求更多数据。例如,当设备上的数据库用完数据时,它会向服务器请求更多数据。

Diagrams of data flows
图 1. 数据如何通过 Paging 库支持的每种架构流动

本节的其余部分提供了配置每个数据流用例的建议。

仅网络

要显示来自后端服务器的数据,请使用 Retrofit API 的同步版本将信息加载到 您自己的自定义 DataSource 对象 中。

仅数据库

设置您的 RecyclerView 以观察本地存储,最好使用 Room 持久性库。这样,每当应用程序的数据库中插入或修改数据时,这些更改都会自动反映在显示此数据的 RecyclerView 中。

网络和数据库

在开始观察数据库后,您可以使用 PagedList.BoundaryCallback 监听数据库何时没有数据。然后,您可以从网络中获取更多项目并将它们插入数据库。如果您的 UI 正在观察数据库,那么这就是您需要做的全部。

处理网络错误

当使用网络获取或分页使用 Paging 库显示的数据时,重要的是不要始终将网络视为“可用”或“不可用”,因为许多连接是间歇性的或不稳定的

  • 特定的服务器可能无法响应网络请求。
  • 设备可能连接到速度慢或信号弱的网络。

相反,您的应用程序应检查每个请求的故障,并在网络不可用时尽可能优雅地恢复。例如,您可以为用户提供一个“重试”按钮,如果数据刷新步骤不起作用,则可以选择该按钮。如果在数据分页步骤期间发生错误,最好自动重试分页请求。

更新您现有的应用程序

如果您的应用程序已经从数据库或后端源使用数据,则可以将其直接升级到 Paging 库提供的功能。本节介绍如何升级具有常见现有设计的应用程序。

自定义分页解决方案

如果您使用自定义功能从应用程序的数据源加载少量数据子集,则可以使用 PagedList 类中的逻辑替换此逻辑。PagedList 的实例提供与常用数据源的内置连接。这些实例还为您的应用程序 UI 中可能包含的 RecyclerView 对象提供适配器。

使用列表而不是页面加载数据

如果您使用内存中的列表作为 UI 适配器的后备数据结构,请考虑使用 PagedList 类观察数据更新,如果列表中的项目数量可能变得很大。PagedList 的实例可以使用 LiveData<PagedList>Observable<List> 将数据更新传递到应用程序的 UI,从而最大程度地减少加载时间和内存使用量。更好的是,在您的应用程序中用 PagedList 对象替换 List 对象不需要对应用程序的 UI 结构或数据更新逻辑进行任何更改。

使用 CursorAdapter 将数据游标与列表视图关联

您的应用程序可能会使用 CursorAdapter 将来自 Cursor 的数据与 ListView 关联。在这种情况下,您通常需要将应用程序的列表 UI 容器从 ListView 迁移到 RecyclerView,然后用 RoomPositionalDataSource 替换 Cursor 组件,具体取决于 Cursor 实例是否访问 SQLite 数据库。

在某些情况下,例如在使用 Spinner 实例时,您只提供适配器本身。然后,库会获取加载到该适配器中的数据并为您显示数据。在这些情况下,将适配器数据的类型更改为 LiveData<PagedList>,然后在尝试让库类在 UI 中填充这些项目之前,将此列表包装在 ArrayAdapter 对象中。

使用 AsyncListUtil 异步加载内容

如果您使用 AsyncListUtil 对象异步加载和显示信息组,则 Paging 库可以让您更轻松地加载数据

  • 您的数据不需要是位置性的。Paging 库允许您使用网络提供的键直接从后端加载数据。
  • 您的数据可以是无限大的。使用 Paging 库,您可以将数据加载到页面中,直到没有剩余数据为止。
  • 您可以更轻松地观察您的数据。Paging 库可以以可观察的数据结构呈现应用程序的 ViewModel 持有的数据。

数据库示例

以下代码片段显示了使所有部分协同工作的一些可能方法。

使用 LiveData 观察分页数据

以下代码片段显示了所有部分协同工作的方式。随着数据库中演唱会活动的添加、删除或更改,RecyclerView 中的内容会自动且有效地更新

Kotlin

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

class ConcertActivity : AppCompatActivity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: ConcertViewModel by viewModels()
        val recyclerView = findViewById(R.id.concert_list)
        val adapter = ConcertAdapter()
        viewModel.concertList.observe(this, PagedList(adapter::submitList))
        recyclerView.setAdapter(adapter)
    }
}

class ConcertAdapter() :
        PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {
    fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {
        val concert: Concert? = getItem(position)

        // Note that "concert" is a placeholder if it's null.
        holder.bindTo(concert)
    }

    companion object {
        private val DIFF_CALLBACK = object :
                DiffUtil.ItemCallback<Concert>() {
            // Concert details may have changed if reloaded from the database,
            // but ID is fixed.
            override fun areItemsTheSame(oldConcert: Concert,
                    newConcert: Concert) = oldConcert.id == newConcert.id

            override fun areContentsTheSame(oldConcert: Concert,
                    newConcert: Concert) = oldConcert == newConcert
        }
    }
}

Java

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
            concertDao.concertsByDate(), /* page size */ 50).build();
    }
}

public class ConcertActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ConcertViewModel viewModel =
                new ViewModelProvider(this).get(ConcertViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.concert_list);
        ConcertAdapter adapter = new ConcertAdapter();
        viewModel.concertList.observe(this, adapter::submitList);
        recyclerView.setAdapter(adapter);
    }
}

public class ConcertAdapter
        extends PagedListAdapter<Concert, ConcertViewHolder> {
    protected ConcertAdapter() {
        super(DIFF_CALLBACK);
    }

    @Override
    public void onBindViewHolder(@NonNull ConcertViewHolder holder,
            int position) {
        Concert concert = getItem(position);
        if (concert != null) {
            holder.bindTo(concert);
        } else {
            // Null defines a placeholder item - PagedListAdapter automatically
            // invalidates this row when the actual object is loaded from the
            // database.
            holder.clear();
        }
    }

    private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<Concert>() {
        // Concert details may have changed if reloaded from the database,
        // but ID is fixed.
        @Override
        public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
            return oldConcert.getId() == newConcert.getId();
        }

        @Override
        public boolean areContentsTheSame(Concert oldConcert,
                Concert newConcert) {
            return oldConcert.equals(newConcert);
        }
    };
}

使用 RxJava2 观察分页数据

如果您更喜欢使用 RxJava2 而不是 LiveData,则可以创建一个 ObservableFlowable 对象

Kotlin

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: Observable<PagedList<Concert>> =
            concertDao.concertsByDate().toObservable(pageSize = 50)
}

Java

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final Observable<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;

        concertList = new RxPagedListBuilder<>(
                concertDao.concertsByDate(), /* page size */ 50)
                        .buildObservable();
    }
}

然后,您可以使用以下代码片段中的代码开始和停止观察数据

Kotlin

class ConcertActivity : AppCompatActivity() {
    private val adapter = ConcertAdapter()

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val viewModel: ConcertViewModel by viewModels()

    private val disposable = CompositeDisposable()

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val recyclerView = findViewById(R.id.concert_list)
        recyclerView.setAdapter(adapter)
    }

    override fun onStart() {
        super.onStart()
        disposable.add(viewModel.concertList
                .subscribe(adapter::submitList)))
    }

    override fun onStop() {
        super.onStop()
        disposable.clear()
    }
}

Java

public class ConcertActivity extends AppCompatActivity {
    private ConcertAdapter adapter = new ConcertAdapter();
    private ConcertViewModel viewModel;

    private CompositeDisposable disposable = new CompositeDisposable();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        RecyclerView recyclerView = findViewById(R.id.concert_list);

        viewModel = new ViewModelProvider(this).get(ConcertViewModel.class);
        recyclerView.setAdapter(adapter);
    }

    @Override
    protected void onStart() {
        super.onStart();
        disposable.add(viewModel.concertList
                .subscribe(adapter.submitList(flowableList)
        ));
    }

    @Override
    protected void onStop() {
        super.onStop();
        disposable.clear();
    }
}

对于基于 RxJava2 的解决方案,ConcertDaoConcertAdapter 的代码与基于 LiveData 的解决方案相同。

提供反馈

通过以下资源与我们分享您的反馈和想法

问题跟踪器
报告问题,以便我们修复错误。

其他资源

要了解有关 Paging 库的更多信息,请参阅以下资源。

示例

Codelabs

视频