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
}

库架构

本节描述并显示了分页库的主要组件。

PagedList

Paging 库的关键组件是PagedList类,它加载应用数据的块或*页面*。随着更多数据的需要,它会被分页到现有的PagedList对象中。如果任何加载的数据发生更改,则会将PagedList的新实例从LiveData或基于 RxJava2 的对象发出到可观察的数据持有者。随着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,然后根据Cursor实例是否访问 SQLite 数据库,将Cursor组件替换为RoomPositionalDataSource

在某些情况下,例如使用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

视频