当前位置:   article > 正文

ItemKeyedDataSource 使用注意事项_datasource: {{- range $key, $value

datasource: {{- range $key, $value

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();            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

场景描述:
数据库默认有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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

关于解决方案的思路是:思路是保证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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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);
            	}
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

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

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));
            }
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

核心的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);
            }
        }
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

由于是初始化,因此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;
                }
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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());
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

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

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

以上即为使用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));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/286515
推荐阅读
相关标签
  

闽ICP备14008679号