Android Jetpack架構組件之 Paging(使用、源碼篇)

一、前言

最近簡單看了下google推出的框架Jetpack,感受此框架的內容能夠對平時的開發有很大的幫助,也能夠解決不少開發中的問題,對代碼的邏輯和UI界面實現深層解耦,打造數據驅動型UI界面。java

Android Architecture組件是Android Jetpack的一部分,它們是一組庫,旨在幫助開發者設計健壯、可測試和可維護的應用程序,包含一下組件:android

上述時Android Architecture所提供的架構組件,本文主要從使用和源碼的角度分析Paging組件數據庫

二、Paging簡介

  • 分頁庫概述
  1. 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
  1. 每一個PagedList實例都會加載應用程序DataSource的最新數據
  2. 數據從應用程序的後端或數據庫流入PagedList對象
  • UI:PagedList類與 PagedListAdapter一塊兒加載 數據到RecyclerView中
  • Paging組件好處
  1. 分頁庫使您能夠更輕鬆地在應用程序中的RecyclerView逐步和優雅地加載數據
  2. 數據請求消耗的網絡帶寬更少,系統資源更少
  3. 即便在數據更新和刷新期間,應用程序仍會繼續快速響應用戶輸入

三、Paging組件的使用

3.一、添加依賴api

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.二、Paging使用步驟bash

  • 定義分頁配置
  1. Page size :頁面大小即每次加載時加載的數量
  2. Prefetch distance:預取距離,給定UI中最後一個可見的Item,超過這個item應該預取一段數據
  3. 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.三、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更新數據:架構

  1. areItemTheSame():是否爲同一個Item
  2. 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)複製代碼
  • Room和PagedList配合使用有點
  1. Room數據庫查詢數據做爲PagedList的數據源,實現列表的自動加載和下拉加載更多數據
  2. 當數據庫改變時會自動回調觀察者從而刷新界面

3.四、使用RxJava2觀察分頁數據app

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.五、Paging使用的注意事項

  • 構建可觀察者列表
  1. 觀察對象的建立通常在ViewModel中,UI代碼觀察ViewModel中的LiveData<PagedList>對象,實現列表和內容的聯繫
  2. 向LivePagedListBuilder 或RxPagedListBuilder 傳入DataSource.Factory的實例,建立可觀察的PagedList對象
  3. 一個DataSource對象爲一個PagedList加載頁面
  4. 工廠類建立新實例 PagedList以響應內容更新
  • 選擇正確的數據類型

Paging組件除了自身建立的DataSource以及Room的配合使用外,還支持自定以實現DataSource,組件提供了一下三種模式的DataSource,咱們在使用時只需根據本身的需求選擇什麼時候的實現子類:

  1. PageKeyedDataSource:若是頁面須要實現上一頁、下一頁,須要將請求的Token傳遞到下一步
  2. ItemKeyedDataSource:程序須要根據上一條數據信息(ID)獲取下一條數據時
  3. PositionalDataSource:須要從數據存儲中選擇的任何位置獲取數據頁;例如,請求可能返回以位置1200開頭的20個數據項
  • 數據無效時通知
  1. 在加載數據時,不少都會配合數據刷新和數據時效性,使用分頁庫時,數據層由表或行變得陳舊時通知應用程序的其餘層,使用DataSource.invalidate()通知數據刷新
  2. 應用的UI可使用下拉刷新模型觸發數據失效功能
  • 內容更新

構建可觀察 PagedList對象時,須要考慮內容的更新方式,按數據的來源分爲本地加載和網絡加載:

  1. 從Room數據庫加載數據,在數據路內容發生改變時,則會自動將更新推送到您應用的UI
  2. 若是分頁網絡API數據,則一般會進行用戶交互;例如:「滑動刷新」做爲當前DataSource失效,並請求新數據的信號

四、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,而後重寫四個方法:

  1. getKey:記錄當前加載時最新的Item的Key
  2. loadInitial:執行每次進入界面的第一次加載,即數據刷新
  3. loadBefore:在指定LoadParams中Key以前加載列表數據
  4. 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的組件的成員的職責

  1. DataSource:數據的來源,須要設置初始化和加載更多的邏輯以及每次加載變化的Key
  2. DataSource。Factory:工廠類提供DataSource的實例
  3. PagedList.Config:PagedList的配置類,設置初始化數量和界面預取數量等
  4. PagedList:數據集散中心;根據須要向DataSource索取加載數據,並將獲得的數據傳遞到PagedListAdapter
  5. PagedListAdapter:數據適配器,這裏處了起到普通界面加載適配器的做用外,更重要的是根據滑動顯示的座標,起到了肯定何時要求向PagedList加載數據(後面源碼分析)
  6. DiffUtil.ItemCallback:判斷數據是否發生改變以相應界面的更新

五、源碼分析

不管是使用Room仍是自定義Datasource,Paging組件的開始執行都是從建立LiveData<PagedList>開始的,因此咱們源碼的分析也從LiveData<PagedList>的建立開始

5.一、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中執行了一些主要操做:

  1. 建立DataSource的刷新回調
  2. 調用傳入的Factory的create()建立DataSource實例
  3. 建立並返回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()方法:

  1. PositionalDataSource
@Override
    boolean isContiguous() {
        return false;
    }複製代碼
  1. 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.二、數據初始化加載

  • 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);
        }複製代碼

在構造函數中執行一下邏輯:

  1. 建立PagedStore實例,主要根據滑動的位置顯示是否要繼續加載數據
  2. 調用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.三、數據的顯示

在自定義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的三個方法:

  1. loadInitial:對應初始化狀態PageResult.INIT
  2. loadBefore:對應初始化狀態PageResult.PREPEND
  3. 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);
                }
            }
        }
    }複製代碼

到此咱們能夠得出結論

  1. 加載的數據保存在PagedStorage中,並記錄了加載的位置信息
  2. 加載完成後根據數據的變化,回調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.四、數據下拉加載

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進行封裝,好了這篇真的花了好長時間,感受整個下午都在寫這個,但願對本身對你們都有所幫助!

相關文章
相關標籤/搜索