diff options
Diffstat (limited to 'android/arch/paging/TiledPagedList.java')
-rw-r--r-- | android/arch/paging/TiledPagedList.java | 276 |
1 files changed, 161 insertions, 115 deletions
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java index c45d029d..a2fc064b 100644 --- a/android/arch/paging/TiledPagedList.java +++ b/android/arch/paging/TiledPagedList.java @@ -16,173 +16,219 @@ package android.arch.paging; -import android.support.annotation.AnyThread; -import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; +import java.lang.ref.WeakReference; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; -class TiledPagedList<T> extends PagedList<T> - implements PagedStorage.Callback { +/** @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class TiledPagedList<T> extends PageArrayList<T> { private final TiledDataSource<T> mDataSource; + private final Executor mMainThreadExecutor; + private final Executor mBackgroundThreadExecutor; + private final Config mConfig; - @SuppressWarnings("unchecked") - private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage; - - private final PageResult.Receiver<Integer, T> mReceiver = - new PageResult.Receiver<Integer, T>() { - @AnyThread + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private final List<T> mLoadingPlaceholder = new AbstractList<T>() { @Override - public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) { - // NOTE: if we're already on main thread, this can delay page receive by a frame - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - onPageResult(pageResult); - } - }); + public T get(int i) { + return null; } - @MainThread @Override - public void onPageResult(@NonNull PageResult<Integer, T> pageResult) { - if (pageResult.page == null) { - detach(); - return; - } - - if (isDetached()) { - // No op, have detached - return; - } - - if (mStorage.getPageCount() == 0) { - mKeyedStorage.init( - pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls, - pageResult.positionOffset, TiledPagedList.this); - } else { - mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page, - TiledPagedList.this); - } + public int size() { + return 0; } }; + private int mLastLoad = -1; + + private AtomicBoolean mDetached = new AtomicBoolean(false); + + private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); + @WorkerThread TiledPagedList(@NonNull TiledDataSource<T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, - @NonNull Config config, + Config config, int position) { - super(new PagedStorage<Integer, T>(), - mainThreadExecutor, backgroundThreadExecutor, config); - mDataSource = dataSource; - - final int pageSize = mConfig.mPageSize; + super(config.mPageSize, dataSource.countItems()); - final int itemCount = mDataSource.countItems(); - final int firstLoadSize = Math.min(itemCount, - (Math.max(mConfig.mInitialLoadSizeHint / pageSize, 2)) * pageSize); - final int firstLoadPosition = computeFirstLoadPosition( - position, firstLoadSize, pageSize, itemCount); + mDataSource = dataSource; + mMainThreadExecutor = mainThreadExecutor; + mBackgroundThreadExecutor = backgroundThreadExecutor; + mConfig = config; + + position = Math.min(Math.max(0, position), mCount); + + int firstPage = position / mPageSize; + List<T> firstPageData = dataSource.loadRangeWrapper(firstPage * mPageSize, mPageSize); + if (firstPageData != null) { + mPageIndexOffset = firstPage; + mPages.add(firstPageData); + mLastLoad = position; + } else { + detach(); + return; + } - mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize, - itemCount, mReceiver); + int secondPage = (position % mPageSize < mPageSize / 2) ? firstPage - 1 : firstPage + 1; + if (secondPage < 0 || secondPage > mMaxPageCount) { + // no second page to load + return; + } + List<T> secondPageData = dataSource.loadRangeWrapper(secondPage * mPageSize, mPageSize); + if (secondPageData != null) { + boolean before = secondPage < firstPage; + mPages.add(before ? 0 : 1, secondPageData); + if (before) { + mPageIndexOffset--; + } + return; + } + detach(); } - static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) { - int idealStart = position - firstLoadSize / 2; + @Override + public void loadAround(int index) { + mLastLoad = index; - int roundedPageStart = Math.round(idealStart / pageSize) * pageSize; + int minimumPage = Math.max((index - mConfig.mPrefetchDistance) / mPageSize, 0); + int maximumPage = Math.min((index + mConfig.mPrefetchDistance) / mPageSize, + mMaxPageCount - 1); - // minimum start position is 0 - roundedPageStart = Math.max(0, roundedPageStart); + if (minimumPage < mPageIndexOffset) { + for (int i = 0; i < mPageIndexOffset - minimumPage; i++) { + mPages.add(0, null); + } + mPageIndexOffset = minimumPage; + } + if (maximumPage >= mPageIndexOffset + mPages.size()) { + for (int i = mPages.size(); i <= maximumPage - mPageIndexOffset; i++) { + mPages.add(mPages.size(), null); + } + } + for (int i = minimumPage; i <= maximumPage; i++) { + scheduleLoadPage(i); + } + } - // maximum start pos is that which will encompass end of list - int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize; - roundedPageStart = Math.min(maximumLoadPage, roundedPageStart); + private void scheduleLoadPage(final int pageIndex) { + final int localPageIndex = pageIndex - mPageIndexOffset; - return roundedPageStart; - } + if (mPages.get(localPageIndex) != null) { + // page is present in list, and non-null - don't need to load + return; + } + mPages.set(localPageIndex, mLoadingPlaceholder); - @Override - boolean isContiguous() { - return false; - } + mBackgroundThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (mDetached.get()) { + return; + } + final List<T> data = mDataSource.loadRangeWrapper( + pageIndex * mPageSize, mPageSize); + if (data != null) { + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (mDetached.get()) { + return; + } + loadPageImpl(pageIndex, data); + } + }); + } else { + detach(); + } + } + }); - @Nullable - @Override - public Object getLastKey() { - return mLastLoad; } - @Override - protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot, - @NonNull Callback callback) { - //noinspection UnnecessaryLocalVariable - final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage; - - // loop through each page and signal the callback for any pages that are present now, - // but not in the snapshot. - final int pageSize = mConfig.mPageSize; - final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize; - final int pageCount = mStorage.getPageCount(); - for (int i = 0; i < pageCount; i++) { - int pageIndex = i + leadingNullPages; - int updatedPages = 0; - // count number of consecutive pages that were added since the snapshot... - while (updatedPages < mStorage.getPageCount() - && mStorage.hasPage(pageSize, pageIndex + updatedPages) - && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) { - updatedPages++; - } - // and signal them all at once to the callback - if (updatedPages > 0) { - callback.onChanged(pageIndex * pageSize, pageSize * updatedPages); - i += updatedPages - 1; + private void loadPageImpl(int pageIndex, List<T> data) { + int localPageIndex = pageIndex - mPageIndexOffset; + + if (mPages.get(localPageIndex) != mLoadingPlaceholder) { + throw new IllegalStateException("Data inserted before requested."); + } + mPages.set(localPageIndex, data); + for (WeakReference<Callback> weakRef : mCallbacks) { + Callback callback = weakRef.get(); + if (callback != null) { + callback.onChanged(pageIndex * mPageSize, data.size()); } } } @Override - protected void loadAroundInternal(int index) { - mStorage.allocatePlaceholders(index, mConfig.mPrefetchDistance, mConfig.mPageSize, this); + public boolean isImmutable() { + // TODO: consider counting loaded pages, return true if mLoadedPages == mMaxPageCount + // Note: could at some point want to support growing past max count, or grow dynamically + return isDetached(); } @Override - public void onInitialized(int count) { - notifyInserted(0, count); + public void addWeakCallback(@Nullable PagedList<T> previousSnapshot, + @NonNull Callback callback) { + PageArrayList<T> snapshot = (PageArrayList<T>) previousSnapshot; + if (snapshot != this && snapshot != null) { + // loop through each page and signal the callback for any pages that are present now, + // but not in the snapshot. + for (int i = 0; i < mPages.size(); i++) { + int pageIndex = i + mPageIndexOffset; + int pageCount = 0; + // count number of consecutive pages that were added since the snapshot... + while (pageCount < mPages.size() + && hasPage(pageIndex + pageCount) + && !snapshot.hasPage(pageIndex + pageCount)) { + pageCount++; + } + // and signal them all at once to the callback + if (pageCount > 0) { + callback.onChanged(pageIndex * mPageSize, mPageSize * pageCount); + i += pageCount - 1; + } + } + } + mCallbacks.add(new WeakReference<>(callback)); } @Override - public void onPagePrepended(int leadingNulls, int changed, int added) { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); + public void removeWeakCallback(@NonNull Callback callback) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + Callback currentCallback = mCallbacks.get(i).get(); + if (currentCallback == null || currentCallback == callback) { + mCallbacks.remove(i); + } + } } @Override - public void onPageAppended(int endPosition, int changed, int added) { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); + public boolean isDetached() { + return mDetached.get(); } @Override - public void onPagePlaceholderInserted(final int pageIndex) { - // placeholder means initialize a load - mBackgroundThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (isDetached()) { - return; - } - final int pageSize = mConfig.mPageSize; - mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver); - } - }); + public void detach() { + mDetached.set(true); } + @Nullable @Override - public void onPageInserted(int start, int count) { - notifyChanged(start, count); + public Object getLastKey() { + return mLastLoad; } } |