ItemKeyedDataSource 使用注意事项

Paging简介

Paging主要由三个部分组成:DataSource PageList PageListAdapter。
DataSource<Key, Value>从字面意思理解是一个数据源,其中key对应加载数据的条件信息,Value对应加载数据的实体类。
DataSource是一个抽象类,但是我们不能直接继承它实现它的子类。但是Paging库里提供了它的三个子类供我们继承用于不同场景的实现。

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

测试例子

PagedList配置如下

 private PagedList.Config config = new PagedList.Config.Builder()
            .setInitialLoadSizeHint(10) // 配置 初始化 加载条数
            .setPageSize(10) // 设置 每页 加载条数
            .setPrefetchDistance(2)
            .setMaxSize(20)
            .setEnablePlaceholders(false)
            .build();
            
   private LiveData<PagedList<Cheese>> livePagedList = new LivePagedListBuilder<Integer,Cheese>(factory,config)
            .setInitialLoadKey(0)//  指定初始化加载的key
            // .setFetchExecutor()// 指定线程执行异步操作
            .setBoundaryCallback(new CheeseBoundaryCallback())
            .build();            

场景描述:
数据库默认有100条数据。初始化加载0~10条数据,PagedList最多存储20条数据。在滑动在距离上边缘2条和下边缘2条的时候加载更多数据。
运行现象:
滑动过程中容易出现只能加载20条数据,超过20条的数据无法加载。

解决方案

 private PagedList.Config config = new PagedList.Config.Builder()
            .setInitialLoadSizeHint(10) // 配置 初始化 加载条数
            .setPageSize(10) // 设置 每页 加载条数
            .setPrefetchDistance(2)
            .setMaxSize(10 *3) // 配置pagedList最大加载条数为3倍的PageSize 
            .setEnablePlaceholders(false)
            .build();

关于解决方案的思路是:思路是保证loadBefore,loadInitial,loadAfter三个回调获取到的数据都可以完整的保存在PageList里面。

初始化流程简介

由于遇到了以上的问题,因此,对Paging如果更新RecyclerView,已经实现加载更多的流程有了兴趣,因此,对代码分析如下。
使用PagedList一般是将PagedList存储在ViewHolder中,因此ViewHolder的代码一般如下

public class CheeseViewModel extends ViewModel{
	private LiveData<PagedList<Cheese>> livePagedList = new LivePagedListBuilder<Integer,Cheese>(factory,config)
            .setInitialLoadKey(0)//  指定初始化加载的key
            // .setFetchExecutor()// 指定线程执行异步操作
            .setBoundaryCallback(new CheeseBoundaryCallback())
            .build();
}

根据源码可以得知,最终调用的代码是

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){
            		return new ContiguousPagedList<>(contigDataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    key,
                    lastLoad);
            	}
            }

在ContiguousPagedList构造函数中调用dispatchLoadInitial方法。
dispatchLoadInitial 即回调到ItemKeyedDataSource的 dispatchLoadInitial 函数中

public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value>{
	@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 是抽象方法,需要在子类实现
        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
    }
}

抽象方法在继承ItemKeyedDataSource的子类实现,源码如下

public class MyItemKeyedDataSource extends ItemKeyedDataSource<Integer,Cheese>{
	    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) {
		...
		// 获取cheeseList,通过回调传递给ItemKeyedDataSource
        callback.onResult(cheeseList);
    }
}

LoadInitialCallback 在ItemKeyedDataSource的实现如下

public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value>{
	static class LoadInitialCallbackImpl<Value> extends LoadInitialCallback<Value> {
		public void onResult(@NonNull List<Value> data){
		if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
				// 调用核心的Receiver
                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
            }
		}
	}
}

核心的ContiguousPagedList#mReceiver如下,PageResult.Receiver实现RecyclerView item的初始化已经加载更新。

class ContiguousPagedList<K, V> extends PagedList<V> implements Callback{
	    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) {
            if (pageResult.isInvalid()) {
                detach();
                return;
            }

            if (isDetached()) {
                // No op, have detached
                return;
            }

            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 we end up trimming, we trim from side that's furthest from most recent access
                boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange();

                // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
                boolean skipNewPage = mShouldTrim
                        && mStorage.shouldPreTrimNewPage(
                                mConfig.maxSize, mRequiredRemainder, page.size());

                if (resultType == PageResult.APPEND) {
                    if (skipNewPage && !trimFromFront) {
                        // don't append this data, drop it
                        mAppendItemsRequested = 0;
                        mAppendWorkerState = READY_TO_FETCH;
                    } else {
                        mStorage.appendPage(page, ContiguousPagedList.this);
                    }
                } else if (resultType == PageResult.PREPEND) {
                    if (skipNewPage && trimFromFront) {
                        // don't append this data, drop it
                        mPrependItemsRequested = 0;
                        mPrependWorkerState = READY_TO_FETCH;
                    } else {
                        mStorage.prependPage(page, ContiguousPagedList.this);
                    }
                } else {
                    throw new IllegalArgumentException("unexpected resultType " + resultType);
                }

                if (mShouldTrim) {
                    if (trimFromFront) {
                        if (mPrependWorkerState != FETCHING) {
                            if (mStorage.trimFromFront(
                                    mReplacePagesWithNulls,
                                    mConfig.maxSize,
                                    mRequiredRemainder,
                                    ContiguousPagedList.this)) {
                                // trimmed from front, ensure we can fetch in that dir
                                mPrependWorkerState = READY_TO_FETCH;
                            }
                        }
                    } else {
                        if (mAppendWorkerState != FETCHING) {
                            if (mStorage.trimFromEnd(
                                    mReplacePagesWithNulls,
                                    mConfig.maxSize,
                                    mRequiredRemainder,
                                    ContiguousPagedList.this)) {
                                mAppendWorkerState = READY_TO_FETCH;
                            }
                        }
                    }
                }
            }

            if (mBoundaryCallback != null) {
                boolean deferEmpty = mStorage.size() == 0;
                boolean deferBegin = !deferEmpty
                        && resultType == PageResult.PREPEND
                        && pageResult.page.size() == 0;
                boolean deferEnd = !deferEmpty
                        && resultType == PageResult.APPEND
                        && pageResult.page.size() == 0;
                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
            }
        }
    };
}

由于是初始化,因此resultType == PageResult.INIT
执行的分支并未调用postValue接口,更新数据,只是将数据保存在agedStorage mStorage 中。

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

这时候需要查看源码LivePagedListBuilder,ViewHolder持有的LiveData本质是ComputableLiveData,在第一次订阅观察者是,会调用onActive

public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }

onActive执行mRefreshRunnable线程,

    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) {
                        	// 这里调用postValue接口,这时候在生命周期允许的条件下回调更新UI
                            mLiveData.postValue(value);
                        }
                    } finally {
                        mComputing.set(false);
                    }
                }
scenario.

            } while (computed && mInvalid.get());
        }
    };

Activity或者Fragment中注册PagedList的观察者代码如下

 viewModel.getLivePagedList().observe(getViewLifecycleOwner(),
                cheeses ->{
                    Log.d(TAG,"===submitList==="+cheeses.size());
                    // 这里最终调用adapter的更新接口,更新UI
                    adapter.submitList(cheeses);
            }
        );

以上即为使用PagedListAdapter初始化RecyclerView的流程。

滑动更新

RecyclerView在创建Item过程中,会调用onCreateViewHolder和onBindViewHolder等接口,在onBindViewHolder接口中,需要获取Adapter的Item列表,用于与item界面元素绑定。

public class CheeseAdapter extends PagedListAdapter<Cheese,CheeseViewHolder> {
	    @Override
    public void onBindViewHolder(@NonNull CheeseViewHolder holder, int position) {
        holder.bindTo(getItem(position));
    }
}

getItem最终调用到
public class AsyncPagedListDiffer{
public T getItem(int index) {

// 这个方法实现了滑动刷新
mPagedList.loadAround(index);
return mPagedList.get(index);
}
}

    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) {
        	// 调用 loadBefore
            schedulePrepend();
        }

        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
        if (mAppendItemsRequested > 0) {
        	// loadAfter
            scheduleAppend();
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值