角色3 数据模型
数据模型其实就是 ViewModel,用来管理数据
PagedList: 数据源获取的数据最终靠PagedList来承载。对于PagedList,我们可以这样来理解,它就是一页数据的集合。 每请求一页,就是新的一个PagedList对象。
public class StudentViewModel extends ViewModel {
// 看源码:@1 listLiveData 数据怎么来的 private final LiveData<PagedList> listLiveData;
public StudentViewModel() { StudentDataSourceFactory factory = new StudentDataSourceFactory();
// 初始化 ViewModel this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, Flag.SIZE).build(); }
// TODO 暴露数据出去 public LiveData<PagedList> getListLiveData() { return listLiveData; } }
角色4 适配器
这个Adapter就是一个RecyclerView的Adapter。不过我们在使用paging实现RecyclerView的分页加载效果,不能直接继承RecyclerView的Adapter,而是需要继承PagedListAdapter。
LiveData观察到的数据,把感应到的数据给适配器,适配器又绑定了 RecyclerView,那么RecyclerView的列表数据就改变了
public class RecyclerPagingAdapter extends PagedListAdapter<Student, RecyclerPagingAdapter.MyRecyclerViewHolder> {
// TODO 比较的行为 private static DiffUtil.ItemCallback DIFF_STUDNET = new DiffUtil.ItemCallback() {
// 一般是比较 唯一性的内容, ID @Override public boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) { return oldItem.getId().equals(newItem.getId()); }
// 对象本身的比较 @Override public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) { return oldItem.equals(newItem); } };
protected RecyclerPagingAdapter() { super(DIFF_STUDNET); }
@NonNull @Override public MyRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null); return new MyRecyclerViewHolder(view); }
@Override public void onBindViewHolder(@NonNull MyRecyclerViewHolder holder, int position) { Student student = getItem(position);
// item view 出来了, 分页库还在加载数据中,我就显示 Id加载中 if (null == student) { holder.tvId.setText("Id加载中"); holder.tvName.setText("Name加载中"); holder.tvSex.setText("Sex加载中"); } else { holder.tvId.setText(student.getId()); holder.tvName.setText(student.getName()); holder.tvSex.setText(student.getSex()); } }
// Item 优化的 ViewHolder public static class MyRecyclerViewHolder extends RecyclerView.ViewHolder {
TextView tvId; TextView tvName; TextView tvSex;
public MyRecyclerViewHolder(View itemView) { super(itemView); tvId = itemView.findViewById(R.id.tv_id); // ID tvName = itemView.findViewById(R.id.tv_name); // 名称 tvSex = itemView.findViewById(R.id.tv_sex); // 性别 } }
}
展示结果
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView; RecyclerPagingAdapter recyclerPagingAdapter; StudentViewModel viewModel;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycle_view); recyclerPagingAdapter = new RecyclerPagingAdapter();
// 最新版本初始化 viewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()) .get(StudentViewModel.class);
// LiveData 观察者 感应更新 viewModel.getListLiveData().observe(this, new Observer<PagedList>() { @Override public void onChanged(PagedList students) { // 再这里更新适配器数据 recyclerPagingAdapter.submitList(students); } });
recyclerView.setAdapter(recyclerPagingAdapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); } }
Paging的各个角色职责
- DataSource:数据的来源;
- DataSource.Factory:工厂类提供DataSource的实例,在自定义DataSource时使用;
- PagedList:数据集散中心,根据需要向DataSource索取加载数据,并将得到的数据传递到PagedListAdapter;
- PagedListAdapter:数据适配器,这里除了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向PagedList加载数据;
- DiffUtil.ItemCallback:判断数据是否发生改变以确定界面是否更新;
数据源详解
DataSource是一个抽象类,但是我们不能直接继承它实现它的子类。但是Paging库里提供了它的三个子类供我们继承用于不同场景的实现:
第一种:PositionalDataSource:适用于目标数据总数固定,通过特定的位置加载数据,这里Key是Integer类型的位置信息,T即Value。 比如从数据库中的1200条开始加在20条数据。
第二种:ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定item的信息, 即Key字段包含的是Item中的信息,比如需要根据第N项的信息加载第N+1项的数据,传参中需要传入第N项的ID时,该场景多出现于论坛类应用评论信息的请求。
第三种:PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,即Key字段是页相关的信息。比如请求的数据的参数中包含类似next / pervious页数的信息。
Paging的源码分析
类之关系
abstract class DataSource<Key, Value>:
abstract class ItemKeyedDataSource<Key, Value>:
abstract class PageKeyedDataSource<Key, Value>:
abstract class PositionalDataSource: 我们刚刚使用的是这个 数据源子类
DataSource的三个子类:
PageKeyedDataSource:如果页面需要实现上一页、下一页,需要将请求的Token传递到下一步时使用 ItemKeyedDataSource:程序需要根据上一条数据信息(ID)获取下一条数据时使用 PositionalDataSource:需要从数据存储中选择的任何位置获取数据页;例如,请求可能返回以位置1200开头的20个数据项
当然是在拿取数据的地方开始分析,Paging组件的开始执行都是从创建 LiveData开始的,我们源码的分析也从LiveData的创建开始一探Paging背后的逻辑,我们开始分析吧:
初始化工作
开始查看 ”private final LiveData listLiveData;“ 此变量是如何创建的:
public StudentViewModel() { StudentDataSourceFactory factory = new StudentDataSourceFactory(); this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, 20) .build(); }
点击进入build函数分析:
@NonNull @SuppressLint("RestrictedApi") public LiveData<PagedList> build() { return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor); }
进入create函数分析:
使用LivePagedListBuilder配置Factory和Config,然后调用build创建实例,在build方法中直接调用了create()方法创建LiveData
@AnyThread @NonNull private static <Key, Value> LiveData<PagedList> create( @Nullable final Key initialLoadKey, @NonNull final PagedList.Config config, @Nullable final PagedList.BoundaryCallback boundaryCallback, @NonNull final DataSource.Factory<Key, Value> dataSourceFactory, @NonNull final Executor notifyExecutor, @NonNull final Executor fetchExecutor) {
// 注意:在这里创建ComputableLiveData抽象类 return new ComputableLiveData<PagedList>(fetchExecutor) {
@Nullable private PagedList mList; @Nullable private DataSource<Key, Value> mDataSource;
private final DataSource.InvalidatedCallback mCallback = new DataSource.InvalidatedCallback() { @Override public void onInvalidated() { invalidate(); } };
// 注意,在这里重写compute方法, 是我们需要的PagedList @Override protected PagedList compute() { @Nullable Key initializeKey = initialLoadKey; if (mList != null) { //noinspection unchecked initializeKey = (Key) mList.getLastKey(); }
do { if (mDataSource != null) { mDataSource.removeInvalidatedCallback(mCallback); } // 从Builder中传入的Factory中创建DataSource mDataSource = dataSourceFactory.create(); mDataSource.addInvalidatedCallback(mCallback); // 创建PagedList mList = new PagedList.Builder<>(mDataSource, config) .setNotifyExecutor(notifyExecutor) .setFetchExecutor(fetchExecutor) .setBoundaryCallback(boundaryCallback) .setInitialKey(initializeKey) .build(); } while (mList.isDetached()); return mList; } }.getLiveData(); }
在create()中直接返回了ComputableLiveData的实例,在ComputableLiveData实例重写的compute中执行了一些主要操作:
一:调用传入的Factory的create()创建DataSource实例; 二:创建并返回PagedList实例; 三:PagedList.build() & PagedList.create() 就是如下代码(细节);
mList = new PagedList.Builder<>(mDataSource, config) .setNotifyExecutor(notifyExecutor) .setFetchExecutor(fetchExecutor) .setBoundaryCallback(boundaryCallback) .setInitialKey(initializeKey) .build();
public PagedList build() { // TODO: define defaults, once they can be used in module without android dependency if (mNotifyExecutor == null) { throw new IllegalArgumentException("MainThreadExecutor required"); } if (mFetchExecutor == null) { throw new IllegalArgumentException("BackgroundThreadExecutor required"); }
//noinspection unchecked return PagedList.create( mDataSource, mNotifyExecutor, mFetchExecutor, mBoundaryCallback, mConfig, mInitialKey); }
PagedList的创建过程,在PagedList.build()中调用了PagedList.create(),所以真正的创建是在create()中发生的:
private static <K, T> PagedList create(...) { if (dataSource.isContiguous() || !config.enablePlaceholders) { ...... return new ContiguousPagedList<>(contigDataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, key, lastLoad); } else { return new TiledPagedList<>((PositionalDataSource) dataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, (key != null) ? (Integer) key : 0); } }
从上面的代码中看出根据 条件(dataSource.isContiguous() || !config.enablePlaceholders)的不同分别创建ContiguousPagedList和TiledPagedList,其实这里就是区分上面的三个自定义DataSource的类型(三个数据源),如果是PositionalDataSource创建TiledPagedList,其他的返回ContiguousPagedList,我们依次查看三个DataSource中的isContiguous()方法:
PositionalDataSource类中的:
@Overrideboolean isContiguous() {
return false;
}
ItemKeyedDataSource和PageKeyedDataSource都继承与ContiguousDataSource,只查看ContiguousDataSource类中的:
@Overrideboolean isContiguous() {
return true;
}
又回来,从LivePageListBuilder .build开始看:
new ComputableLiveData有什么用 与 何时执行compute函数, 这两个疑问,查看ComputableLiveData源码,发现在ComputableLiveData的构造函数中创建LiveData实例,下面查看Runnable接口中执行了哪些逻辑:
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData() {
@Override
protected void onActive() {
mExecutor.execute(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;
// 这里会执行 compute(); 函数
// 调用了compuet创建了PagedList
value = compute();
}
if (computed) {
// 设置LiveData的值
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}
.......
} while (computed && mInvalid.get());
}
};
在mRefreshRunnable中调用了ComputableLiveData的compute()方法创建了PagedList,所以此处的Value就是PagedList,然后为mLiveData初始化赋值PagedList。
细心的会留意到,在上面的create()方法最后一句调用了getLiveData()获取到的就是ComputableLiveData构造函数中创建的LIveData:
@SuppressWarnings("WeakerAccess")
@NonNull
public LiveData getLiveData() {
return mLiveData;
}
到这里为止,LiveData终于创建完成了
数据的加载工作
ContiguousPagedList 作为出发点
当我们自定义实现ItemKeySource时,创建的PagedList实际为ContiguousPagedList,查看ContiguousPagedList构造函数源码:
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback boundaryCallback,
@NonNull Config config, final @Nullable K key, int lastLoad) {
super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, config); mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,mConfig.initialLoadSizeHint,mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
mShouldTrim = mDataSource.supportsPageDropping()&& mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
}
ItermKeyDataSource的dispatchLoadInitial()
在构造函数中执行一下逻辑,所以继续追踪代码:
第一点:创建PagedStorage实例,主要根据滑动的位置显示是否要继续加载数据
第二点:调用DataSource.dispatchLoadInitial方法,此时使用的时ItermKeyDataSource的dispatchLoadInitial 方法
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders,
@NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver receiver) {
LoadInitialCallbackImpl callback = new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver); loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
上面代码在ItermKeyDataSource的dispatchLoadInitial()方法中调用了抽象函数loadInitial(),根据前面的学习我们知道在 loadInitial() 中设置了初始化的网络请求,到此实现了Paging组件初始化数据的加载
数据的显示工作
在自定义ItemDataSource的loadInitial()中加载数据后,调用了callback.onResult(it?.data!!.datas!!)方法,此处的callback是LoadInitialCallback的实现类LoadInitialCallbackImpl,在onResult()方法中又调用了LoadCallbackHelper.dispatchResultToReceiver()
static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
final LoadCallbackHelper mCallbackHelper;
private final PageKeyedDataSource<Key, Value> mDataSource;
private final boolean mCountingEnabled;
LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
boolean countingEnabled, @NonNull PageResult.Receiver receiver) {
mCallbackHelper = new LoadCallbackHelper<>(
dataSource, PageResult.INIT, null, receiver);
mDataSource = dataSource;
mCountingEnabled = countingEnabled;
}
@Override
public void onResult(@NonNull List data, @Nullable Key previousPageKey,
@Nullable Key nextPageKey) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
mDataSource.initKeys(previousPageKey, nextPageKey);
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}
狙击点:LoadCallbackHelper.dispatchResultToReceiver()
void dispatchResultToReceiver(final @NonNull PageResult result) {
Executor executor;
synchronized (mSignalLock) {
if (mHasSignalled) {
throw new IllegalStateException(
"callback.onResult already called, cannot call again.");
}
mHasSignalled = true;
executor = mPostExecutor;
}
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
mReceiver.onPageResult(mResultType, result);
}
});
} else {
mReceiver.onPageResult(mResultType, result);
}
}
在dispatchResultToReceiver()方法中,调用PageResult.Receiver.onPageResult()方法,这里的mReceiver是在调用mDataSource.dispatchLoadInitial()时传入的最后一个参数,他的实现在ContiguousPagedList中匿名创建:
final PageResult.Receiver mReceiver; // mSignalLock protects mPostExecutor, and mHasSignalled
private final Object mSignalLock = new Object();
private Executor mPostExecutor = null;
private boolean mHasSignalled = false;
LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
@Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver receiver) {
mDataSource = dataSource;
mResultType = resultType;
mPostExecutor = mainThreadExecutor;
mReceiver = receiver;
}
ContiguousPagedList:
private PageResult.Receiver mReceiver = new PageResult.Receiver() {
// 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 pageResult) {
List 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);
}
}
}
};
在onPageResult()方法中根据resultType的类型执行操作,PageResult的三个数据类型分别对应者ItemKeyDataSource的三个方法:
loadInitial:对应初始化状态PageResult.INIT loadBefore:对应初始化状态PageResult.PREPEND loadAfter:对应初始化状态PageResult.APPEND
此出分析初始化,回调的类型为PageResult.INIT,调用了PagedStorage的init()方法:
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
void init(int leadingNulls,
@NonNull List page, int trailingNulls, int positionOffset,
@NonNull Callback callback) {
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
}
在init()方法中首先调用另一个init()方法记录加载的位置,并保存加载的数据, 然后调用callback.onInitialized(),在onInitialzed()方法中调用了notifyInserted(),在notifyInserted()中遍历mCallbacks回调callback的onInserted()
interface Callback {
void onInitialized(int count);
void onPagePrepended(int leadingNulls, int changed, int added);
void onPageAppended(int endPosition, int changed, int added);
void onPagePlaceholderInserted(int pageIndex);
void onPageInserted(int start, int count);
void onPagesRemoved(int startOfDrops, int count);
void onPagesSwappedToPlaceholder(int startOfDrops, int count);
void onEmptyPrepend();
void onEmptyAppend();
}
继续追踪源码:
//ContiguousPagedList:
public void onInitialized(int count) {
notifyInserted(0, count);
}
PagedList:
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);
}
}
}
}
//PagedList 的 接口:public abstract void onInserted(int position, int count);
以上源码, 让我们明白了:
一: 加载的数据保存在PagedStorage中,并记录了加载的位置信息
二: 加载完成后根据数据的变化,回调callback.onInserted()通知数据改变的数量和位置
终于看到曙光了:
那CallBack是从哪来的呢?应该是哪里需要哪里才会注册回调,想想数据位置的变化在哪个地方能用得着,哪个地方优惠根据position和count处理呢?答案就在PagedListAdapter中 终于要看到 PagedListAdapter 了
PagedListAdapter
当然 下面我们也可以简单的追踪下代码 能否到 PagedListAdapter
AsyncPagedListDiffer:
public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, @NonNull AsyncDifferConfig config) {
class NamelessClass_1 extends Callback {
NamelessClass_1() {
}
public void onInserted(int position, int count) {
AsyncPagedListDiffer.this.mUpdateCallback.onInserted(position, count);
}
public void onRemoved(int position, int count) {
AsyncPagedListDiffer.this.mUpdateCallback.onRemoved(position, count);
}
public void onChanged(int position, int count) {
AsyncPagedListDiffer.this.mUpdateCallback.onChanged(position, count, (Object)null);
}
}
ListUpdateCallback:
public interface ListUpdateCallback {
......
void onInserted(int position, int count);
}
AdapterListUpdateCallback:
@Overridepublic void onInserted(int position, int count) { mAdapter.notifyItemRangeInserted(position, count); }
逆向方式,自己去看文字
逆向源码收尾:
在我们开始写的使用Paging的实例中,使用submitList()设置数据,而submiList()直接调用了mDiffer.submitList(pagedList):
public void submitList(PagedList pagedList) {
mDiffer.submitList(pagedList);
}
public void submitList(final PagedList pagedList) {
if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
mPagedList = pagedList;
pagedList.addWeakCallback(null, mPagedListCallback);
return;
}
}
这里就调用了addWeakCallback()添加Callback实例mPagedListCallback
private PagedList.Callback mPagedListCallback = new PagedList.Callback() {