Android Jetpack架構元件之 Paging(使用、原始碼篇)
1、前言
最近簡單看了下google推出的框架Jetpack,感覺此框架的內容可以對平時的開發有很大的幫助,也可以解決很多開發中的問題,對程式碼的資料邏輯和UI介面深層解耦,實現資料驅動型的ui。
Android Architecture元件是Android Jetpack的一部分,它們是一組庫,旨在幫助開發者設計健壯、可測試和可維護的應用程式,包含一下元件:
上述時Android Architecture所提供的架構元件,本文主要從使用和原始碼的角度分析Paging元件
2、Paging簡介
- 分頁庫概述
- Paging Library中DataSource,PagedList,PagedAdapter三者之間的關係以及資料載入到資料展示的流程
比如當一條新的item插入到資料庫,DataSource會被初始化,LiveData後臺執行緒就會建立一個新的PagedList。這個新的PagedList會被髮送到UI執行緒的PagedListAdapter中,PagedListAdapter使用DiffUtil在對比現在的Item和新建Item的差異。當對比結束,PagedListAdapter通過呼叫RecycleView.Adapter.notifyItemInserted()將新的item插入到適當的位置
- PagedList:是Paging Library的關鍵元件,它是一個非同步載入應用程式資料塊或頁面的集合
- Data
- 每個PagedList例項都會載入應用程式DataSource的最新快照
- 資料從應用程式的後端或資料庫流入PagedList物件
- UI:PagedList類與 PagedListAdapter一起載入 資料到RecyclerView中
- Paging元件好處
- 分頁庫使您可以更輕鬆地在應用程式中的RecyclerView逐步和優雅地載入資料
- 資料請求消耗的網路頻寬更少,系統資源更少
- 即使在資料更新和重新整理期間,應用程式仍會繼續快速響應使用者輸入
3、Paging元件的使用
3.1、新增依賴
def paging_version = "1.0.0" implementation "android.arch.paging:runtime:$paging_version" testImplementation "android.arch.paging:common:$paging_version" implementation "android.arch.paging:rxjava2:1.0.0-rc1"
3.2、Paging使用步驟
- 定義分頁配置
- Page size :頁面大小即每次載入時載入的數量
- Prefetch distance:預取距離,給定UI中最後一個可見的Item,超過這個item應該預取一段資料
- UI佔位符:setEnablePlaceholders()
val myPagingConfig = PagedList.Config.Builder() // 分頁設定
.setPageSize(50)
.setPrefetchDistance(150)
.setEnablePlaceholders(true)
.build()
- 使用分頁配置建立LiveData<PagedList> ,傳入DataSource.Factory,用於建立DataSource,從DataSource中載入資料到PagedList中
val concertList = LivePagedListBuilder(myConcertDataSource, myPagingConfig)
.setFetchExecutor(myExecutor)
.build()
- 觀察LiveData<PagedList>,在資料改變時呼叫介面卡重新整理資料
viewModel.concertList.observe(this, { pagedList ->
adapter.submitList(pagedList) })
3.3、Paging和Room的使用
- 在Room的@Dao中新增查詢方法
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll() : DataSource.Factory<Int,User> // 返回DataSOurce.Factory配合PagingList使用
}
- 在ViewModel中查詢資料庫並建立LiveData<PagedList>
class ViewModelUser(application: Application) : AndroidViewModel(application) {
val dao = UserDataBase.getInstence(application)?.getDao()
////傳入Room返回的DataSource.Factory
var liveArray : LiveData<PagedList<User>> =
LivePagedListBuilder(dao!!.getAll(),PagedList.Config.Builder()
.setPageSize(10)
.setPrefetchDistance(10)
.setEnablePlaceholders(true)
.build()).build()
}
- 建立PagedListAdapter的實現類,載入並顯示資料
class Adapter : PagedListAdapter<User, Adapter.UserViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return UserViewHolder(layoutInflater.inflate(R.layout.item,parent,false))
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = getItem(position)
if (user != null){
holder.bind(user)
}else{
holder.clear()
}
}
companion object {
val diffCallback = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User?, newItem: User?): Boolean {
return oldItem?.id == newItem?.id
}
override fun areContentsTheSame(oldItem: User?, newItem: User?): Boolean {
return oldItem == newItem
}
}
}
class UserViewHolder(val view : View) :
RecyclerView.ViewHolder(view){
private val tvId : TextView = view.findViewById(R.id.tvId)
private val tvName : TextView = view.findViewById(R.id.tvName)
fun bind(user: User){
tvId.text = user.id.toString()
tvName.text = user.name
}
fun clear(){
tvName.text = null
tvId.text = null
}
}
這裡說一下傳入的DiffUtil.ItemCallback<> 例項,當資料載入到PagedListAdapter時,會回撥DiffUtil.ItemCallback中兩個抽象方法,確認資料和之前是否發生了改變,如果改變則呼叫Adapter更新資料:
- areItemTheSame():是否為同一個Item
- areContentsTheSame():資料內容是否發生變化
- 新增LiveData<PagedList>觀察者
val viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java!!)
val recyclerView = findViewById(R.id.concert_list)
val adapter = ConcertAdapter()
viewModel.concertList.observe(this, { pagedList ->
... })
recyclerView.setAdapter(adapter)
這裡使用ViewModel中儲存載入到的LivaData<PagedList>(關於ViewModel點選檢視另一篇Android Jetpack架構元件之 ViewModel (原始碼篇)),為LiveData新增觀察者,當資料發生改變時回撥方法,將資料傳送到PagedListAdapter中更新介面UI
- 將 LiveData<PagedList> 例項連線到 PagedListAdapter
adapter.submitList(pagedList)
- oom和PagedList配合使用有點
- Room資料庫查詢資料作為PagedList的資料來源,室現列表的自動載入和下拉載入更多資料
- 當資料庫改變時會自動回撥觀察者從而重新整理介面
3.4、使用RxJava2觀察分頁資料
Paging除了支援LiveData載入資料外,還支援RxJava2觀察資料,通過建立一個Observable或Flowable例項,觀察資料的改變併發送資料到Adapter,下面用Flowable代替LiveData:
var liveArray : Flowable<PagedList<User>> = RxPagedListBuilder(dao!!.getAll(),PagedList.Config.Builder()
.setPageSize(10)
.setPrefetchDistance(10)
.setEnablePlaceholders(true)
.build()).buildFlowable(BackpressureStrategy.LATEST)
和建立LiveData一樣傳入DataSource.Factory和PagedList.Config,並配置背壓策略,建立Flowable後像RxJava正常使用一樣訂閱觀察者即可獲取資料:
viewModel.concertList.subscribe({
flowableList -> adapter.submitList(flowableList)
3.5、Paging使用的注意事項
- 構建可觀察者列表
- 觀察物件的建立一般在ViewModel中,UI程式碼觀察ViewModel中的LiveData<PagedList>物件,實現列表和內容的聯絡
- 向LivePagedListBuilder 或RxPagedListBuilder 傳入DataSource.Factory的例項,建立可觀察的PagedList物件
- 一個DataSource物件為一個PagedList載入頁面
- 工廠類建立新例項 PagedList以響應內容更新
- 選擇正確的資料型別
Paging元件除了自身建立的DataSource以及Room的配合使用外,還支援自定以實現DataSource,元件提供了一下三種模式的DataSource,我們在使用時只需根據自己的需求選擇何時的實現子類:
- PageKeyedDataSource:如果頁面需要實現上一頁、下一頁,需要將請求的Token傳遞到下一步
- ItemKeyedDataSource:程式需要根據上一條資料資訊(ID)獲取下一條資料時
- PositionalDataSource:需要從資料儲存中選擇的任何位置獲取資料頁;例如,請求可能返回以位置1200開頭的20個數據項
- 資料無效時通知
- 在載入資料時,很多都會配合資料重新整理和資料時效性,使用分頁庫時,資料層由表或行變得陳舊時通知應用程式的其他層,使用DataSource.invalidate()通知資料重新整理
- 應用的UI可以使用滑動重新整理模型觸發此資料失效功能
- 內容更新
構建可觀察 PagedList物件時,需要考慮內容的更新方式,按資料的來源分為本地載入和網路載入:
- 從Room資料庫載入資料,在資料路內容發生改變時,則會自動將更新推送到您應用的UI
- 如果分頁網路API資料,則通常會進行使用者互動;例如:“滑動重新整理”作為當前DataSource失效,並請求新資料的訊號
4、Paging自定義DataSource
在開發過程中,除了查詢本地資料庫和Room配合使用不需要自定義DataSource外,其他載入網路資料時可能都需要自定義來控制資料的獲取,下面以實現ItemKeyedDataSource為例分析使用自定義DataSource
- 實現ItemKeyedDataSource的子類
class ItemDataSource : ItemKeyedDataSource<Int,ArticleBean>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<ArticleBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<ArticleBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<ArticleBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getKey(item: ArticleBean): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
實現ItemKeyedDataSource的子類,這裡傳入的兩個泛型:Key表示每一個Item的標誌,這裡使用Int即每個Item的id,Value :載入的資料,這裡傳入資料型別Bean,然後重寫四個方法:
- getKey:記錄當前載入時最新的Item的Key
- loadInitial:執行每次進入介面的第一次載入,即資料重新整理
- loadBefore:在指定LoadParams中Key之前載入列表資料
- loadAfter:在指定LoadParams中Key之後載入列表資料
實現方法中載入資料的邏輯:
override fun getKey(item: M) = item.id
/**
* 初始化時的載入
*/
override fun loadInitial(params: LoadInitialParams<T>, callback: LoadInitialCallback<M>) {
api.getArticleList(0) //初始化載入第一頁
.compose(RxHelper.rxSchedulerHelper())
.subscribe({
callback.onResult(it?.data!!.datas!!)
}, {
refreshFailed(it.message, params, callback)
})
}
/**
* 載入更多
*/
override fun loadAfter(params: LoadParams<T>, callback: LoadCallback<M>) {
api.getArticleList(page) // 下拉載入更多資料
.compose(RxHelper.rxSchedulerHelper())
.subscribe({
callback.onResult(it.data!!.datas!!)
}, {
networkFailed(it.message, params, callback)
})
}
- 建立具體DataSource.Factory實現類,用於建立自定義的DataSource
class ItemDataSourceFactory() :
DataSource.Factory<Int, ArticleBean>() {
val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
override fun create(): DataSource<Date, Concert> {
val source = ItemDataSource()
sourceLiveData.postValue(source)
return source
}
}
實現了DataSource.Factory,重寫onCreate()方法建立DataSource例項,這裡使用了LiveData包裝了DataSource的例項,這樣做的好處就是可以對建立的DataSource的時效性進行控制,例如我們在重新整理的時候只需呼叫
ItemDataSourceFactory.sourceLivaData.value?.invalidate()
這裡會通知DataSource的資料失效,就會重新初始化載入資料
- 使用自定義的DataSource和Factory
使用的方式和前面的一致,唯一的區別就是Factory的來源不同,前面Factory是從資料庫Room中查詢生成,這裡直接傳入實現的Factory的例項即可:
val concertList = LivePagedListBuilder(
ItemDataSourceFactory, 20).build()
到此Paging元件執行時會根據傳遞的Factory建立DataSource,然後呼叫DataSource中重寫的方法初始化和載入資料到PagedList,然後使用資料重新整理介面
- 總結
從上面的使用可以看出Paging的元件的成員的職責
- DataSource:資料的來源,需要設定初始化和載入更多的邏輯以及每次載入變化的Key
- DataSource。Factory:工廠類提供DataSource的例項
- PagedList.Config:PagedList的配置類,設定初始化數量和介面預取數量等
- PagedList:資料集散中心;根據需要向DataSource索取載入資料,並將得到的資料傳遞到PagedListAdapter
- PagedListAdapter:資料介面卡,這裡處了起到普通介面載入介面卡的作用外,更重要的是根據滑動顯示的座標,起到了確定什麼時候要求向PagedList載入資料(後面原始碼分析)
- DiffUtil.ItemCallback:判斷資料是否發生改變以相應介面的更新
5、原始碼分析
無論是使用Room還是自定義Datasource,Paging元件的開始執行都是從建立LiveData<PagedList>開始的,所以我們原始碼的分析也從LiveData<PagedList>的建立開始
5.1、LiveData<PagingList<T>>
- 建立LiveData<PagingList<T>>
LiveData<PagingList<T>>的建立過程如上圖,使用LivePagedListBuilder配置Factory和Config,然後呼叫build建立例項,在build方法中直接呼叫了create()方法建立LiveData
- create()
@AnyThread
@NonNull
private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
@NonNull final Executor notifyExecutor,
@NonNull final Executor fetchExecutor) {
// 建立ComputableLiveData類
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
@Override
protected PagedList<Value> compute() { // 重寫compute方法
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
//noinspection unchecked
initializeKey = (Key) mList.getLastKey();
}
do {
if (mDataSource != null) {
mDataSource.removeInvalidatedCallback(mCallback);
}
// 從Builder中傳入的Factory中建立DataSource
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
// 建立PagedList
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
return mList;
}
}.getLiveData();
}
在create()中直接返回了ComputableLiveData的例項,在ComputableLiveData例項重寫的compute中執行了一些主要操作:
- 建立DataSource的重新整理回撥
- 呼叫傳入的Factory的create()建立DataSource例項
- 建立並返回PagedList例項
- PagedList.build() & PagedList.create()
先來看一下PagedList的建立過程,在PagedList.build()中呼叫了PagedList.create(),所以真正的建立是在create()中發生的,
private static <K, T> PagedList<T> create(...) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
......
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
} else {
return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
}
從程式碼中看出根據 條件(dataSource.isContiguous() || !config.enablePlaceholders)的不同分別建立ContiguousPagedList和TiledPagedList,其實這裡就是區分上面的三個自定義DataSource的型別,如果是PositionalDataSource建立TiledPagedList,其他的返回ContiguousPagedList,我們依次檢視三個DataSource中的isContiguous()方法:
- PositionalDataSource
@Override
boolean isContiguous() {
return false;
}
- ItemKeyedDataSource和PageKeyedDataSource都繼承與ContiguousDataSource,只檢視ContiguousDataSource中
@Override
boolean isContiguous() {
return true;
}
- ComputableLiveData
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
帶著對ComputableLiveData作用和何時執行compute這兩個疑問,檢視ComputableLiveData原始碼,發現在ComputableLiveData的建構函式中建立LiveData例項,並在onActive()中執行了Runnable介面(關於onActive()請檢視Android Jetpack架構元件之 LiveData),下面檢視Runnable介面中執行了哪些邏輯:
@VisibleForTesting
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
boolean computed;
do {
computed = false;
others.
if (mComputing.compareAndSet(false, true)) {
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
value = compute(); // 呼叫了compuet建立了PagedList
}
if (computed) {
mLiveData.postValue(value); // 設定LiveData的值
}
} finally {
mComputing.set(false);
}
}
} while (computed && mInvalid.get());
}
};
在Runnable中呼叫了ComputableLiveData的compute()方法建立了PagedList,所以此處的Value就是PagedList,然後為mLiveData初始化賦值PagedList,細心的同學會留意到,在上面的create()方法最後一句呼叫了getLiveData()獲取到的就是ComputableLiveData建構函式中建立的LIveData
@SuppressWarnings("WeakerAccess")
@NonNull
public LiveData<T> getLiveData() {
return mLiveData;
}
到此LiveData<PagedList>的建立就完成了。
5.2、資料初始化載入
- ContiguousPagedList
從上面的執行過程中,我們知道當我們自定義實現ItemKeySource時,建立的PagedList實際為ContiguousPagedList,檢視ContiguousPagedList建構函式原始碼:
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
在建構函式中執行一下邏輯:
- 建立PagedStore例項,主要根據滑動的位置顯示是否要繼續載入資料
- 呼叫DataSource.dispatchLoadInitial方法,此時使用的時ItermKeyDataSource的dispatchLoadInitial方法
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
LoadInitialCallbackImpl<Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
在ItermKeyDataSource的dispatchLoadInitial()方法中呼叫了抽象函式loadInitial(),根據前面的學習我們知道在oadInitial()中設定了初始化的網路請求,到此實現了Paging元件初始化資料的載入;
5.3、資料的顯示
在自定義ItemDataSource的loadInitial()中載入資料後,呼叫了callback.onResult(it?.data!!.datas!!)方法,此處的callback是LoadInitialCallback的實現類LoadInitialCallbackImpl,在onResult()方法中又呼叫了LoadCallbackHelper.dispatchResultToReceiver()
- LoadCallbackHelper.dispatchResultToReceiver()
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
mReceiver.onPageResult(mResultType, result);
}
});
} else {
mReceiver.onPageResult(mResultType, result);
}
}
在dispatchResultToReceiver()方法中,呼叫PageResult.Receiver.onPageResult()方法,這裡的mReceiver是在呼叫 mDataSource.dispatchLoadInitial()時傳入的最後一個引數,他的實現在ContiguousPagedList中:
private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
// Creation thread for initial synchronous load, otherwise main thread
// Safe to access main thread only state - no other thread has reference during construction
@AnyThread
@Override
public void onPageResult(@PageResult.ResultType int resultType,
@NonNull PageResult<V> pageResult) {
List<V> page = pageResult.page;
if (resultType == PageResult.INIT) {
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
// Because the ContiguousPagedList wasn't initialized with a last load position,
// initialize it to the middle of the initial load
mLastLoad =
pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
}
} else if (resultType == PageResult.APPEND) {
mStorage.appendPage(page, ContiguousPagedList.this);
} else if (resultType == PageResult.PREPEND) {
mStorage.prependPage(page, ContiguousPagedList.this);
} else {
throw new IllegalArgumentException("unexpected resultType " + resultType);
}
}
}
};
在onPageResult()方法中根據resultType的型別執行操作,PageResult的三個資料型別分別對應者ItemKeyDataSource的三個方法:
- loadInitial:對應初始化狀態PageResult.INIT
- loadBefore:對應初始化狀態PageResult.PREPEND
- loadAfter:對應初始化狀態PageResult.APPEND
此出分析初始化,回撥的型別為PageResult.INIT,呼叫了PagedStorage的init()方法:
void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
@NonNull Callback callback) {
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
}
在init()方法中首先呼叫另一個init()方法記錄載入的位置,並儲存載入的資料,主要用於控制下拉載入,這部分我們稍後分析,然後呼叫callback.onInitialized(),在onInitialzed()方法中呼叫了notifyInserted(),在notifyInserted()中遍歷mCallbacks回撥callback的onInserted()
public void onInitialized(int count) {
notifyInserted(0, count);
}
void notifyInserted(int position, int count) {
if (count != 0) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
Callback callback = mCallbacks.get(i).get();
if (callback != null) {
callback.onInserted(position, count);
}
}
}
}
到此我們可以得出結論
- 載入的資料儲存在PagedStorage中,並記錄了載入的位置資訊
- 載入完成後根據資料的變化,回撥callback.onInserted()通知資料改變的數量和位置
那CallBack是從哪來的呢?應該是哪裡需要哪裡才會註冊回撥,想想資料位置的變化在哪個地方能用得著,哪個地方優惠根據position和count處理呢?答案就時Adapter
- PagedListAdapter
在前面的例項中,使用submitList()設定資料,而submiList()直接呼叫了mDiffer.submitList(pagedList)
public void submitList(final PagedList<T> pagedList) {
if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
mPagedList = pagedList;
pagedList.addWeakCallback(null, mPagedListCallback);
return;
}
}
此處呼叫了addWeakCallback()新增Callback例項mPagedListCallback,
private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
@Override
public void onInserted(int position, int count) {
mUpdateCallback.onInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mUpdateCallback.onRemoved(position, count);
}
@Override
public void onChanged(int position, int count) {
// NOTE: pass a null payload to convey null -> item
mUpdateCallback.onChanged(position, count, null);
}
};
mPagedListCallback的onInserted()直接回調mUPdateCallback.onInserted(),在mUPdateCallback.onInserted()中直接呼叫Adapter的notifyItemRangeInserted(position, count)實現資料更新
5.4、資料下拉載入
Paging的好處之一就是自動幫我們實現了下拉載入的操作,其時他的實現是依靠adapter的滑動位置,根本的邏輯和平時自己寫的滑動到底載入資料大致一致,都是根據可見position和資料量的比較觸發載入,在PagingAdapter中的getItem()中直接呼叫mDiffer.getItem(position),
public T getItem(int index) {
mPagedList.loadAround(index); // 呼叫載入資料
return mPagedList.get(index);
}
在getItem()中處了獲取到資料以外,還呼叫了mPagedList.loadAround(index)去載入資料,loadAround()方法中有呼叫了loadAroundInternal()
@MainThread
@Override
protected void loadAroundInternal(int index) {
int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
int appendItems = index + mConfig.prefetchDistance
- (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
schedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
if (mAppendItemsRequested > 0) {
scheduleAppend();
}
}
根據現在顯示的index和設定的Config計算需要請求的數量,呼叫scheduleAppend()載入更多資料,
@MainThread
private void scheduleAppend() {
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
//呼叫DataSource的載入更多方法
mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
}
是不是有發現了熟悉的身影,還記得初始化時呼叫的mDataSource.dispatchLoadInitial()嗎?這裡呼叫mDataSource.dispatchLoadAfter()方法,呼叫我們實現的loadAfter()載入更多資料,之後資料的顯示都和初始化一致將PageResult.INIT換成PageResult.APPEND然後想Adapter中追加資料;
到此整個Paging的執行邏輯和原理都分析完了,從整體的流程看架構的設計還是有他獨特的魅力的,架構的內涵讀者自己體味,下面還有一點,我們知道DataSource的重新整理是從呼叫Invalidate()開始的,有沒有相過是如何實現的
- Invalidate()
@AnyThread
public void invalidate() {
if (mInvalid.compareAndSet(false, true)) {
for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
callback.onInvalidated();
}
}
}
呼叫InvalidatedCallback 的onInvalidate(),這裡的InvalidatedCallback其實在我們程式碼分析的第一步就添加了,還記得嗎?在建立PageList時,呼叫了Factory.create(),之後就給DataSource添加了CallBack()
// 建立CallBack
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
// 新增CallBack
mDataSource.addInvalidatedCallback(mCallback);
- invalidate()
public void invalidate() {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
}
@VisibleForTesting
final Runnable mInvalidationRunnable = new Runnable() {
@MainThread
@Override
public void run() {
boolean isActive = mLiveData.hasActiveObservers();
if (mInvalid.compareAndSet(false, true)) {
if (isActive) {
mExecutor.execute(mRefreshRunnable);
}
}
}
};
在invalidate()中執行了mInvalidationRunnable 中的run(),run()方法中有重新執行了mRefreshRunnable,還記的mRefreshRunnable執行了什麼嗎?對就是衝新建立了DataSource和PagedLIst,然後衝新載入資料,然後上面所有過程再來一次!!
本篇時整個元件的最後一篇其實也是最長的一篇(因為我最後寫的),,因為Paging元件也是Jetpack元件中比較複雜的一個,使用頻率也非常高,後期會針對自定義DataSource進行封裝,好了這篇真的花了好長時間,感覺整個下午都在寫這個,希望對自己對大家都有所幫助!