前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >android paging 库介绍

android paging 库介绍

作者头像
提莫队长
修改2023-09-25 19:14:38
1.3K0
修改2023-09-25 19:14:38
举报
文章被收录于专栏:刘晓杰

1.paging库简介

Paging 使您的应用程序配合RecyclerView更容易从数据源中高效优雅地加载所需的数据,不会因为数据库数据量大而造成查询时间过长。说白了就是分页加载的优化。

1.1 目录结构
代码语言:javascript
复制
implementation "androidx.paging:paging-runtime:2.1.2"

之所以没用最新的是因为kotlin版本号冲突,所以降低了版本

paging.png

1.2 重要的类介绍

paging库最重要的三个类就是DataSource,PageList,PageListAdapter。

(1)PageListAdapter

PagedListAdapter是通过RecyclerView.Adapter实现,用于展示PagedList的数据。它本身并没有比adapter多多少东西。主要需要注意 AsyncPagedListDiffer 这个辅助类。它负责监听PagedList的更新, Item数量的统计等功能。当数据源变动产生新的PagedList,PagedAdapter会在后台线程中比较前后两个PagedList的差异,然后调用notifyItem…()方法更新RecyclerView。具体来看看PagedListAdapter的submitList方法

代码语言:javascript
复制
    public void submitList(@Nullable final PagedList<T> pagedList,
            @Nullable final Runnable commitCallback) {

        ......

        final PagedList<T> oldSnapshot = mSnapshot;
        final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
        mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                final DiffUtil.DiffResult result;
                result = PagedStorageDiffHelper.computeDiff(
                        oldSnapshot.mStorage,
                        newSnapshot.mStorage,
                        mConfig.getDiffCallback());

                mMainThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (mMaxScheduledGeneration == runGeneration) {
                            latchPagedList(pagedList, newSnapshot, result,
                                    oldSnapshot.mLastLoad, commitCallback);//*******************
                        }
                    }
                });
            }
        });
    }

    void latchPagedList(
            @NonNull PagedList<T> newList,
            @NonNull PagedList<T> diffSnapshot,
            @NonNull DiffUtil.DiffResult diffResult,
            int lastAccessIndex,
            @Nullable Runnable commitCallback) {

        PagedList<T> previousSnapshot = mSnapshot;
        mPagedList = newList;
        mSnapshot = null;

        // dispatch update callback after updating mPagedList/mSnapshot
        PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
                previousSnapshot.mStorage, newList.mStorage, diffResult);
        ......
    }

最后一行 PagedStorageDiffHelper.dispatchDiff 传进去的第一个参数 mUpdateCallback内部就实现了 mAdapter 的 notifyItem 等方法。具体是 ListUpdateCallback 简单来说就是 调用了submitList 就没必要再去调用 notify 方法了

(2) PagedList

PageList继承AbstractList,支持所有List的操作。同时它还有一个重要的成员变量,PagedStorage。PagedStorage 有如下变量

代码语言:javascript
复制
    private final ArrayList<List<T>> mPages;

说明是按页存储数据。PagedList会从Datasource中加载数据,更准确的说是通过Datasource加载数据, 通过Config的配置,可以设置一次加载的数量以及预加载的数量。 除此之外,PagedList还可以向RecyclerView.Adapter发送更新的信号,驱动UI的刷新。 PagedList 有三个子类ContiguousPagedList,SnapshotPagedList,TiledPagedList。可以通过 create 方法找到

代码语言:javascript
复制
    static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
            @NonNull Executor notifyExecutor,
            @NonNull Executor fetchExecutor,
            @Nullable BoundaryCallback<T> boundaryCallback,
            @NonNull Config config,
            @Nullable K key) {
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
            ......
            ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
            return new ContiguousPagedList<>(***);
        } else {
            return new TiledPagedList<>((PositionalDataSource<T>) dataSource,);
        }
    }

SnapshotPagedList 这个暂时可以不管,类似于弄了一个副本。ContiguousPagedList和TiledPagedList之后再介绍

(3)DataSource

DataSource<Key, Value>从字面意思理解是一个数据源,其中key对应加载数据的条件信息,Value对应加载数据的实体类。 DataSource是一个抽象类,但是我们不能直接继承它实现它的子类。但是Paging库里提供了好些它的子类

代码语言:javascript
复制
DataSource --- ContiguousDataSource --- ItemKeyedDataSource --- WrapperItemKeyedDataSource
DataSource --- ContiguousDataSource --- PageKeyedDataSource --- WrapperPageKeyedDataSource
DataSource --- PositionalDataSource --- ListDataSource
DataSource --- PositionalDataSource --- WrapperPositionalDataSource
  • PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,即Key 字段是页相关的信息。比如请求的数据的参数中包含类似next/previous页数的信息。
  • ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定item的信息, 即Key字段包含的是Item中的信息,比如需要根据第N项的信息加载第N+1项的数据,传参中需要传入第N项的ID时,该场景多出现于论坛类应用评论信息的请求。
  • PositionalDataSource<T>:适用于目标数据总数固定,通过特定的位置加载数据,这里Key是Integer类型的位置信息,T即Value。 比如从数据库中的1200条开始加在20条数据。

还有其他的,比如 ListDataSource ,其实就是已经定制好的,可以直接用的

(4) PageKeyedDataSource 和 ContiguousPagedList

一般的网络请求都是分页的,所以我把这个单独拿出来分析一下。 我们从 ContiguousPagedList 的 loadAroundInternal 开始

代码语言:javascript
复制
    protected void loadAroundInternal(int index) {
        int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index,
                mStorage.getLeadingNullCount());
        int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index,
                mStorage.getLeadingNullCount() + mStorage.getStorageCount());

        mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
        if (mPrependItemsRequested > 0) {
            schedulePrepend();
        }

        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
        if (mAppendItemsRequested > 0) {
            scheduleAppend();
        }
    }

    private void scheduleAppend() {
        if (mAppendWorkerState != READY_TO_FETCH) {
            return;
        }
        mAppendWorkerState = FETCHING;

        final int position = mStorage.getLeadingNullCount()
                + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();

        // safe to access first item here - mStorage can't be empty if we're appending
        final V item = mStorage.getLastLoadedItem();
        mBackgroundThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (isDetached()) {
                    return;
                }
                if (mDataSource.isInvalid()) {
                    detach();
                } else {
                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
                            mMainThreadExecutor, mReceiver);
                }
            }
        });
    }

走 mDataSource.dispatchLoadAfter 方法

代码语言:javascript
复制
    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
            int pageSize, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        @Nullable Key key = getNextKey();
        if (key != null) {
            loadAfter(new LoadParams<>(key, pageSize),
                    new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
        } else {
            receiver.onPageResult(PageResult.APPEND, PageResult.<Value>getEmptyResult());
        }
    }

如果设置了key,就自己实现loadAfter。如果没设置,就用 ContiguousPagedList 默认的 mReceiver。在里面可以看到 mStorage.appendPage

2.自己动手实现一个 paging demo

首先我们来简单看一下Paging库的工作示意图,主要是分为如下几个步骤

  • 使用DataSource从服务器获取或者从本地数据库获取数据(需要自己实现)
  • 将数据保存到PageList中(会根据DataSource类型来生成对应的PageList,paging库已实现)
  • 将PageList的数据submitList给PageListAdapter(需要自己调用)
  • PageListAdapter在后台线程对比原来的PageList和新的PageList,生成新PageList(Paging库已实现对比操作,用户只需提供DiffUtil.ItemCallback实现) PageListAdapter通知RecyclerView更新
(1)使用DataSource从服务器获取数据

这里我们就用官方demo的url做测试。因为是分页加载的,所以肯定选用PageKeyedDataSource

代码语言:javascript
复制
public class UserPageKeyedDataSource extends PageKeyedDataSource<String, Repo> {

    private int pageNum = 0;
    private GithubService service;

    UserPageKeyedDataSource(GithubService service) {
        this.service = service;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, Repo> callback) {
        pageNum = 0;
        try {
            Response<RepoSearchResponse> response = service.searchRepos("Android" + IN_QUALIFIER, pageNum, 20).execute();
            callback.onResult(response.body().getItems(), "", "");
        } catch (Exception e) {

        }
    }

    /**
     * 请求上一页数据(基本不用)
     */
    @Override
    public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, Repo> callback) {

    }

    /**
     * 请求下一页数据
     */
    @Override
    public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, Repo> callback) {
        pageNum++;
        try {
            Response<RepoSearchResponse> response = service.searchRepos("Android" + IN_QUALIFIER, pageNum, 20).execute();
            callback.onResult(response.body().getItems(), "");
        } catch (Exception e) {

        }
    }
}

用到的相关类贴出来

代码语言:javascript
复制
const val IN_QUALIFIER = "in:name,description"

/**
 * Github API communication setup via Retrofit.
 */
interface GithubService {
    /**
     * Get repos ordered by stars.
     */
    @GET("search/repositories?sort=stars")
    fun searchRepos(
        @Query("q") query: String,
        @Query("page") page: Int,
        @Query("per_page") itemsPerPage: Int
    ): Call<RepoSearchResponse>

    companion object {
        private const val BASE_URL = "https://api.github.com/"

        fun create(): GithubService {
            val logger = HttpLoggingInterceptor()
            logger.level = Level.BASIC

            val client = OkHttpClient.Builder()
                .addInterceptor(logger)
                .build()
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                //.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(IoScheduler()))
                .build()
                .create(GithubService::class.java)
        }
    }
}

data class RepoSearchResponse(
    @SerializedName("total_count") val total: Int = 0,
    @SerializedName("items") val items: List<Repo> = emptyList(),
    val nextPage: Int? = null
)

data class Repo(
    @field:SerializedName("id") val id: Long,
    @field:SerializedName("name") val name: String,
    @field:SerializedName("full_name") val fullName: String,
    @field:SerializedName("description") val description: String?,
    @field:SerializedName("html_url") val url: String,
    @field:SerializedName("stargazers_count") val stars: Int,
    @field:SerializedName("forks_count") val forks: Int,
    @field:SerializedName("language") val language: String?
)
(2)配置PageList

PageList主要负责控制 第一次默认加载多少数据,之后每一次加载多少数据,如何加载 等等。同时将数据的变更反映到UI上。

代码语言:javascript
复制
val adapter = MyPagedAdapter()
        recycleview.layoutManager = LinearLayoutManager(this)
        recycleview.adapter = adapter

        val sourceFactory = UserDataSourceFactory(GithubService.create())

        LivePagedListBuilder(sourceFactory, 20)
            //.setFetchExecutor()
            .build().observe(this,
                Observer<PagedList<Repo>> {
                    adapter.submitList(it)
                })

这里我们用LiveData来观察。注意她有个参数是 DataSource.Factory。这是DataSource 的内部工厂类,通过create()方法就可以获得DataSource 的实例。

代码语言:javascript
复制
public class UserDataSourceFactory extends DataSource.Factory<String, Repo> {

    private GithubService service;

    UserDataSourceFactory(GithubService service) {
        this.service = service;
    }

    @NonNull
    @Override
    public DataSource<String, Repo> create() {
        return new UserPageKeyedDataSource(service);
    }
}
(3)配置adapter
代码语言:javascript
复制
public class MyPagedAdapter extends PagedListAdapter<Repo, MyPagedAdapter.UserHolder> {

    public MyPagedAdapter() {
        super(callback);
    }

    @NonNull
    @Override
    public MyPagedAdapter.UserHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new UserHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyPagedAdapter.UserHolder holder, int position) {
        holder.bind(getItem(position));
    }

    static class UserHolder extends RecyclerView.ViewHolder {

        TextView mText;

        public UserHolder(@NonNull View itemView) {
            super(itemView);
            mText = itemView.findViewById(R.id.txt);
        }

        public void bind(Repo repo) {
            mText.setText(repo.getFullName());
        }
    }

    /**
     * DiffCallback的接口实现中定义比较的规则,比较的工作则是由PagedStorageDiffHelper来完成
     */
    private static final DiffUtil.ItemCallback<Repo> callback = new DiffUtil.ItemCallback<Repo>() {
        @Override
        public boolean areItemsTheSame(@NonNull Repo oldItem, @NonNull Repo newItem) {
            return oldItem.getId() == newItem.getId();
        }

        @Override
        public boolean areContentsTheSame(@NonNull Repo oldItem, @NonNull Repo newItem) {
            return oldItem.getFullName().equals(newItem.getFullName());
        }
    };
}

3.参考

Android Paging library详解(一) Android Paging library详解(二) Android Paging

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.paging库简介
  • 2.自己动手实现一个 paging demo
  • 3.参考
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档