日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

深入理解Android Paging分页加载库

發布時間:2025/6/15 Android 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解Android Paging分页加载库 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

來新公司半年多,最近一直在參與 Andorid 團隊的架構升級工作。最近在圖片選擇庫中使用了 paging 作為分頁加載框架。順便閱讀了一下paging的源碼。在這里記錄一下。

初次接除 paging, 可能會一臉懵逼,感覺出來了很多 API, 不知道從哪里下手。我們先對 paging 的組成部分進行一個了解。

首先,我們按照 列表分頁加載 這個行為進行一個基本的劃分,分為 2 個部分, 數據 和 UI, paging 就是按照這個來進行劃分的

數據

數據部分 paging 包括

  • PagedList 一個繼承了 AbstractList 的 List 子類, 包括了數據源獲取的數據
  • DataSource 數據源的概念,分別提供了 PageKeyedDataSource、ItemKeyedDataSource、PositionalDataSource, 在數據源中,我們可以定義我們自己的數據加載邏輯。
UI

UI 部分 paging 提供了一個新的 PagedListAdapter, 在實例化這個 Adapter 的時候,我們需要提供一個自己實現的 DiffUtil.ItemCallback 或者 AsyncDifferConfig

入門

以分頁數據源 PageKeyedDataSource 為例

創建一個數據源, 其中 Language 為 demo 中的實體對象

class LanguageDataSource: PageKeyedDataSource<Int, Language>() 復制代碼

實現三個 override 方法

override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Language>) { } 復制代碼override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Language>) { } 復制代碼override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Language>) { } 復制代碼

著 3 個方法,依次解釋為

  • 初次加載
  • 后面一頁加載
  • 前一頁加載

我們給第一頁數據填充邏輯

LanguageRepository.requestLanguages({datas->if (datas.code == 200) {val languages = datas.dataHandler(Looper.getMainLooper()).post {callback.onResult(languages, null, 1)}} else {}}, {t->Log.e(javaClass.simpleName, "${t.message}") }) 復制代碼

其中 LanguageRepository 是利用 retrofit 請求了一個 Language 對象的列表。 我們調用 callback.onResult 就會刷新 RecyclerView 的視圖

loadAfter 的實現大致與 loadInitial 一致,這里不做贅述。

我們再來看一下 UI 層,我們定義一個 PagedListAdapter

class LanguageAdapter(private val context: Context) : PagedListAdapter<Language, ViewHolder>(languageDiff) 復制代碼

這里我們需要 override 2個方法

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder 復制代碼override fun onBindViewHolder(holder: ViewHolder, position: Int) 復制代碼

在 onBindViewHolder 中, 我們可以通過 getItem(position) 獲取相對于的數據實例去進行 UI 的展示。

接下來是一個比較關鍵的部分,那就是如何連接 DATA 和 UI 這兩部分。

val config = PagedList.Config.Builder().setPageSize(15).setPrefetchDistance(2).setInitialLoadSizeHint(15).setEnablePlaceholders(false).build()val pageList = PagedList.Builder(LanguageDataSource(), config).setNotifyExecutor {Handler(Looper.getMainLooper()).post {it.run()}}.setFetchExecutor(Executors.newFixedThreadPool(2)).build()adapter.submitList(pageList) 復制代碼

在這里, pageList 的 NotifyExecutor 和 FetchExecutor 也是必須設置的。在 Android arch componet 完整的架構中,更推薦使用構建一個 PageList 的 LiveData 的方式。但是不使用也沒有關系,arch compoent 的完整內容在這里不做過多的描述。具體的詳細使用可以查看google的實例源碼

在大致了解了 paging 的組成部分后,我們會開始好奇,那我們到底為什么需要 paging 呢, 他和我們之前普通的使用方式有什么區別呢,我們可以在源碼中尋找到答案。

我們可以在 2 個部分的真正對接處作為切入點進行分析,查看 PagedList.Builder#build() 的源碼:

return PagedList.create(mDataSource,mNotifyExecutor,mFetchExecutor,mBoundaryCallback,mConfig,mInitialKey); 復制代碼

繼續查看

return new ContiguousPagedList<>(contigDataSource,notifyExecutor,fetchExecutor,boundaryCallback,config,key,lastLoad); 復制代碼

跟到這個類的構造方法,可以看到如下邏輯

mDataSource.dispatchLoadInitial(key,mConfig.initialLoadSizeHint,mConfig.pageSize,mConfig.enablePlaceholders,mMainThreadExecutor,mReceiver); 復制代碼

這里以 PageKeyedDataSource 為例, 其他的 DataSource 對象同理

查看 dispatchLoadInital 方法

LoadInitialCallbackImpl<Key, Value> callback =new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver); loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);callback.mCallbackHelper.setPostExecutor(mainThreadExecutor); 復制代碼

這里我們可以看到, loadInitial 就是我們需要在 override 的方法之一。那我們里面調用 callback 的 onResult 方法到底發生了什么呢?

查看 LoadInitialCallbackImpl#onResult() 的源碼,關鍵邏輯如下

mDataSource.initKeys(previousPageKey, nextPageKey); int trailingUnloadedCount = totalCount - position - data.size(); if (mCountingEnabled) {mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position, trailingUnloadedCount, 0)); } else {mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position)); } 復制代碼

查看 dispatchResultToReceiver

繼續查看 onPageResult 方法

我們關注一下 init 時候的邏輯

mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,pageResult.positionOffset, ContiguousPagedList.this); 復制代碼

init 的邏輯很簡單,只有 2 行

init(leadingNulls, page, trailingNulls, positionOffset); callback.onInitialized(size()); 復制代碼

在這里, 我們可以看見關鍵的邏輯

mPages.clear(); mPages.add(page); 復制代碼

這里,和 PageList 綁定的數據就發生了變化。之后我們把 PageList submit 給了 adapter 那么,數據就發生了更新。

初始加載我們看完了,那么,剩下的數據是如何加載的呢

我們反過來看 RecyclerView, 如果我們滑動列表或者其他操作的時候,很自然會調用 adapter 的 bind 方法。那么,我們去查看 PagedListAdapter#getItem 的源碼。

return mDiffer.getItem(position); 復制代碼

查看 PageList 的 loadAround

loadAroundInternal(index); 復制代碼

繼續,

if (mAppendItemsRequested > 0) {scheduleAppend(); } 復制代碼

查看 scheduleAppend 的實現

mBackgroundThreadExecutor.execute(new Runnable() {@Overridepublic void run() {if (isDetached()) {return;}if (mDataSource.isInvalid()) {detach();} else {mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,mMainThreadExecutor, mReceiver);}}}); 復制代碼

這里,我們看到了 dispatchLoadAfter 方法的調用,之后的邏輯和之前的 dispathLoadInitial 就非常的類似了。

最終,會調用到如下邏輯

這里會走 AsyncPagedListDiffer 的 PagedList.Callback 的回調

這里,callback 是和 adapter 關聯起來的。所以會在這里刷新列表。

最后,我們看一下 Adapter 的 submit 方法,最后可以看到這樣的邏輯

我們可以看到 paging 是利用了 DiffUtils 對 RecyclerView 進行刷新的。這樣我們也無需擔心 paging 會存在性能問題。

總結

最后談一下對 paging 的理解。 一般情況下,我們最原始的方式,列表 UI 所在的部分,是需要知道數據的來源等邏輯部分,我們在常見的 mvp 模式中,會對數據和 UI 進行分層。 而 paging 就利用一系列的封裝, 提供了更加通用的 API 調用來做這些事情。更通俗點說,就是實現了分頁加載結構中的 Presenter 層及 Presenter層的下游處理部分。

這種模式,業務的編寫者,可以把 UI 部分的代碼模板化, 只需要關心業務邏輯,并且把業務邏輯中的數據獲取寫在 DataSource 中,使分頁加載的操作解耦程度更高。

總結

以上是生活随笔為你收集整理的深入理解Android Paging分页加载库的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。