最近简单看了下google推出的框架Jetpack,感受此框架的内容能够对平时的开发有很大的帮助,也能够解决不少开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。java
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:android
上述时Android Architecture所提供的架构组件,本文主要从使用和源码的角度分析Paging组件数据库
好比当一条新的item插入到数据库,DataSource会被初始化,LiveData后台线程就会建立一个新的PagedList。这个新的PagedList会被发送到UI线程的PagedListAdapter中,PagedListAdapter使用DiffUtil在对比如今的Item和新建Item的差别。当对比结束,PagedListAdapter经过调用RecycleView.Adapter.notifyItemInserted()将新的item插入到适当的位置后端
3.一、添加依赖api
def paging_version = "1.0.0"
implementation "android.arch.paging:runtime:$paging_version"
testImplementation "android.arch.paging:common:$paging_version"
implementation "android.arch.paging:rxjava2:1.0.0-rc1"复制代码
3.二、Paging使用步骤bash
val myPagingConfig = PagedList.Config.Builder() // 分页设置
.setPageSize(50)
.setPrefetchDistance(150)
.setEnablePlaceholders(true)
.build()复制代码
val concertList = LivePagedListBuilder(myConcertDataSource, myPagingConfig)
.setFetchExecutor(myExecutor)
.build()复制代码
viewModel.concertList.observe(this, { pagedList ->
adapter.submitList(pagedList) })复制代码
3.三、Paging和Room的使用网络
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll() : DataSource.Factory<Int,User> // 返回DataSOurce.Factory配合PagingList使用
}复制代码
class ViewModelUser(application: Application) : AndroidViewModel(application) {
val dao = UserDataBase.getInstence(application)?.getDao()
////传入Room返回的DataSource.Factory
var liveArray : LiveData<PagedList<User>> =
LivePagedListBuilder(dao!!.getAll(),PagedList.Config.Builder()
.setPageSize(10)
.setPrefetchDistance(10)
.setEnablePlaceholders(true)
.build()).build()
}复制代码
class Adapter : PagedListAdapter<User, Adapter.UserViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return UserViewHolder(layoutInflater.inflate(R.layout.item,parent,false))
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = getItem(position)
if (user != null){
holder.bind(user)
}else{
holder.clear()
}
}
companion object {
val diffCallback = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User?, newItem: User?): Boolean {
return oldItem?.id == newItem?.id
}
override fun areContentsTheSame(oldItem: User?, newItem: User?): Boolean {
return oldItem == newItem
}
}
}
class UserViewHolder(val view : View) :
RecyclerView.ViewHolder(view){
private val tvId : TextView = view.findViewById(R.id.tvId)
private val tvName : TextView = view.findViewById(R.id.tvName)
fun bind(user: User){
tvId.text = user.id.toString()
tvName.text = user.name
}
fun clear(){
tvName.text = null
tvId.text = null
}
}复制代码
这里说一下传入的DiffUtil.ItemCallback<> 实例,当数据加载到PagedListAdapter时,会回调DiffUtil.ItemCallback中两个抽象方法,确认数据和以前是否发生了改变,若是改变则调用Adapter更新数据:架构
val viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java!!)
val recyclerView = findViewById(R.id.concert_list)
val adapter = ConcertAdapter()
viewModel.concertList.observe(this, { pagedList ->
... })
recyclerView.setAdapter(adapter)复制代码
这里使用ViewModel中保存加载到的LivaData<PagedList>(关于ViewModel点击查看另外一篇Android Jetpack架构组件之 ViewModel (源码篇)),为LiveData添加观察者,当数据发生改变时回调方法,将数据发送到PagedListAdapter中更新界面UI并发
adapter.submitList(pagedList)复制代码
3.四、使用RxJava2观察分页数据app
Paging除了支持LiveData加载数据外,还支持RxJava2观察数据,经过建立一个Observable或Flowable实例,观察数据的改变并发送数据到Adapter,下面用Flowable代替LiveData:
var liveArray : Flowable<PagedList<User>> = RxPagedListBuilder(dao!!.getAll(),PagedList.Config.Builder()
.setPageSize(10)
.setPrefetchDistance(10)
.setEnablePlaceholders(true)
.build()).buildFlowable(BackpressureStrategy.LATEST)复制代码
和建立LiveData同样传入DataSource.Factory和PagedList.Config,并配置背压策略,建立Flowable后像RxJava正常使用同样订阅观察者便可获取数据:
viewModel.concertList.subscribe({
flowableList -> adapter.submitList(flowableList)复制代码
3.五、Paging使用的注意事项
Paging组件除了自身建立的DataSource以及Room的配合使用外,还支持自定以实现DataSource,组件提供了一下三种模式的DataSource,咱们在使用时只需根据本身的需求选择什么时候的实现子类:
构建可观察 PagedList对象时,须要考虑内容的更新方式,按数据的来源分为本地加载和网络加载:
四、Paging自定义DataSource
在开发过程当中,除了查询本地数据库和Room配合使用不须要自定义DataSource外,其余加载网络数据时可能都须要自定义来控制数据的获取,下面以实现ItemKeyedDataSource为例分析使用自定义DataSource
class ItemDataSource : ItemKeyedDataSource<Int,ArticleBean>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<ArticleBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<ArticleBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<ArticleBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getKey(item: ArticleBean): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}复制代码
实现ItemKeyedDataSource的子类,这里传入的两个泛型:Key表示每个Item的标志,这里使用Int即每一个Item的id,Value :加载的数据,这里传入数据类型Bean,而后重写四个方法:
实现方法中加载数据的逻辑:
override fun getKey(item: M) = item.id
/**
* 初始化时的加载
*/
override fun loadInitial(params: LoadInitialParams<T>, callback: LoadInitialCallback<M>) {
api.getArticleList(0) //初始化加载第一页
.compose(RxHelper.rxSchedulerHelper())
.subscribe({
callback.onResult(it?.data!!.datas!!)
}, {
refreshFailed(it.message, params, callback)
})
}
/**
* 加载更多
*/
override fun loadAfter(params: LoadParams<T>, callback: LoadCallback<M>) {
api.getArticleList(page) // 下拉加载更多数据
.compose(RxHelper.rxSchedulerHelper())
.subscribe({
callback.onResult(it.data!!.datas!!)
}, {
networkFailed(it.message, params, callback)
})
}复制代码
class ItemDataSourceFactory() :
DataSource.Factory<Int, ArticleBean>() {
val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
override fun create(): DataSource<Date, Concert> {
val source = ItemDataSource()
sourceLiveData.postValue(source)
return source
}
}复制代码
实现了DataSource.Factory,重写onCreate()方法建立DataSource实例,这里使用了LiveData包装了DataSource的实例,这样作的好处就是能够对建立的DataSource的时效性进行控制,例如咱们在刷新的时候只需调用
ItemDataSourceFactory.sourceLivaData.value?.invalidate()复制代码
这里会通知DataSource的数据失效,就会从新初始化加载数据
使用的方式和前面的一致,惟一的区别就是Factory的来源不一样,前面Factory是从数据库Room中查询生成,这里直接传入实现的Factory的实例便可:
val concertList = LivePagedListBuilder(
ItemDataSourceFactory, 20).build()复制代码
到此Paging组件执行时会根据传递的Factory建立DataSource,而后调用DataSource中重写的方法初始化和加载数据到PagedList,而后使用数据刷新界面
从上面的使用能够看出Paging的组件的成员的职责
五、源码分析
不管是使用Room仍是自定义Datasource,Paging组件的开始执行都是从建立LiveData<PagedList>开始的,因此咱们源码的分析也从LiveData<PagedList>的建立开始
5.一、LiveData<PagingList<T>>
LiveData<PagingList<T>>的建立过程如上图,使用LivePagedListBuilder配置Factory和Config,而后调用build建立实例,在build方法中直接调用了create()方法建立LiveData
@AnyThread
@NonNull
private static <Key, Value> LiveData<PagedList<Value>> 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<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
@Override
protected PagedList<Value> compute() { // 重写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中执行了一些主要操做:
先来看一下PagedList的建立过程,在PagedList.build()中调用了PagedList.create(),因此真正的建立是在create()中发生的,
private static <K, T> PagedList<T> create(...) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
......
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);
}
}复制代码
从代码中看出根据 条件(dataSource.isContiguous() || !config.enablePlaceholders)的不一样分别建立ContiguousPagedList和TiledPagedList,其实这里就是区分上面的三个自定义DataSource的类型,若是是PositionalDataSource建立TiledPagedList,其余的返回ContiguousPagedList,咱们依次查看三个DataSource中的isContiguous()方法:
@Override
boolean isContiguous() {
return false;
}复制代码
@Override
boolean isContiguous() {
return true;
}复制代码
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}复制代码
带着对ComputableLiveData做用和什么时候执行compute这两个疑问,查看ComputableLiveData源码,发如今ComputableLiveData的构造函数中建立LiveData实例,并在onActive()中执行了Runnable接口(关于onActive()请查看Android Jetpack架构组件之 LiveData),下面查看Runnable接口中执行了哪些逻辑:
@VisibleForTesting
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
boolean computed;
do {
computed = false;
others.
if (mComputing.compareAndSet(false, true)) {
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
value = compute(); // 调用了compuet建立了PagedList
}
if (computed) {
mLiveData.postValue(value); // 设置LiveData的值
}
} finally {
mComputing.set(false);
}
}
} while (computed && mInvalid.get());
}
};复制代码
在Runnable中调用了ComputableLiveData的compute()方法建立了PagedList,因此此处的Value就是PagedList,而后为mLiveData初始化赋值PagedList,细心的同窗会留意到,在上面的create()方法最后一句调用了getLiveData()获取到的就是ComputableLiveData构造函数中建立的LIveData
@SuppressWarnings("WeakerAccess")
@NonNull
public LiveData<T> getLiveData() {
return mLiveData;
}复制代码
到此LiveData<PagedList>的建立就完成了。
5.二、数据初始化加载
从上面的执行过程当中,咱们知道当咱们自定义实现ItemKeySource时,建立的PagedList实际为ContiguousPagedList,查看ContiguousPagedList构造函数源码:
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}复制代码
在构造函数中执行一下逻辑:
@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(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}复制代码
在ItermKeyDataSource的dispatchLoadInitial()方法中调用了抽象函数loadInitial(),根据前面的学习咱们知道在oadInitial()中设置了初始化的网络请求,到此实现了Paging组件初始化数据的加载;
5.三、数据的显示
在自定义ItemDataSource的loadInitial()中加载数据后,调用了callback.onResult(it?.data!!.datas!!)方法,此处的callback是LoadInitialCallback的实现类LoadInitialCallbackImpl,在onResult()方法中又调用了LoadCallbackHelper.dispatchResultToReceiver()
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
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中:
private 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) {
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 (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的三个方法:
此出分析初始化,回调的类型为PageResult.INIT,调用了PagedStorage的init()方法:
void init(int leadingNulls, @NonNull List<T> 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()
public void onInitialized(int count) {
notifyInserted(0, count);
}
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);
}
}
}
}复制代码
到此咱们能够得出结论
那CallBack是从哪来的呢?应该是哪里须要哪里才会注册回调,想一想数据位置的变化在哪一个地方能用得着,哪一个地方优惠根据position和count处理呢?答案就时Adapter
在前面的实例中,使用submitList()设置数据,而submiList()直接调用了mDiffer.submitList(pagedList)
public void submitList(final PagedList<T> 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() {
@Override
public void onInserted(int position, int count) {
mUpdateCallback.onInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mUpdateCallback.onRemoved(position, count);
}
@Override
public void onChanged(int position, int count) {
// NOTE: pass a null payload to convey null -> item
mUpdateCallback.onChanged(position, count, null);
}
};复制代码
mPagedListCallback的onInserted()直接回调mUPdateCallback.onInserted(),在mUPdateCallback.onInserted()中直接调用Adapter的notifyItemRangeInserted(position, count)实现数据更新
5.四、数据下拉加载
Paging的好处之一就是自动帮咱们实现了下拉加载的操做,其时他的实现是依靠adapter的滑动位置,根本的逻辑和平时本身写的滑动到底加载数据大体一致,都是根据可见position和数据量的比较触发加载,在PagingAdapter中的getItem()中直接调用mDiffer.getItem(position),
public T getItem(int index) {
mPagedList.loadAround(index); // 调用加载数据
return mPagedList.get(index);
}复制代码
在getItem()中处了获取到数据之外,还调用了mPagedList.loadAround(index)去加载数据,loadAround()方法中有调用了loadAroundInternal()
@MainThread
@Override
protected void loadAroundInternal(int index) {
int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
int appendItems = index + mConfig.prefetchDistance
- (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
schedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
if (mAppendItemsRequested > 0) {
scheduleAppend();
}
}复制代码
根据如今显示的index和设置的Config计算须要请求的数量,调用scheduleAppend()加载更多数据,
@MainThread
private void scheduleAppend() {
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
//调用DataSource的加载更多方法
mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
}复制代码
是否是有发现了熟悉的身影,还记得初始化时调用的mDataSource.dispatchLoadInitial()吗?这里调用mDataSource.dispatchLoadAfter()方法,调用咱们实现的loadAfter()加载更多数据,以后数据的显示都和初始化一致将PageResult.INIT换成PageResult.APPEND而后想Adapter中追加数据;
到此整个Paging的执行逻辑和原理都分析完了,从总体的流程看架构的设计仍是有他独特的魅力的,架构的内涵读者本身体味,下面还有一点,咱们知道DataSource的刷新是从调用Invalidate()开始的,有没有相过是如何实现的
@AnyThread
public void invalidate() {
if (mInvalid.compareAndSet(false, true)) {
for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
callback.onInvalidated();
}
}
}复制代码
调用InvalidatedCallback 的onInvalidate(),这里的InvalidatedCallback其实在咱们代码分析的第一步就添加了,还记得吗?在建立PageList时,调用了Factory.create(),以后就给DataSource添加了CallBack()
// 建立CallBack
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
// 添加CallBack
mDataSource.addInvalidatedCallback(mCallback);复制代码
public void invalidate() {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
}
@VisibleForTesting
final Runnable mInvalidationRunnable = new Runnable() {
@MainThread
@Override
public void run() {
boolean isActive = mLiveData.hasActiveObservers();
if (mInvalid.compareAndSet(false, true)) {
if (isActive) {
mExecutor.execute(mRefreshRunnable);
}
}
}
};复制代码
在invalidate()中执行了mInvalidationRunnable 中的run(),run()方法中有从新执行了mRefreshRunnable,还记的mRefreshRunnable执行了什么吗?对就是冲新建立了DataSource和PagedLIst,而后冲新加载数据,而后上面全部过程再来一次!!
本篇时整个组件的最后一篇其实也是最长的一篇(由于我最后写的),,由于Paging组件也是Jetpack组件中比较复杂的一个,使用频率也很是高,后期会针对自定义DataSource进行封装,好了这篇真的花了好长时间,感受整个下午都在写这个,但愿对本身对你们都有所帮助!