閱讀本文之前,可先閱讀同系列 Android Jetpack 組件文章如下:
- Android Jetpack 組件之 Lifecycle 篇
- Android Jetpack 組件之 LiveData 篇
- Android Jetpack 組件之 ViewModel 篇
- Android Jetpack 組件之 DataBinding 篇
- Android Jetpack 組件之 BindingAdapter 篇
- Android Jetpack 組件之使用可觀察的數據對象
本文將介紹 Paging Library 庫的使用,其源碼解析將在下篇文章中介紹,Paging Library 組件是 Android Jetpack 的一部分,是 Google 推出的官方分頁組件,如果項目中使用了 Google 新推出的官方架構組件,如 LiveData、Lifecycle、ViewModel 等,就可以嘗試將該分頁組件引入自己的項目,它的優點是無痕加載更多數據,一定程度上提高用戶體驗。
簡述一下使用 paging 組件分頁加載數據的過程,DataSource 負責從網絡或數據庫加載數據,將數據存儲在 PagedList 中,使用 submitList 提交數據到 PagedListAdapter,當數據發生變化時會在後台線程中計算數據差異,最後 PagedListAdapter 通知 RecyclerView 更新數據。
- 準備數據
- 引入 Paging Library 組件
- 自定義 DataSource
- 配置分頁參數
- 加載顯示數據
- 測試效果
- Paging Library 源碼解析
準備數據#
這裡使用幹貨集中營的開源 Api 來進行測試,具體如下:
public interface CommonAPI {
// 這裡使用幹貨集中營開源API:http://gank.io/api/search/query/listview/category/Android/count/10/page/1
@GET("api/search/query/listview/category/Android/count/8/page/{page}")
Call<List<DataBean.ResultsBean>> getArticleList1(@Path("page") int page);
}
引入 Paging Library 組件#
引入 Paging Library 庫如下:
def paging_version = "2.1.0"
// androidx
implementation "androidx.paging:paging-runtime:$paging_version"
// 老版本 (page library 2.0.0-rc01)
implementation "android.arch.paging:runtime:$paging_version"
這裡使用的是 androidx 最新版本。
自定義 DataSource#
自定義 DataSource 加載數據,這裡加載的網絡數據,使用 PageKeyedDataSource 更合適,繼承 PageKeyedDataSource 自定義 DataSource 如下:
// 自定義DataSource
public class MDataSource extends PageKeyedDataSource<String, DataBean.ResultsBean> {
private static final String TAG = MDataSource.class.getSimpleName();
private int mPage = 1;
public MDataSource() {
}
// 初始化
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params,
@NonNull final LoadInitialCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadInitial-->");
CommonAPI api = RetrofitApi.getInstance().mRetrofit.create(CommonAPI.class);
Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
@Override
public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
Log.i(TAG, "--onResponse-->" + response.toString());
if (response.isSuccessful() && response.code() == 200) {
List<DataBean.ResultsBean> data = response.body();
callback.onResult(data, "before", "after");
}
}
@Override
public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
Log.i(TAG, "--onFailure-->" + t.getMessage());
}
});
}
// 加載上一頁
@Override
public void loadBefore(@NonNull LoadParams<String> params,
@NonNull LoadCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadBefore-->" + params.key);
}
// 加載下一頁
@Override
public void loadAfter(@NonNull final LoadParams<String> params,
@NonNull final LoadCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadAfter-->" + params.key);
mPage++;
CommonAPI api = RetrofitApi.getInstance().mRetrofit.create(CommonAPI.class);
Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
@Override
public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
Log.i(TAG, "--onResponse-->" + response.toString());
if (response.isSuccessful() && response.code() == 200) {
List<DataBean.ResultsBean> data = response.body();
callback.onResult(data, params.key);
}
}
@Override
public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
Log.i(TAG, "--onFailure-->" + t.getMessage());
}
});
}
}
很簡單沒有什麼多餘的東西,細節可以參考後文中的源碼解析,創建一個工廠方便數據變化是創建新的 DataSource 如下:
// MDataSource創建工廠
public class MDataSourceFactory extends DataSource.Factory<String, DataBean.ResultsBean> {
public MDataSourceFactory() {
}
@NonNull
@Override
public DataSource<String, DataBean.ResultsBean> create() {
MDataSource mDataSource = new MDataSource();
return mDataSource;
}
}
配置分頁參數#
在 ViewModel 中創建 PagedList.Config 並進行分頁參數配置,創建 DataSource 工廠對象,最終生成支持分頁的 LiveData 數據,具體參考如下:
// ViewModel
public class MViewModel extends ViewModel {
private int pageSize = 20;
// PagedList配置
private PagedList.Config config = new PagedList.Config.Builder()
.setInitialLoadSizeHint(pageSize)//設置首次加載的數量
.setPageSize(pageSize)//設置每頁加載的數量
.setPrefetchDistance(2)//設置距離每頁最後數據項來時預加載下一頁數據
.setEnablePlaceholders(false)//設置是否啟用UI佔用符
.build();
// DataSource.Factory
private DataSource.Factory<String,DataBean.ResultsBean> factory = new MDataSourceFactory();
// LiveData
private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory, config)
.build();
public LiveData<PagedList<DataBean.ResultsBean>> getPagedList() {
return mPagedList;
}
}
加載顯示數據#
這裡使用 LiveData 監聽加載的數據,然後使用 submitList 將數據提交給 PagedListAdapter,會在後台線程中對比新舊數據的差異,最後更新 RecyclerView,具體如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView mRecyclerView;
private ArticleAdapter mAdapter;
private MViewModel mViewModel;
private static DiffUtil.ItemCallback<DataBean.ResultsBean> itemCallback = new DiffUtil.ItemCallback<DataBean.ResultsBean>() {
@Override
public boolean areItemsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
return oldItem.getGanhuo_id() == newItem.getGanhuo_id();
}
@Override
public boolean areContentsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
return oldItem.equals(newItem);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.rvData);
mAdapter = new ArticleAdapter(itemCallback);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
ViewModelProvider mViewModelProvider = new ViewModelProvider(this,
new ViewModelProvider.AndroidViewModelFactory(getApplication()));
mViewModel = mViewModelProvider.get(MViewModel.class);
}
public void getData(View view) {
mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
@Override
public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
Log.i(TAG, "--onChanged-->");
mAdapter.submitList(dataBeans);
}
});
}
}
測試效果#
以上就是 Page Library 庫的使用方式。
Paging Library 源碼解析#
接著 LiveData 的 observer 方法進入開始,源碼如下:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
// 記住這個LifecycleBoundObserver,後面有用
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
// 添加Observer,具體是LifecycleBoundObserver
owner.getLifecycle().addObserver(wrapper);
}
繼續查看 LifecycleRegistry 的 addObserver 方法,源碼如下:
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
// ...
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
// ObserverWithState的dispatchEvent方法
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
}
// ...
}
static class ObserverWithState {
State mState;
LifecycleEventObserver mLifecycleObserver;
ObserverWithState(LifecycleObserver observer, State initialState) {
mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer);
mState = initialState;
}
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
// 實際調用的是LifecycleBoundObserver的onStateChanged方法
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
繼續查看 LiveData 的內部類 LifecycleBoundObserver 的 onStateChanged 方法,源碼如下:
// 方法
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
// Lifecycle組件監聽的生命週期方法回調
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
// 查看源碼shouldBeActive方法知,只要Activity的狀態在ON_START之後就返回true
activeStateChanged(shouldBeActive());
}
// activeStateChanged方法
void activeStateChanged(boolean newActive) {
// mActive默認false,所以不成立
// 如果第二次執行mActive為false,則不繼續處理,最終的結果就是不會回到onChanged方法
if (newActive == mActive) {
return;
}
// 第一次執行完後mActive的值將被設置為true
mActive = newActive;
// 只要有活躍觀察者,也就是組件的狀態是START或RESUME時mActiveCount就不等於0,wasInactive為true
LiveData.this.mActiveCount += mActive ? 1 : -1;
// wasInactive和mActive都為true,執行
if (wasInactive && mActive) {
onActive();
}
// mActiveCount大於0 ,fasle && false,不執行
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
// mActive為true
if (mActive) {
//分發處理ObserverWrapper,也就是添加進去的觀察者LifecycleBoundObserver
dispatchingValue(this);
}
}
看到這裡至少就可以解釋為什麼在 PagedList 初始加載數據時會回調 onChanged 方法,而在加載下一頁數據時不再回調 onChanged 方法了,
繼續查看 LiveData 的方法 dispatchingValue 方法,源碼如下:
// dispatchingValue方法
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// ...
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
// 遍歷獲取Observer處理
considerNotify(iterator.next().getValue());
//...
}
// ...
}
// considerNotify方法
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// 觀察者不處於活躍狀態,不通知觀察者
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
// 通知觀察者
observer.mObserver.onChanged((T) mData);
}
至此,將結果回調給具體的觀察者。
從 mPagedList 的創建開始,創建 mPagedList 代碼如下:
// LiveData
private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory,config)
.build();
繼續查看關鍵方法 build,源碼如下:
// build方法
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
// create方法
private static <Key, Value> LiveData<PagedList<Value>> create(
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
// DataSource失效回調
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
// 回調該方法通常表示需要新的數據源
invalidate();
}
};
@SuppressWarnings("unchecked") // for casting getLastKey to Key
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
initializeKey = (Key) mList.getLastKey();
}
do {
if (mDataSource != null) {
mDataSource.removeInvalidatedCallback(mCallback);
}
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
// 創建PagedList,PageList是如何創建存儲的且看下文
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());//DataSource無效時,還是使用以前的數據,DataSource有效是執行一次返回PagedList
return mList;
}
}.getLiveData();
}
使用 getLiveData 獲取 LiveData 數據,其中 LiveData 數據的更新是在一個名為 mRefreshRunnable 的 Runnable 中更新的,在 mRefreshRunnable 中會調用上面的 compute 方法生成 PagedList,然後使用 LiveData 的 postValue 方法更新 PageList 數據到 LiveData 中,源碼如下:
final Runnable mRefreshRunnable = new Runnable() {
// ...
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
// 生成PagedList
value = compute();
}
if (computed) {
// 更新LiveData
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
// ...
};
到此為止,LiveData<PagedList> 從創建到更新就分析完了。
接著上文繼續看一下 PagedList 是如何生成的,PagedList 創建的關鍵源碼如下:
// PagedList的創建
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
// 繼續查看build方法,調用了PagedList的create方法
public PagedList<Value> build() {
// ...
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}
// 真正創建PagedList
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) {
// PageKeyedDataSource繼承ContiguousDataSource,dataSource.isContiguous()為true
if (dataSource.isContiguous() || !config.enablePlaceholders) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
// 使用PageKeyedDataSource不會執行
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
// 創建並返回ContiguousPagedList,也就是PagedList
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
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);
}
}
繼續查看 ContiguousPagedList 的創建,其關鍵源碼如下:
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
// ...
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
// ...
}
這裡調用了抽象類 ContiguousDataSource 的 dispatchLoadInitial 方法,查看各類具體的實現類 PageKeyedDataSource 裡面實現的具體的 dispatchLoadInitial 方法,源碼如下:
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
// 創建了LoadInitialCallback,也就是自定義DataSource中回調從網絡加載數據時使用的callback
LoadInitialCallbackImpl<Key, Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
// 自定義DataSource需要實現的loadInitial方法,在這裡完成回調
loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
// 設置callback的回調執行在主線程
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
到此為止,還不知道回調過去的數據時怎