Jetpack学习-Paging

个人博客

http://www.milovetingting.cn

Jetpack学习-Paging

Paging是什么

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

简单使用

引入Paging

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

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

定义Bean

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

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

public class StudentDataSourceFactory extends DataSource.Factory<Integer, Student> {
    @NonNull
    @Override
    public DataSource<Integer, Student> create() {
        StudentDataSource dataSource = new StudentDataSource();
        return dataSource;
    }
}

定义ViewModel

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

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);
        }
    }

}

显示数据

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()

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

listLiveData在构造方法中赋值

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

通过LivePagedListBuilder的build方法赋值

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

调用create

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对象

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方法

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()

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

public DataSource<Integer, Student> create() {
        StudentDataSource dataSource = new StudentDataSource();
        return dataSource;
    }

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

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方法

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的构造函数

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

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

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

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

调用onResult

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

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中的回调就会执行

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

会调用submitList

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

调用submitList

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

调用submitList

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

原文地址:https://www.cnblogs.com/milovetingting/p/12726872.html