avatar

目录
Jetpack学习-Paging

个人博客

http://www.milovetingting.cn

Jetpack学习-Paging

Paging是什么

分页库可一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。

简单使用

引入Paging

在需要引入Paging模块的build.gradle中配置

gradle
1
2
def paging_version = "2.1.0"
implementation "androidx.paging:paging-runtime:$paging_version"

定义Bean

java
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
public class Student {

private String id;

private String name;

private String gender;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return id.equals(student.id) &&
name.equals(student.name) &&
gender.equals(student.gender);
}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public int hashCode() {
return Objects.hash(id, name, gender);
}
}

需要重写equalshashCode方法,后面比较数据时会用到

定义DataSource

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class StudentDataSource extends PositionalDataSource<Student> {
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
callback.onResult(getStudents(0, Config.SIZE), 0, 1000);
}

@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Student> callback) {
callback.onResult(getStudents(params.startPosition, params.loadSize));
}

private List<Student> getStudents(int startPosition, int pageSize) {
List<Student> list = new ArrayList<>();
for (int i = startPosition; i < startPosition + pageSize; i++) {
Student student = new Student();
student.setId("ID:" + i);
student.setName("名称:" + i);
student.setGender("性别:" + i);
list.add(student);
}
return list;
}
}

定义一个类继承自PositionalDataSource,这是一个固定大小的数据源。这里只作演示,具体业务可以根据实际情况修改。

在这个类中定义获取数据的方法getStudents,然后重写loadInitial,loadRange方法

定义DataSourceFactory

java
1
2
3
4
5
6
7
8
public class StudentDataSourceFactory extends DataSource.Factory<Integer, Student> {
@NonNull
@Override
public DataSource<Integer, Student> create() {
StudentDataSource dataSource = new StudentDataSource();
return dataSource;
}
}

定义ViewModel

java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class StudentViewModel extends ViewModel {

private final LiveData<PagedList<Student>> listLiveData;

public StudentViewModel() {
StudentDataSourceFactory factory = new StudentDataSourceFactory();
this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, Config.SIZE).build();
}

public LiveData<PagedList<Student>> getListLiveData() {
return listLiveData;
}
}

定义Adapter

java
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
public class RecyclerPagingAdapter extends PagedListAdapter<Student, RecyclerPagingAdapter.RecyclerViewHolder> {

private static DiffUtil.ItemCallback<Student> DIFF_STUDENT = new DiffUtil.ItemCallback<Student>() {
@Override
public boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
return oldItem.getId() == newItem.getId();
}

@Override
public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
return oldItem.equals(newItem);
}
};

public RecyclerPagingAdapter() {
super(DIFF_STUDENT);
}

@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//LayoutInflater.from(parent.getContext()).inflate(R.layout.item_paging, null);不能在宽度上满屏
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_paging, parent,false);
return new RecyclerViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
Student student = getItem(position);
if (student == null) {
holder.tvId.setText("加载中");
holder.tvName.setText("加载中");
holder.tvGender.setText("加载中");
} else {
holder.tvId.setText(student.getId());
holder.tvName.setText(student.getName());
holder.tvGender.setText(student.getGender());
}
}

public static class RecyclerViewHolder extends RecyclerView.ViewHolder {

TextView tvId;
TextView tvName;
TextView tvGender;

public RecyclerViewHolder(@NonNull View itemView) {
super(itemView);
tvId = itemView.findViewById(R.id.id);
tvName = itemView.findViewById(R.id.name);
tvGender = itemView.findViewById(R.id.gender);
}
}

}

显示数据

java
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
public class PagingActivity extends AppCompatActivity {

RecyclerView recyclerView;
RecyclerPagingAdapter adapter;
StudentViewModel viewModel;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paging);

recyclerView = findViewById(R.id.rv);
adapter = new RecyclerPagingAdapter();
viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(StudentViewModel.class);
viewModel.getListLiveData().observe(this, new Observer<PagedList<Student>>() {
@Override
public void onChanged(PagedList<Student> students) {
adapter.submitList(students);
}
});
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

}
}

原理

分析Paging,首先从获取数据开始: viewModel.getListLiveData()

java
1
2
3
public LiveData<PagedList<Student>> getListLiveData() {
return listLiveData;
}

listLiveData在构造方法中赋值

java
1
2
3
4
public StudentViewModel() {
StudentDataSourceFactory factory = new StudentDataSourceFactory();
this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, Config.SIZE).build();
}

通过LivePagedListBuilder的build方法赋值

java
1
2
3
public LiveData<PagedList<Value>> build() {
return create(this.mInitialLoadKey, this.mConfig, this.mBoundaryCallback, this.mDataSourceFactory, ArchTaskExecutor.getMainThreadExecutor(), this.mFetchExecutor);
}

调用create

java
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
private static <Key, Value> LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey, @NonNull final Config config, @Nullable final BoundaryCallback boundaryCallback, @NonNull final Factory<Key, Value> dataSourceFactory, @NonNull final Executor notifyExecutor, @NonNull final Executor fetchExecutor) {
return (new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
private final InvalidatedCallback mCallback = new InvalidatedCallback() {
public void onInvalidated() {
invalidate();
}
};

protected PagedList<Value> compute() {
Key initializeKey = initialLoadKey;
if (this.mList != null) {
initializeKey = this.mList.getLastKey();
}

do {
if (this.mDataSource != null) {
this.mDataSource.removeInvalidatedCallback(this.mCallback);
}

this.mDataSource = dataSourceFactory.create();
this.mDataSource.addInvalidatedCallback(this.mCallback);
this.mList = (new androidx.paging.PagedList.Builder(this.mDataSource, config)).setNotifyExecutor(notifyExecutor).setFetchExecutor(fetchExecutor).setBoundaryCallback(boundaryCallback).setInitialKey(initializeKey).build();
} while(this.mList.isDetached());

return this.mList;
}
}).getLiveData();
}

实例化了一个ComputableLiveData对象

java
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
public ComputableLiveData(@NonNull Executor executor) {
this.mInvalid = new AtomicBoolean(true);
this.mComputing = new AtomicBoolean(false);
this.mRefreshRunnable = new Runnable() {
@WorkerThread
public void run() {
boolean computed;
do {
computed = false;
if (ComputableLiveData.this.mComputing.compareAndSet(false, true)) {
try {
Object value;
for(value = null; ComputableLiveData.this.mInvalid.compareAndSet(true, false); value = ComputableLiveData.this.compute()) {
computed = true;
}

if (computed) {
ComputableLiveData.this.mLiveData.postValue(value);
}
} finally {
ComputableLiveData.this.mComputing.set(false);
}
}
} while(computed && ComputableLiveData.this.mInvalid.get());

}
};
this.mInvalidationRunnable = new Runnable() {
@MainThread
public void run() {
boolean isActive = ComputableLiveData.this.mLiveData.hasActiveObservers();
if (ComputableLiveData.this.mInvalid.compareAndSet(false, true) && isActive) {
ComputableLiveData.this.mExecutor.execute(ComputableLiveData.this.mRefreshRunnable);
}

}
};
this.mExecutor = executor;
this.mLiveData = new LiveData<T>() {
protected void onActive() {
ComputableLiveData.this.mExecutor.execute(ComputableLiveData.this.mRefreshRunnable);
}
};
}

ComputableLiveData的构造方法中,定义了一个mRefreshRunnable,当LiveDataonActive方法回调时,就会执行mRefreshRunnable

RefreshRunnablerun方法,先执行compute然后会通过ComputableLiveData.this.mLiveData.postValue(value)刷新

先来看compute方法

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected PagedList<Value> compute() {
Key initializeKey = initialLoadKey;
if (this.mList != null) {
initializeKey = this.mList.getLastKey();
}

do {
if (this.mDataSource != null) {
this.mDataSource.removeInvalidatedCallback(this.mCallback);
}

this.mDataSource = dataSourceFactory.create();
this.mDataSource.addInvalidatedCallback(this.mCallback);
this.mList = (new androidx.paging.PagedList.Builder(this.mDataSource, config)).setNotifyExecutor(notifyExecutor).setFetchExecutor(fetchExecutor).setBoundaryCallback(boundaryCallback).setInitialKey(initializeKey).build();
} while(this.mList.isDetached());

return this.mList;
}

在这个方法里面调用dataSourceFactory.create()

这个实现在我们定义的类中

java
1
2
3
4
public DataSource<Integer, Student> create() {
StudentDataSource dataSource = new StudentDataSource();
return dataSource;
}

然后通过PagedList.Builder.build()对mList进行赋值

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public PagedList<Value> 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.create方法

java
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
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) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
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的构造函数

java
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
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 = 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;
}

调用dispatchLoadInitial

java
1
2
3
4
5
6
7
8
9
10
11
void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
final int convertPosition = position == null ? 0 : position;

// Note enablePlaceholders will be false here, but we don't have a way to communicate
// this to PositionalDataSource. This is fine, because only the list and its position
// offset will be consumed by the LoadInitialCallback.
mSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
pageSize, mainThreadExecutor, receiver);
}

调用dispatchLoadInitial

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final void dispatchLoadInitial(boolean acceptCount,
int requestedStartPosition, int requestedLoadSize, int pageSize,
@NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
LoadInitialCallbackImpl<T> callback =
new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);

LoadInitialParams params = new LoadInitialParams(
requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
loadInitial(params, callback);

// If initialLoad's callback is not called within the body, we force any following calls
// to post to the UI thread. This constructor may be run on a background thread, but
// after constructor, mutation must happen on UI thread.
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}

调用loadInitial,这里调用到了我们定义的StudentDataSource

java
1
2
3
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
callback.onResult(getStudents(0, Config.SIZE), 0, 1000);
}

调用onResult

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void onResult(@NonNull List<T> data, int position) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
if (position < 0) {
throw new IllegalArgumentException("Position must be non-negative");
}
if (data.isEmpty() && position != 0) {
throw new IllegalArgumentException(
"Initial result cannot be empty if items are present in data set.");
}
if (mCountingEnabled) {
throw new IllegalStateException("Placeholders requested, but totalCount not"
+ " provided. Please call the three-parameter onResult method, or"
+ " disable placeholders in the PagedList.Config");
}
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
}
}

调用dispatchResultToReceiver

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void dispatchResultToReceiver(final @NonNull PageResult<T> 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);
}
}

compute暂时分析到这里,后面先不作过多深入

compute执行后,会执行postValue,这样在Activity中的回调就会执行

java
1
2
3
public void onChanged(PagedList<Student> students) {
adapter.submitList(students);
}

会调用submitList

java
1
2
3
public void submitList(@Nullable PagedList<T> pagedList) {
this.mDiffer.submitList(pagedList);
}

调用submitList

java
1
2
3
public void submitList(@Nullable PagedList<T> pagedList) {
this.submitList(pagedList, (Runnable)null);
}

调用submitList

java
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
public void submitList(@Nullable final PagedList<T> pagedList, @Nullable final Runnable commitCallback) {
if (pagedList != null) {
if (this.mPagedList == null && this.mSnapshot == null) {
this.mIsContiguous = pagedList.isContiguous();
} else if (pagedList.isContiguous() != this.mIsContiguous) {
throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both contiguous and non-contiguous lists.");
}
}

final int runGeneration = ++this.mMaxScheduledGeneration;
if (pagedList == this.mPagedList) {
if (commitCallback != null) {
commitCallback.run();
}

} else {
PagedList<T> previous = this.mSnapshot != null ? this.mSnapshot : this.mPagedList;
if (pagedList == null) {
int removedCount = this.getItemCount();
if (this.mPagedList != null) {
this.mPagedList.removeWeakCallback(this.mPagedListCallback);
this.mPagedList = null;
} else if (this.mSnapshot != null) {
this.mSnapshot = null;
}

this.mUpdateCallback.onRemoved(0, removedCount);
this.onCurrentListChanged(previous, (PagedList)null, commitCallback);
} else if (this.mPagedList == null && this.mSnapshot == null) {
this.mPagedList = pagedList;
pagedList.addWeakCallback((List)null, this.mPagedListCallback);
this.mUpdateCallback.onInserted(0, pagedList.size());
this.onCurrentListChanged((PagedList)null, pagedList, commitCallback);
} else {
if (this.mPagedList != null) {
this.mPagedList.removeWeakCallback(this.mPagedListCallback);
this.mSnapshot = (PagedList)this.mPagedList.snapshot();
this.mPagedList = null;
}

if (this.mSnapshot != null && this.mPagedList == null) {
final PagedList<T> oldSnapshot = this.mSnapshot;
final PagedList<T> newSnapshot = (PagedList)pagedList.snapshot();
this.mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
public void run() {
final DiffResult result = PagedStorageDiffHelper.computeDiff(oldSnapshot.mStorage, newSnapshot.mStorage, AsyncPagedListDiffer.this.mConfig.getDiffCallback());
AsyncPagedListDiffer.this.mMainThreadExecutor.execute(new Runnable() {
public void run() {
if (AsyncPagedListDiffer.this.mMaxScheduledGeneration == runGeneration) {
AsyncPagedListDiffer.this.latchPagedList(pagedList, newSnapshot, result, oldSnapshot.mLastLoad, commitCallback);
}

}
});
}
});
} else {
throw new IllegalStateException("must be in snapshot state to diff");
}
}
}
}

这个方法里会执行onCurrentListChanged进行通知更新。

Paging的原理先分析到这里。什么?这也太简单了吧,根本没有讲清楚具体的流程啊!你是不是不会原理啊!!!嗯~说对了,目前还只是理解到这里,后续再补充(手动尴尬)!

文章作者: milovetingting
文章链接: http://www.milovetingting.cn/2020/04/18/Android/Jetpack%E5%AD%A6%E4%B9%A0-Paging/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 milovetingting

评论