Paging 2 库概览   属于 Android Jetpack

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

本指南提供了该库的几个概念性示例,以及其工作原理的概览。如需查看该库功能的完整示例,请尝试其他资源部分中的 Codelab 和示例。

设置

如需将 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 控制器的生命周期

以下代码段显示了如何配置您应用的视图模型,以使用 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 将数据游标与列表视图关联

您的应用可能会使用 CursorAdapterCursor 中的数据与 ListView 关联。在这种情况下,您通常需要将 ListView 迁移到 RecyclerView 作为应用列表 UI 容器,然后根据 Cursor 实例是否访问 SQLite 数据库,将 Cursor 组件替换为 RoomPositionalDataSource

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

使用 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();
    }
}

ConcertDaoConcertAdapter 的代码对于基于 RxJava2 的解决方案和基于 LiveData 的解决方案都是相同的。

提供反馈

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

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

其他资源

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

示例

Codelab

视频