Android组件之Paging的使用及原理

2022-10-20  本文已影响0人  安安_660c

流程

● 创建DataSource
● 创建Factory
● 创建Adapter相关类
● 创建LivePagedListBuilder
● 监听LiveData

DataSource

处理数据源相关抽象类,DataSource<Key, Value>Key是用来帮助开发者进行数据的组合以及请求的变化,会在请求开始和过程中传递给开发者,Key的类型和值由开发者决定
Value就是数据源的实体类

有三个默认提供的类:

● PageKeyedDataSource<Key, Value>:
如果页面在加载时插入一个/下一个键,例如:从网络获取社交媒体的帖子,可能需要将nextPage加载到后续的加载中
● ItemKeyedDataSource<Key, Value>:
在需要让使用的数据的item从N条增加到N+1条时使用,一般的请求用这个类可以大部分解决,KEY值传page页数即可
● PositionalDataSource<T>:
如果需要从数据存储的任意位置来获取数据页面。此类支持你从任何位置开始请求一组item的数据集。例如,该请求可能会返回从位置1200条开始的20个数据项,适合用于本地数据源的加载

PageList

从DataSource获取不可变数量的数据,可以通过Config进行各种配置,将数据提交给Adapter进行展示

PageList.Config

对数据如何处理的配置,控制加载多少数据,什么时候加载

PagedList.Config.Builder()
                    .setEnablePlaceholders(false)
                    .setInitialLoadSizeHint(20)
                    .setPageSize(1).build()

● int pageSize:每个页面需要加载多少数量
● int prefetchDistance:滑动剩余多少item时,去加载下一页的数据,默认是pageSize大小
● boolean enablePlaceholders:开启占位符
● int initialLoadSizeHint:第一次加载多少数据量

Placeholders

占位列表,在你的列表未加载前,是否显示占位列表,就像各类新闻app中,没加载之前,列表显示的都是一些默认的灰色的布局,默认开启

优点:
● 支持滚动条
● 不需要loading提示,因为List大小是确定的

缺点:
● 必须确定List的大小
● 需要每个item大小一致
● 需要Adapter触发未加载数据的加载

PagedListAdapter(DiffUtil.ItemCallback<T> diffCallback)
RecyclerView.Adapter的一个实现类,用于当数据加载完毕时,通知 RecyclerView数据已经到达。 RecyclerView就可以把数据填充进来,取代原来的占位元素。
数据变化时,PageListAdapter会接受到通知,交由委托类AsyncPagedListDiffer来处理,AsyncPagedListDiffer是对DiffUtil.ItemCallback<T>持有对象的委托类,AsyncPagedListDiffer使用后台线程来计算PagedList的改变,item是否改变,由DiffUtil.ItemCallback<T>决定

DataSource.Factory<Key, Value>
如何创建DataSource的Factory类,主要工作是创建DataSource

LivePagedListBuilder

根据提供的Factory和PageConfig来创建数据源,返回数据为LiveData<PagedList<Value>>

val pagedList: LiveData<PagedList<Entity>> = LivePagedListBuilder(sourceFactory, pagedListConfig).build()

BoundaryCallback

经常用于与ROOM结合使用时

public abstract static class BoundaryCallback<T> {
        //如果本地数据获取到的数量为 0 时调用
        public void onZeroItemsLoaded() {}
        //一般不做处理
        public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
        // 最后一个item加载时调用
        // 假设ROOM中存了1000条数据,但并不是这1000全部拿出来,而是根据你设置的pagedListConfig来加载数据
        //当滑动的时候 根据pagedListConfig的规则,最后一个item需要被加载时,才会调用该方法
        public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
    }

例子

DataSource:

class ListDataSource : ItemKeyedDataSource<Int,Entity>() {

var page: Int = 1

    override fun loadInitial(params: LoadInitialParams<Int>, callback:  LoadInitialCallback<Entity>) {
        //初始请求数据,必须要同步请求
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Entity>) {
        
        //请求后续数据,异步
         page++

    }
}
    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<V>) {

    }

    override fun getKey(item: Entity): Int {
        return page
    }

Factory:

class ListFactory : DataSource.Factory<Int, Entity>() {

     var sourceLiveData = MutableLiveData<ListDataSource>()

    override fun create(): DataSource<Int, Entity> {
        val dataSource = ListDataSource()
        sourceLiveData.postValue(dataSource)
        return dataSource
    }
}

Adapter:

class ListAdapter() :
        PagedListAdapter<Entity, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<Entity>() {
            override fun areItemsTheSame(oldItem: Entity, newItem: Entity): Boolean =
                    oldItem.name == newItem.name

            override fun areContentsTheSame(oldItem: Entity, newItem: Entity): Boolean =
                    oldItem == newItem
        }) {
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = ListViewHolder.create(parent)

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as ListViewHolder).bind(getItem(position))
    }
}

ViewHolder:

class ListViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
    private val contentTv = itemView.findViewById<TextView>(R.id.contentTv)
    fun bind(entity: WeiboMessageEntity?) {
        contentTv.name = entity?.name
    }

    companion object {
        fun create(parent: ViewGroup): ListViewHolder {
            val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item, parent, false)
            return ListViewHolder(view)
        }
    }
}

LivePagedListBuilder:

val sourceFactory = ListFactory()
val pagedListConfig = PagedList.Config.Builder()
                .setEnablePlaceholders(false)
                .setInitialLoadSizeHint(20*2)
                .setPageSize(20)
                .build()
val pagedList: LiveData<PagedList<Entity>> = LivePagedListBuilder(sourceFactory, pagedListConfig).build()

UI:

viewModel.pagedList.observe(this, Observer {
            listAdapter?.submitList(it)
        })

原理

从使用角度分析,从LivePagedListBuilder开始

  1. LivePagedListBuilder-build(根据Factory和DataSource来构建包含数据源LiveData的PageList)
  2. 创建 ComputableLiveData(创建的时候就会执行mRefreshRunnable),创建后返回LiveData
private static <Key, Value> LiveData<PagedList<Value>> create(...) {
        return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
            ...

            @Override
            protected PagedList<Value> compute() {
                ...
                return mList;
            }
        }.getLiveData();
  1. 执行Runnable,mRefreshRunnable线程完成计算工作后,调用mLiveData.postValue(value), View层的Observer则会接收到结果
public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }

 @VisibleForTesting
    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            boolean computed;
            do {
                computed = false;
                // compute can happen only in 1 thread but no reason to lock others.
                if (mComputing.compareAndSet(false, true)) {
                    // as long as it is invalid, keep computing.
                    try {
                        T value = null;
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            value = compute();
                        }
                        if (computed) {
                            mLiveData.postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
            } while (computed && mInvalid.get());
        }
    };
  1. computed() 计算工作主要是创建PageList,只会计算一次,计算完后会返回List
protected PagedList<Value> compute() {
                @Nullable Key initializeKey = initialLoadKey;
                if (mList != null) {
                    //noinspection unchecked
                    initializeKey = (Key) mList.getLastKey();
                }

                do {
                    if (mDataSource != null) {
                        mDataSource.removeInvalidatedCallback(mCallback);
                    }

                    mDataSource = dataSourceFactory.create();
                    mDataSource.addInvalidatedCallback(mCallback);

                    mList = new PagedList.Builder<>(mDataSource, config)
                            .setNotifyExecutor(notifyExecutor)
                            .setFetchExecutor(fetchExecutor)
                            .setBoundaryCallback(boundaryCallback)
                            .setInitialKey(initializeKey)
                            .build();
                } while (mList.isDetached());
                return mList;
            }
  1. 一系列的创建以及调用工作

● PageList-build-create
● 创建 ContiguousPagedList 或 TiledPagedList(if isContiguous is true) 如果保证数据的item数不会变化,则可以设置这个属性
● 调用 dispatchLoadInitial
● 创建 LoadInitialCallbackImpl
● 调用我们需要编写代码的 loadInitial(如果此时加载数据失败可以调用loadInitial()重新进行请求)
● 调用 callBack.onResult() 返回数据
● 回调至 LoadInitialCallbackImpl

  1. 根据原数据重新创建PageList,调用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);
            }
        }

注意这里的executor,这里就是为什么我们需要在loadInitial使用同步请求的原因
当我们调用刷新方法后,会重新执行一开始初始化的mRefreshRunnable

private final DataSource.InvalidatedCallback mCallback =
    new DataSource.InvalidatedCallback() {
    @Override
    public void onInvalidated() {
    invalidate();
    }
    };

 public void invalidate() {
        ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
    }

final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            boolean isActive = mLiveData.hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    mExecutor.execute(mRefreshRunnable);
                }
            }
        }
    };

在compute的计算方法中,会将pageList重新实例化,会优先调用loadInitial方法进行初始化,实例化后,将结果返回给compute(),
后续在dispatchLoadInitial方法中会进行postExecutor的设置,如果loadInitial方法是异步的,postExecutor就会优先设置

如果不进行同步操作,会导致数据无法显示或者时刷新操作时提前清空了数据,导致显示不正常

  1. 回到上一步,调用dispatchResultToReceiver后会执行 mReceiver.onPageResult, mReceiver就是之前创建的ContiguousPagedList或TiledPagedList
    onPageResult方法中根据PageResult的不同状态处理不同情况
private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
        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);
            }
        }
    };

void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
            @NonNull Callback callback) {
        init(leadingNulls, page, trailingNulls, positionOffset);
        callback.onInitialized(size());
    }

第一次显示列表状态为 PageResult.INIT,后续加载数据的状态为PageResult.APPEND,进行一些回调工作(onChanged,onInserted,onRemoved等)

  1. 由于在loadInitial方法中,我们的请求时同步的,所以会在数据处理结束后,View层的LiveData才会接受到数据,接受到数据后调用adapter.submitList(it)
  2. 列表初始显示、滑动或者notifyDataSetChanged时,会调用Adapter的getItem,然后委托给AsyncPagedListDiffer的getItem
public T getItem(int index) {
        if (mPagedList == null) {
            if (mSnapshot == null) {
                throw new IndexOutOfBoundsException(
                        "Item count is zero, getItem() call is invalid");
            } else {
                return mSnapshot.get(index);
            }
        }

        mPagedList.loadAround(index);
        return mPagedList.get(index);
    }


    public void loadAround(int index) {
        mLastLoad = index + getPositionOffset();
        loadAroundInternal(index);
        ...
    }

    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();
        }
    }

mPagedList.loadAround(index)-loadAroundInternal(这里根据设置的prefetchDistance设置加载到多少item时去加载新数据)
schedulePrepend和scheduleAppend是分别调用before和after的两个方法

loadBefore和loadAfter的callBack调用在onPageResult方法中不再调用mStorage.init而是mStorage.appendPage

void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
        final int count = page.size();
        ...
        callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
                changedCount, addedCount);
    }

public void onPageAppended(int endPosition, int changedCount, int addedCount) {
       ...

        // finally dispatch callbacks, after append may have already been scheduled
        notifyChanged(endPosition, changedCount);
        notifyInserted(endPosition + changedCount, addedCount);
    }

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);
                }
            }
        }
    }

void notifyChanged(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.onChanged(position, count);
                }
            }
        }
    }

回调至AsyncPagedListDiffer的PagedList.Callback

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);
        }
    };

则是和Adapter绑定的callBack

public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
            @NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mUpdateCallback = new AdapterListUpdateCallback(adapter);
        mConfig = new AsyncDifferConfig.Builder<T>(diffCallback).build();
    }

这样recyclerView就能接受到我们的数据变化

转载于:https://www.yuque.com/mikaelzero/blog/epr7ut

上一篇下一篇

猜你喜欢

热点阅读