Paging学习
需要补充的知识
-
Android架构组件Room的使用(适合进阶)
Java注解和反射
JAVA 注解的基本原理
Java 反射由浅入深 | 进阶必备
Java反射(包含getMethod以及invoke)
搞定代理模式,看懂Retrofit动态代理的骚操作LiveData
Android应用结构之LiveData- ViewModel
- RxJava
这可能是最好的RxJava 2.x 教程(完结版)
我的理解
首先来说,paging框架应该分成三部分:Data, ViewModel, UI
Data
- Data是数据的提供者,数据的来源可以是后端返回数据,也可以是Room数据库存贮,当然也可以是本地数据。
- Data可以分成两部分:DataSource和DataSourceFactory。DataSourceFactory继承DataSource.Factory,是用来创建DataSource的途径。
- DataSource
和page相关的3种DataSource的区别
Android Paging分页组件的使用及原理
我所理解的 RxJava——上手其实很简单(二)(publishSubject的使用)
大话Android多线程(一) Thread和Runnable的联系和区别
30 分钟入门 Java8 之 lambda 表达式
- DataSource可以利用Retrofit等方式,通过传入代理类进行数据请求,将数据返回到被观察的LiveData中。
- 设置可取消类Disposable,防止内存泄漏???????
- PositionalDataSource中需要实现两种方法:LoadInitial, LoadRange.进行数据请求时代理类中的参数有:初始请求的数据位置(startpostion),每次请求的页数(pagesize), PagaingCallback接口实例(这个是自定义的)
无论是LoadInitial还是LoadRange,方法参数都有callback, callback都有一个方法onResult(List<>), 调用这个方法,便可将数据传送到被观察的数据中。
- 与此同时,在请求前,请求成功以及请求失败时,都应更新ViewModel对应的网络状态。
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
public class PagingDataSource<T> extends PositionalDataSource<T> implements DataSource.InvalidatedCallback {
private Runnable mRetry;
private PagingViewModel<T> mViewModel;
private PagingRepository mApi;
private final CompositeDisposable mDisposable = new CompositeDisposable();
public PagingDataSource(PagingViewModel<T> viewModel, PagingRepository api) {
this.mViewModel = viewModel;
this.mApi = api;
//registerRetry返回的是一个publishSubject,可以用来简化订阅过程
register(mViewModel.registerRetry().subscribe(s -> {
if (mRetry != null) {
mRetry.run();
mRetry = null;
}
}, Throwable::printStackTrace));//这里mViewModel正式被一个线程订阅,检测其变化?????????????????
//这个额。为啥一定要出现????????????
addInvalidatedCallback(this);
}
//防止出现内存泄漏
private void register(Disposable disposable) {
mDisposable.add(disposable);
}
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<T> callback) {
//更新mViewModel里的网络状态量
mViewModel.getRefreshState().postValue(NetworkStatus.loading());
//这里请求的过程后面会重写
mApi.getDataList(0, PagingViewModel.INIT_LOAD_SIZE, new PagingCallBack() {
public void onSuccess(List list) {
//更新网络状态
mViewModel.getRefreshState().postValue(NetworkStatus.success());
//设置hasMore里持有量为true,说明还有更多的数据
mViewModel.hasMore().postValue(true);
if (isEmptyList(list)) {
//onResult方法会将数据直接发送到检测这个LiveData的UI进程中。
callback.onResult(new ArrayList<>(), 0);
} else {
callback.onResult(new ArrayList(list), 0);
}
}
public void onError(Exception e) {
mViewModel.getRefreshState().postValue(NetworkStatus.error());
//重新进行初始化数据请求
mRetry = () -> loadInitial(params, callback);
}
});
}
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<T> callback) {
mViewModel.getLoadingState().postValue(NetworkStatus.loading());
mApi.getDataList(params.startPosition, PagingViewModel.PAGE_SIZE, new PagingCallBack() {
public void onSuccess(List list) {
mViewModel.getLoadingState().postValue(NetworkStatus.success());
if (isEmptyList(list)) {
callback.onResult(new ArrayList<>());
} else {
callback.onResult(list);
}
}
public void onError(Exception e) {
mViewModel.getLoadingState().postValue(NetworkStatus.error());
mRetry = () -> loadRange(params, callback);
}
});
}
private boolean isEmptyList(List list) {
return list == null || list.isEmpty();
}
//这个应该是取消订阅或者销毁线程的时候用到的,不用太管它
public void onInvalidated() {
removeInvalidatedCallback(this);
mDisposable.clear();
}
}
- DataSourceFactory
- 这个就很简单了,创建一个
Multabel<DataSource>, 创建构造方法,重写create方法,这个会在接下来的ViewModel中用到。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class PagingDataSourceFactory<T> extends DataSource.Factory<Integer, T> {
private MutableLiveData<PagingDataSource<T>> mDataSource = new MutableLiveData<>();
public PagingDataSourceFactory(PagingDataSource<T> dataSource) {
mDataSource.setValue(dataSource);
}
public DataSource<Integer, T> create() {
return mDataSource.getValue();
}
public MutableLiveData<PagingDataSource<T>> getDataSource() {
return mDataSource;
}
}
ViewModel
如果说Data部分是数据的储藏车间,那么viewmodel进行的工作就是将数据按照特定的格式,每次定量地从车间搬运,从而在UI上显示数据的更新。
- PagingViewModel(extends ViewModel)
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
94public abstract class PagingViewModel<T> extends ViewModel {
//设置初始化请求量
public static final int INIT_LOAD_SIZE = 20;
//设置请求页数
public static final int PAGE_SIZE = 20;
private LiveData<PagedList<T>> mDataList;
private MutableLiveData<NetworkStatus> mRefreshState = new MutableLiveData<>();
protected MutableLiveData<NetworkStatus> mLoadingState = new MutableLiveData<>();
protected MutableLiveData<Boolean> isEmpty = new MutableLiveData<>();
protected MutableLiveData<Boolean> hasMore = new MutableLiveData<>();
private PublishSubject<String> retry = PublishSubject.create();
private PagingDataSourceFactory<T> mFactory;
//初始化数据,得到一个包含DataSource的DataSourceFactory的实例
public PagingViewModel() {
if (mDataList == null) {
mFactory = new PagingDataSourceFactory<>(getDataSource());
mDataList = buildPagedList();
}
}
//这一步很重要,这一步直接将ViewModel与DataSourceFactory对接,从DataSourceFactory里面得到DataSource的实例,LivePagedListBuilder<>()方法 中需要传入的参数是继承Data.Factory的类和一个配置Config,getConfig是在下面自定义的方法,配置了PagedList.Config。最后返回的数据就是一个LiveData<PgaedList<T>>.
private LiveData<PagedList<T>> buildPagedList() {
isEmpty.setValue(false);
return new LivePagedListBuilder<>(mFactory, getConfig())
//setBoundaryCallback是在数据耗尽后进行的回调传参,其中PagingBoundaryCallback<T>为自定义类,继承自PagedList.BoundaryCallback<T>
.setBoundaryCallback(new PagingBoundaryCallback<>(hasMore, isEmpty))
.build();
}
public LiveData<PagedList<T>> getDataList() {
return mDataList;
}
public MutableLiveData<NetworkStatus> getLoadingState() {
return mLoadingState;
}
public MutableLiveData<NetworkStatus> getRefreshState() {
return mRefreshState;
}
public MutableLiveData<Boolean> isEmpty() {
return isEmpty;
}
public MutableLiveData<Boolean> hasMore() {
return hasMore;
}
//publishSubject可以当作一个observerble,发送消息("retry");
//在PagingDataSource中被订阅
//好吧这个我实在不知道他多此一举是为什么??????????
public void retry() {
retry.onNext("retry");
}
public void refresh() {
mDataList = buildPagedList();
}
public void showList(List<T> list) {
if(mDataList.getValue()!=null) {
mDataList.getValue().clear();
mDataList.getValue().addAll(list);
}
}
public PublishSubject<String> registerRetry() {
return retry;
}
private PagedList.Config getConfig() {
return new PagedList.Config.Builder()
//配置分页加载的数量
.setPageSize(PAGE_SIZE)
//配置是否启动PlaceHolders
.setEnablePlaceholders(false)
//初始化加载的数量
.setInitialLoadSizeHint(INIT_LOAD_SIZE)
.build();
}
//在后面继承PagingViewModel的类中具体实现,从而能够构造PagingDataSourceFactory
protected abstract PagingDataSource<T> getDataSource();
}
- PagingViewModel(extends ViewModel)
PagingListAdapter
深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法
## 老铁顶住,我要放大招了:##
刺激不 ( ๑乛◡乛๑ )(一点一点地看)
首先我们先看adapter的构造方法
1
2
3
4
5protected PagingListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback,
LifecycleOwner owner) {
super(diffCallback);
mLifecycleOwner = owner;
}里面需要传入两个参数,一个是DiffUtil.ItemCallback, 一个是LifecycleOwner,DiffUtil.ItemCallback是在继承pagingListAdapter的类中根据实际情况实现的。
这个diffutil.itemcallback的传入是很讲究滴。
在adapter构造完成后,传入一个已经写好的DiffUitl.ItemCallback实例,继续传入到AsyncPagedListDiffer中,最后一路传入PagedStorageHelper中。
1 | protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) { |
而在adapter中可以调用一个submitList方法,如下图,调用了PagedListAdapter的submitList方法,从而调用了AsyncPagedListDiffer的submitList方法。
1 | //AsyncPagedListDiff.java 149行 这个构造是从PagedListAdapter调过来的 |
1 | //PagedListAdapter 150行 |
在AsyncPagedListDiffer的submitList方法中,调用了PagedStorageDiffHelper的computeDiff方法,通过之前传入的回调实例,得到一个DiffResult.1
2
3
4
5
6
7
//PagedStorageDiffHelper.java 37行 获取result
{
static <T> DiffUtil.DiffResult computeDiff(······)
//······//
return diffCallback.getChangePayload(oldItem, newItem);
}
1 |
|
1 | //PagedStorageDiffHelper result将数据发送给adapter |
后续的直接看注释就可以了dispatchUpdatesTo方法传入的其实就是一个adapter,所以很显然,这些数据最终会发到adapter并且进行数据更新。再深入我也搞不来了,以后再说吧,先留个坑。
其实到现在我们只是这么说,但是如何让数据更新时将新数据发送,实现submitList方法?这个我也没特别看懂。就比如下面这些,在adapter里的利用Java 8 Lambda方法的 this :: submitList 为什么能够返回Observer? 它是如何实现动态submitList的?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// 监听数据列表变化
private final Observer<PagedList<T>> mDataListObserver = this::submitList;
// 监听LoadMore状态
private final Observer<NetworkStatus> mLoadingObserver = networkStatus -> {
if (networkStatus == null) {
return;
}
setLoadingStatus(networkStatus.getStatus());
};
// 监听Refresh状态
private final Observer<NetworkStatus> mRefreshObserver = networkStatus -> {
if (networkStatus == null) {
return;
}
setRefreshStatus(networkStatus.getStatus());
};
// 监听是否为空列表
private final Observer<Boolean> mEmptyObserver = isEmpty -> {
if (isEmpty == null) {
return;
}
setEmpty(isEmpty);
};
// 监听是否有更多
private final Observer<Boolean> mHasMoreObserver = hasMore -> {
if (hasMore == null) {
return;
}
setHasMore(hasMore);
};
为什么要这么写呢?其实这个就是lambda语法里面比较狗的地方了
先看个例子吧1
2
3
4
5
6
7
8//这是一段我从别的地方进行实验后搬来的代码,跟整体无关,
//这个说明了如果我们想对LiveData设置监听的话,必须传入一个LifeRecyclerOwner,一个Observer
feedViewModel.getArticleLiveData().observe(this, new Observer<PagedList<Article>>() {
public void onChanged(@Nullable PagedList<Article> articles) {
adapter.submitList(articles);
}
});
所以学长的骚操作完全可以有三种方法实现
第一种 菜鸡乖乖写法
1
2
3
4
5
6
7//
private final Observer<PagedList<T>> observer = new Observer<PagedList<T>>() {
public void onChanged(@Nullable PagedList<T> ts) {
getAdapter().submitList(ts);
}
};第二种,小小进阶
1
private final Observer<PagedList<T>> observer = ts -> this.submitList(ts);
第三种,灵魂代码手
1
private final Observer<PagedList<T>> mDataListObserver = this::submitList;
关于数据的绑定问题
之前一直被我忽略掉了bindViewHolder方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
int type = getItemViewType(position);
//前三种可能都是处理的异常情况
//1.数据请求到底,没有数据可返回
if (type == TYPE_EMPTY && viewHolder instanceof EmptyViewHolder) {
((EmptyViewHolder) viewHolder).bind((showLoadingStatusView() && isAtEnd()), isEmpty());
//2.正在加载数据
} else if (type == TYPE_LOADING && viewHolder instanceof LoadingViewHolder) {
((LoadingViewHolder) viewHolder).bind();
//3.数据出现错误
} else if (type == TYPE_ERROR && viewHolder instanceof ErrorViewHolder) {
((ErrorViewHolder) viewHolder).bind(showLoadingStatusView());
//4.正常情况,调用viewholder的bind方法,将data传入
} else if (viewHolder instanceof BaseViewHolder) {
T data = getItem(position);
((BaseViewHolder<T>) viewHolder).bind(data, position);
}
}
如果这时候我们点进去getItem方法,发现这个是PagedListAdapter的方法,其中由使用了mDiffer.getItem()方法。
1 |
|
这个mDiffer是个什么鬼?如果这时候你往上翻滚本文,在adapter实现过程的源码分析环节就可以看到这个mDiffer,这个是AsyncPagedListDiffer类的一个实例,我们之前知道了,当adapter调用了submit方法后,里面需要传入一个PagedList,然后这个pagedList就会赋值给AsyncPagedListDiffer里面的mPagedList,然后在getItem方法中可以通过mPagedList,根据位置调取单个实例。
1 |
|
得到了单个item的data后,我们就可以将data和position传入viewholder类实例的bind方法,那么在自定义的viewholder继承BaseViewHolder,可以实现bind方法,将data与view绑定
以PriceItemViewHolder为例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//重写了bind方法,参数为data,将数据与视图绑定
public void bind(Jade data, int position) {
mGoodsTitleTxt.setText(data.getTitle());
mRangeTxt.setText(warpPercent(Math.abs(data.getRange())));
mSpeedTxt.setText(warpPercent(Math.abs(data.getSpeed())));
mRangeLabelTxt.setText(data.getRange() >= 0
? R.string.price_label_range_rise
: R.string.price_label_range_drop);
mSpeedLabelTxt.setText(data.getSpeed() >= 0
? R.string.price_label_speed_rise
: R.string.price_label_speed_drop);
Glide.with(itemView).load(data.getCover()).into(mCoverImg);
if (withOrder) {
mRankOrder.setText(String.valueOf(position + 1));
mRankOrder.setVisibility(View.VISIBLE);
} else {
mRankOrder.setVisibility(View.GONE);
}
}
总结(UI部分还没看,先这么写一些)
Data
- PagingDatasource用来请求数据,并且将数据返回给观察它的类。
- PagingDtaSourceFactory是用来创建DataSource的实例的工具。
ViewModel
- PagingViewModel是能够创建各种LiveData的类,其中包括最重要的
LiveData<PagedList<T>>,通过LivePagedListBuilder,传入PagingDataSourceFactory和Config(Config是配置类,规定了请求页数,初始请求数量等一些基本配置),最终得到需要的LiveData<PagedList<T>>.PaginListAdapter继承PagedListAdapter
- 构造方法:构造方法中需要传入DiffUtil.ItemCallback类。而且在adapter中可以实现submitList(LiveData<PagedLsit
>)方法,从而实现对数据变换的监视,数据变动时会发送给adapter.(更新2019.8.2): 今天更加深入理解之后,进行一个新的解释,在adapter里面实现了对LiveData的观察,当LiveData发生变动时,会调用submitList方法,将变动的数据传入,然后结合我上面的那些调用过程,在调用的过程大部分是在DiffUtil.itemCallback的包装类中,所以一定使用了DiffUtil.itemcallback类(在具体使用时实现)中的对比方法,从而只将变动的数据传回adapter,提高了效率。 - 添加setViewModel方法:在活动中可以创建ViewModel实例,从而将实例传给adapter
- 提供抽象函数 CreateNormalViewHolder,用来创建不同页面的viewHolder.
- 构造方法:构造方法中需要传入DiffUtil.ItemCallback类。而且在adapter中可以实现submitList(LiveData<PagedLsit
使用
又可能上面这些东西你都看得觉得无比头疼,心想着我只需要知道怎么用就行了呀。行,那咱们就结合实例讲一讲怎么用
- 首先是ViewModel,由于增哥已经把PagingViewModel封装得很全面了,你只需要实现里面的抽象方法
protected abstract PagingDataSource<T> getDataSource();
例如我们进行一个请求1
2
3
4
5
6
7
8
9
10
11
12
13
14public interface PersonalApi {
/**
* 获取我的预约评估列表
*
* @param checkStatus 评估状态 : 送检中/已检验
* @param checkRes 评估结果 : 通过/未通过
* @return 预约列表
*/
("check/records/me")
Call<ApiResponse<List<JadeModel>>> getMyEvaluationList(("check_status") String checkStatus,
("check_res") String checkRes);
}
那么我们需要一个储藏室(repository)来进行数据请求以及实现接口回调