Paging框架

Paging学习

Paging是Google 2018 IO大会最新发布的Jetpack中的一个组件,主要用于大数据的分页加载

需要补充的知识

我的理解

首先来说,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 表达式

  1. DataSource可以利用Retrofit等方式,通过传入代理类进行数据请求,将数据返回到被观察的LiveData中。
  2. 设置可取消类Disposable,防止内存泄漏???????
  3. PositionalDataSource中需要实现两种方法:LoadInitial, LoadRange.进行数据请求时代理类中的参数有:初始请求的数据位置(startpostion),每次请求的页数(pagesize), PagaingCallback接口实例(这个是自定义的)
  4. 无论是LoadInitial还是LoadRange,方法参数都有callback, callback都有一个方法onResult(List<>), 调用这个方法,便可将数据传送到被观察的数据中。

    1. 与此同时,在请求前,请求成功以及请求失败时,都应更新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);
    }

    @Override
    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() {
    @Override
    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);
    }
    }

    @Override
    public void onError(Exception e) {
    mViewModel.getRefreshState().postValue(NetworkStatus.error());
    //重新进行初始化数据请求
    mRetry = () -> loadInitial(params, callback);
    }
    });
    }

    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<T> callback) {

    mViewModel.getLoadingState().postValue(NetworkStatus.loading());
    mApi.getDataList(params.startPosition, PagingViewModel.PAGE_SIZE, new PagingCallBack() {
    @Override
    public void onSuccess(List list) {
    mViewModel.getLoadingState().postValue(NetworkStatus.success());
    if (isEmptyList(list)) {
    callback.onResult(new ArrayList<>());
    } else {
    callback.onResult(list);
    }
    }

    @Override
    public void onError(Exception e) {
    mViewModel.getLoadingState().postValue(NetworkStatus.error());
    mRetry = () -> loadRange(params, callback);
    }
    });
    }


    private boolean isEmptyList(List list) {
    return list == null || list.isEmpty();
    }

    //这个应该是取消订阅或者销毁线程的时候用到的,不用太管它
    @Override
    public void onInvalidated() {
    removeInvalidatedCallback(this);
    mDisposable.clear();
    }
    }
  • DataSourceFactory
  1. 这个就很简单了,创建一个Multabel<DataSource>, 创建构造方法,重写create方法,这个会在接下来的ViewModel中用到。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class PagingDataSourceFactory<T> extends DataSource.Factory<Integer, T> {


    private MutableLiveData<PagingDataSource<T>> mDataSource = new MutableLiveData<>();

    public PagingDataSourceFactory(PagingDataSource<T> dataSource) {
    mDataSource.setValue(dataSource);
    }

    @Override
    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
      94
        public 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();
      }
  • PagingListAdapter
    深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法

## 老铁顶住,我要放大招了:##

刺激不 ( ๑乛◡乛๑ )(一点一点地看)

  • 首先我们先看adapter的构造方法

    1
    2
    3
    4
    5
    protected 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
2
3
4
5
protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
//这是PagedListAdapter的构造函数,调用了AsyncPagedListDiffer的构造函数
mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
mDiffer.mListener = mListener;
}

而在adapter中可以调用一个submitList方法,如下图,调用了PagedListAdapter的submitList方法,从而调用了AsyncPagedListDiffer的submitList方法。

1
2
3
4
5
6
//AsyncPagedListDiff.java 149行 这个构造是从PagedListAdapter调过来的
public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mUpdateCallback = new AdapterListUpdateCallback(adapter);
mConfig = new AsyncDifferConfig.Builder<T>(diffCallback).build();
}
1
2
3
4
//PagedListAdapter 150行
public void submitList(PagedList<T> pagedList) {
mDiffer.submitList(pagedList);
}

在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
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

//AsyncPagedListDiffer.java 230行

public void submitList(final PagedList<T> pagedList){

//······//

mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
//调用PagedStorageHelper的computeDiff方法得到DiffResult
result = PagedStorageDiffHelper.computeDiff(
oldSnapshot.mStorage,
newSnapshot.mStorage,
mConfig.getDiffCallback());

mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {

//***********************调用了如下方法
latchPagedList(pagedList, newSnapshot, result);
//************************
}
}
});
}
});

}


private void latchPagedList(
PagedList<T> newList, PagedList<T> diffSnapshot,
DiffUtil.DiffResult diffResult) {
if (mSnapshot == null || mPagedList != null) {
throw new IllegalStateException("must be in snapshot state to apply diff");
}

PagedList<T> previousSnapshot = mSnapshot;
mPagedList = newList;
mSnapshot = null;
//mUpdateCallback = new AdapterListUpdateCallback(adapter);

PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
previousSnapshot.mStorage, newList.mStorage, diffResult);//result在这里出现了***************继续点击进去这个方法

newList.addWeakCallback(diffSnapshot, mPagedListCallback);
if (mListener != null) {
mListener.onCurrentListChanged(mPagedList);
}
}
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
//PagedStorageDiffHelper result将数据发送给adapter

static <T> void dispatchDiff(ListUpdateCallback callback,
final PagedStorage<T> oldList,
final PagedStorage<T> newList,
final DiffUtil.DiffResult diffResult) {

final int trailingOld = oldList.computeTrailingNulls();
final int trailingNew = newList.computeTrailingNulls();
final int leadingOld = oldList.computeLeadingNulls();
final int leadingNew = newList.computeLeadingNulls();

if (trailingOld == 0
&& trailingNew == 0
&& leadingOld == 0
&& leadingNew == 0) {
// Simple case, dispatch & return 发送数据给adapter
diffResult.dispatchUpdatesTo(callback);
return;
}

//········//


}

后续的直接看注释就可以了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>>() {
@Override
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>>() {
    @Override
    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
@Override
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
2
3
4
@Nullable
protected T getItem(int position) {
return mDiffer.getItem(position);
}

这个mDiffer是个什么鬼?如果这时候你往上翻滚本文,在adapter实现过程的源码分析环节就可以看到这个mDiffer,这个是AsyncPagedListDiffer类的一个实例,我们之前知道了,当adapter调用了submit方法后,里面需要传入一个PagedList,然后这个pagedList就会赋值给AsyncPagedListDiffer里面的mPagedList,然后在getItem方法中可以通过mPagedList,根据位置调取单个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Nullable
public T getItem(int index) {
if (mPagedList == null) {
if (mSnapshot == null) {
throw new IndexOutOfBoundsException(
"Item count is zero, getItem() call is invalid");
} else {
return mSnapshot.get(index);
}
}

mPagedList.loadAround(index);
return mPagedList.get(index);
}

得到了单个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,将数据与视图绑定
@Override
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.

使用

又可能上面这些东西你都看得觉得无比头疼,心想着我只需要知道怎么用就行了呀。行,那咱们就结合实例讲一讲怎么用

  • 首先是ViewModel,由于增哥已经把PagingViewModel封装得很全面了,你只需要实现里面的抽象方法
    protected abstract PagingDataSource<T> getDataSource();
    例如我们进行一个请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface PersonalApi {

    /**
    * 获取我的预约评估列表
    *
    * @param checkStatus 评估状态 : 送检中/已检验
    * @param checkRes 评估结果 : 通过/未通过
    * @return 预约列表
    */
    @GET("check/records/me")
    Call<ApiResponse<List<JadeModel>>> getMyEvaluationList(@Query("check_status") String checkStatus,
    @Query("check_res") String checkRes);

    }

那么我们需要一个储藏室(repository)来进行数据请求以及实现接口回调

-------------本文结束感谢您的阅读-------------