如何应对 Android 面试官 -> 玩转 Jetpack Paging

前言


本章讲解 Paging,依然是从基础使用和原理两个方向;

是什么?

Paging 是 Jetpack 提供的一个分页组件,可以更轻松地在应用程序中的RecyclerView逐步和优雅地加载数据;数据请求消耗的网络带宽更少,系统资源更少;即使在数据更新和刷新期间,应用程序仍会继续快速响应用户输入;不过多浪费,显示多少就用多少;

使用篇


Paging 的使用主要区分在 DataSource 的使用,定义 DataSource 一共有三种:

继承 PositionalDataSource

继承 PageKeyedDataSource

继承 ItemKeyedDataSource

使用起来相对来说一般,需要提供四个类:

  • DataSource(是数据源,包含了多种形式,例如:Room 来源,PositionalDataSource 来源,PageKeyedDataSource 来源,ItemKeyedDataSource 来源)
  • PagedList(是 UIModel 数据层,通过 Factory 的方式拿到数据源)
  • PagedAdapter(这里不再是之前使用 RecycleView 的那种适配器了,而是和 Paging 配套的 PagedListAdapter)
  • RecycleView(是之前用的 RecycleView,只不过 setAdapter 的时候,绑定的适配器是 PagedAdapter)

依赖

arduino 复制代码
// Paging库依赖
implementation 'androidx.paging:paging-runtime:xxx'

数据源 PositionalDataSource 的基础使用

数据bean

用来展示列表数据,那么我们就先来定义一个数据 bean

typescript 复制代码
public class Student {

    private String id;
    private String name;
    private String sex;

    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 getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    // 比较的函数
    @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) &&
                sex.equals(student.sex);
    }

    // 比较的函数
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public int hashCode() {
        return Objects.hash(id, name, sex);
    }
}

一定要重写 equals 方法,Paging 用来判断是否相等,从而决定要不要刷新;

数据源 DataSource

接下来我们来定义数据源,需要继承 Paging 中的 PositionalDataSource 来定义数据源

less 复制代码
/**
 * 数据的来源(room,网络,...)
 *
 * 这里是数据源,获取数据目前是在这里完成的
 *
 * 官方文档上,继承的是 ItemKeyedDataSource, 而这里实现的是 PositionalDataSource
 *
 * PositionalDataSource<Student>: 适用于目标数据总数的固定,通过特别的位置加载数据(0-10)
 *  比如从数据库中的1200条开始加在20条数据。
 */
public class StudentDataSource extends PositionalDataSource<Student> {

    /**
     * 可以理解是加载第一页数据的时候,会执行此函数来完成
     * 加载初始化数据,可以这么来理解,加载的是第一页的数据。
     * 形象的说,当我们第一次打开页面,需要回调此方法来获取数据。
     * @param params
     * @param callback
     */
    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
        // @1:数据源 @2:位置 @3:总大小
        new Thread(){
            public void run() {
                callback.onResult(getStudents(0, 20), 0, 1000);
            }
        }.start()
    }

    /**
     * 当有了初始化数据之后,滑动的时候如果需要加载数据的话,会调用此方法。
     * @param params
     * @param callback
     */
    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Student> callback) {
        // @1:从哪里开始加载(位置内部算的)  @2:size(size 内部算的)
        callback.onResult(getStudents(params.startPosition, params.loadSize));
    }

    /**
     * 可以理解这里是数据源,数据的来源(数据库,文件,网络服务器响应等等)
     * @param startPosition
     * @param pageSize
     * @return
     */
    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.setSex("我性别:" + i);
            list.add(student);
        }
        return list;
    }
}

这个 loadInitial 方法可以理解为:加载第一页数据的时候,会执行此函数来完成;

这个 loadRange 方法可以理解为:加载更多数据的时候,会执行此函数来完成;

常规业务开发,我们就是使用这个

数据工厂 DataSource.Factory

数据工厂需要继承 DataSource.Factory 来实现;

scala 复制代码
/**
 * 数据的工厂
 */
public class StudentDataSourceFactory extends DataSource.Factory<Integer, Student> {

    @NonNull
    @Override
    public DataSource<Integer, Student> create() {
        StudentDataSource studentDataSource = new StudentDataSource();
        return studentDataSource;
    }
}

主要用来创建我们的 DataSource,也就是 StudentDataSource

适配器 PagedListAdapter

接下来我们来定义 Adapter,使用 Paging 需要继承 PagedListAdapter,而不是继承 RecyclerView.Adapter

less 复制代码
public class RecyclerPagingAdapter extends PagedListAdapter<Student, RecyclerPagingAdapter.MyRecyclerViewHolder> {

    // TODO 比较的行为
    private static DiffUtil.ItemCallback<Student> DIFF_STUDENT = new
            DiffUtil.ItemCallback<Student>() {

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

                // 对象本身的比较
                @Override
                public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
                    return oldItem.equals(newItem);
                }
            };

    protected RecyclerPagingAdapter() {
        // Remind
        super(DIFF_STUDENT);
    }

    @NonNull
    @Override
    public MyRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null);
        return new MyRecyclerViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyRecyclerViewHolder holder, int position) {
        Student student = getItem(position);

        // itemView 出来了, 分页库还在加载数据中,就显示Id加载中
        if (null == student) {
            holder.tvId.setText("Id加载中");
            holder.tvName.setText("Name加载中");
            holder.tvSex.setText("Sex加载中");
        } else {
            holder.tvId.setText(student.getId());
            holder.tvName.setText(student.getName());
            holder.tvSex.setText(student.getSex());
        }
    }

    // Item 优化的 ViewHolder
    public static class MyRecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView tvId;
        TextView tvName;
        TextView tvSex;

        public MyRecyclerViewHolder(View itemView) {
            super(itemView);
            tvId = itemView.findViewById(R.id.tv_id); // ID
            tvName = itemView.findViewById(R.id.tv_name); // 名称
            tvSex = itemView.findViewById(R.id.tv_sex); // 性别
        }
    }
}

需要注意的地方就是构造方法中,需要传入 DiffUtil.ItemCallback 从而让 PagedListAdapter 来判断变化的 item,从而只刷新需要改变的 item;

组合使用

接下来,我们来定义承载 RecyclerView 的 Activity,以及在 Activity 中使用 Factory,官方文档是建议我们放在 ViewModel 中来使用,我们来定一个 ViewModel;

ViewModel
scala 复制代码
/**
 * PagedList: 数据源获取的数据最终靠 PagedList 来承载。
 * 对于 PagedList, 我们可以这样来理解,它就是一页数据的集合。
 * 每请求一页,就是新的一个 PagedList 对象。
 */
public class StudentViewModel extends ViewModel {

    private final LiveData<PagedList<Student>> listLiveData;

    public StudentViewModel() {
        StudentDataSourceFactory factory = new StudentDataSourceFactory();
        // 初始化 ViewModel
        this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, 20).build();
    }

    // 暴露数据出去
    public LiveData<PagedList<Student>> getListLiveData() {
        return listLiveData;
    }
}

构造方法中,直接创建 StudentDataSourceFactory 并交给 LivePagedListBuilder 来构建 LiveData

不想用 ViewModel 的可以直接拿 new LivePagedListBuilder<Integer, Student>(factory, 20).build();

RecyclerView
scala 复制代码
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    RecyclerPagingAdapter recyclerPagingAdapter;
    StudentViewModel viewModel;

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

        recyclerView =  findViewById(R.id.recycle_view);
        recyclerPagingAdapter = new RecyclerPagingAdapter();

        // 初始化 ViewModel
        viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory())
                .get(StudentViewModel.class);

        // LiveData 观察者感应更新
        viewModel.getListLiveData().observe(this, new Observer<PagedList<Student>>() {
            @Override
            public void onChanged(PagedList<Student> students) {
                // 再这里更新适配器数据
                recyclerPagingAdapter.submitList(students);
            }
        });

        recyclerView.setAdapter(recyclerPagingAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
    }
}

Activity 中就是很基础的使用方式,adapter 设置数据不再是自定义 setData 而是通过 submitList 方法来实现;

数据源 ItemKeyedDataSource 的基础使用

需要我们继承 ItemKeyedDataSource 类;

less 复制代码
/**
 * ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定 item 的信息,
 * 即 Key 字段包含的是 Item 中的信息,比如需要根据第 N 项的信息加载第 N+1 项的数据,传参中需要传入第N 项的 ID,
 * 该场景多出现于论坛类应用评论信息的请求。
 */
public class CustomItemDataSource extends ItemKeyedDataSource<Integer, Person> {

    private DataRepository dataRepository;

    CustomItemDataSource(DataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    // loadInitial 初始加载数据
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Person> callback) {
        List<Person> dataList = dataRepository.initData(params.requestedLoadSize);
        callback.onResult(dataList);
    }

    @NonNull
    @Override
    public Integer getKey(@NonNull Person item) {
        return (int) System.currentTimeMillis();
    }

    // loadBefore 向前分页加载数据
    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList);
        }
    }

    // loadAfter 向后分页加载数据
    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList);
        }
    }
}

这里主要的区别是 loadBeforeloadAfter 方法,向前分页加载数据和向后分页加载数据;

params.key 以及 params.requestedLoadSize 是由 Paging 框架帮我们算好并返回的;

这个通常是 一次加一条数据

就像下面这样,每次都加载一条数据进来;

数据源 PageKeyedDataSource 的基础使用

需要我们继承 PageKeyedDataSource

less 复制代码
/**
 * PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,
 * 即 Key 字段是页相关的信息。比如请求的数据的参数中包含类似next / pervious页数的信息。
 */
public class CustomPageDataSource extends PageKeyedDataSource<Integer, Person> {

    private DataRepository dataRepository;

    CustomPageDataSource(DataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    // loadInitial 初始加载数据
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Person> callback) {
        List<Person> dataList = dataRepository.initData(params.requestedLoadSize);
        callback.onResult(dataList, 0, 2);
    }

    // loadBefore 向前分页加载数据
    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList, params.key - 1);
        }
    }

    // loadAfter 向后分页加载数据
    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList, params.key + 1);
        }
    }
}

区别在于 onResult 传递的参数不一样了,每个方法的 onResult 中都额外添加了参数,用来表示要加载的目标页面数据;

这个通常是 一次加一页数据

一次加载一页,这个有点类似下面这样,点击1 加载1页面内容,点击2 加载2页面内容

结合 Room 使用

Room 天然的支持了 Paging,也就是 Dao 中支持了返回 DataSource.Factory

java 复制代码
@Dao
public interface StudentDao {
    @Insert
    void insertStudents(Student... students);

    @Query("DELETE FROM student_table")
    void deleteAllStudents();

    @Query("SELECT * FROM student_table ORDER BY id")
    DataSource.Factory<Integer, Student> getAllStudents();
}

所以,我们在构建 LivePagedListBuilder 的时候,可以直接传入

ini 复制代码
studentsDatabase = StudentsDatabase.getInstance(this);
studentDao = studentsDatabase.getStudentDao();
// 直接通过 Room 获取返回的 Factory 然后传给 LivePagedListBuilder
allStudentsLivePaged = new LivePagedListBuilder<>(studentDao.getAllStudents(), 2)
        .build();
allStudentsLivePaged.observe(this, new Observer<PagedList<Student>>() {
    @Override
    public void onChanged(final PagedList<Student> students) {
        pagedAdapter.submitList(students);
    }
});

直接通过 Room 获取返回的 Factory 然后传给 LivePagedListBuilder;

原理篇


我们就从数据的初始化作为入口来分析,我们进入 new LivePagedListBuilder<>(studentDao.getAllStudents(), 2).build() 的 build 方法看下:

less 复制代码
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) {
    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();
                    }
                };

        @SuppressWarnings("unchecked") // for casting getLastKey to Key
        @Override
        protected PagedList<Value> compute() {
            @Nullable Key initializeKey = initialLoadKey;
            if (mList != null) {
                initializeKey = (Key) mList.getLastKey();
            }

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

                mDataSource = dataSourceFactory.create();
                mDataSource.addInvalidatedCallback(mCallback);

                mList = new PagedList.Builder<>(mDataSource, config)
                        .setNotifyExecutor(notifyExecutor)
                        .setFetchExecutor(fetchExecutor)
                        .setBoundaryCallback(boundaryCallback)
                        .setInitialKey(initializeKey)
                        .build();
            } while (mList.isDetached());
            return mList;
        }
    }.getLiveData();
}

这里直接 new 了一个 ComputableLiveData 我们进入这个 ComputableLiveData 的构造方法看下:

less 复制代码
public ComputableLiveData(@NonNull Executor executor) {
    mExecutor = executor;
    mLiveData = new LiveData<T>() {
        @Override
        protected void onActive() {
            mExecutor.execute(mRefreshRunnable);
        }
    };
}

这里直接创建了 LiveData 通过前面我们学习 LiveData 可以知道,当有观察者订阅的时候,这个 onActive 会执行,我们进入这个 mRefreshRunnable 看下:

java 复制代码
final Runnable mRefreshRunnable = new Runnable() {
    @WorkerThread
    @Override
    public void run() {
        boolean computed;
        do {
            computed = false;
            if (mComputing.compareAndSet(false, true)) {
                try {
                    T value = null;
                    while (mInvalid.compareAndSet(true, false)) {
                        computed = true;
                        value = compute();
                    }
                    if (computed) {
                        mLiveData.postValue(value);
                    }
                } finally {
                    mComputing.set(false);
                }
            }
        } while (computed && mInvalid.get());
    }
};

最终会通过 mLiveData.postValue(value) 将我们的数据发送出去,这样我们就在 observe 回调方法中接收到这个数据了,那么这个数据是怎么构建的呢?通过上面的 value = compute() 可以知道,最终是调用的我们重写的 compute 方法,也就是我们创建 ComputableLiveData 的时候重写的 compute 方法;

我们进入这个方法看下:

scss 复制代码
protected PagedList<Value> compute() {
    @Nullable Key initializeKey = initialLoadKey;
    .... 省略部分代码

    do {
        .... 省略部分代码
        // 核心逻辑1 创建 DataSource
        mDataSource = dataSourceFactory.create();
        
        .... 省略部分代码
        
        // 核心逻辑2 构建 PagedList
        mList = new PagedList.Builder<>(mDataSource, config)
                .setNotifyExecutor(notifyExecutor)
                .setFetchExecutor(fetchExecutor)
                .setBoundaryCallback(boundaryCallback)
                .setInitialKey(initializeKey)
                .build();
    } while (mList.isDetached());
    return mList;
}

核心逻辑1,最终调用的是我们自己创建的 DataSource 的 create 方法,也就是 StudentDataSourceFactory 中的 create 方法,获取 StudentDataSource;

核心逻辑2:构建 PagedList,我们进入这个 build 方法看下:

csharp 复制代码
public PagedList<Value> build() {
    return PagedList.create(
            mDataSource,
            mNotifyExecutor,
            mFetchExecutor,
            mBoundaryCallback,
            mConfig,
            mInitialKey);
}

这里直接调用了 PagedList 的 create 方法,我们进入这个方法看下:

less 复制代码
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) {
    
        .... 省略部分代码
        
        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);
    }
}

这个方法返回了两种 PagedList,一种是 ContiguousPagedList 一种是 TiledPagedList

我们通过查看 PagedList 的子类,其实可以看到

通过复写 isContiguous 这方法,可以知道 ContiguousPagedList 直接返回了 trueTiledPagedList 直接返回了 false

我们进入 ContiguousPagedList 的构造方法看下:

less 复制代码
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 方法,我们进入这个方法看下:

这个方法也是提供了三个实现,我们需要进入的是 ContiguousWithoutPlaceholdersWrapper 进入之后,可以看到,它其实是 PositionalDataSource 的一个内部类,而 PositionalDataSource 又是我们前面使用的 DataSource 之一;

我们进入这个 PositionalDataSourcedispatchLoadInitial 方法看下:

less 复制代码
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;
    // 核心逻辑
    mSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
            pageSize, mainThreadExecutor, receiver);
}

这里也是直接调用了 dispatchLoadInitial 方法,我们进入看下:

less 复制代码
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);
    callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}

可以看到,这个 loadInitial 是一个抽象方法,由我们具体实现的 DataSource 来实现,也就是我们的 StudentDataSource 来实现;

最终,我们的初始化数据,通过 onResult 回传回去;

less 复制代码
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
    // @1:数据源 @2:位置 @3:总大小
    callback.onResult(getStudents(0, Flag.SIZE), 0, 1000);
}

我们接下来看下,这个 callback 是如何将数据回调回去的,由 dispatchLoadInitial 方法可以知道,LoadInitialCallback 有一个实现类 LoadInitialCallbackImpl

ini 复制代码
LoadInitialCallbackImpl<T> callback =
            new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);

我们进入实现类的 onResult 方法看下:

arduino 复制代码
public void onResult(@NonNull List<T> data, int position, int totalCount) {
    if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
        LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
        // 核心逻辑,if else 中最终都是调用的 dispatchResultToReceiver
        if (mCountingEnabled) {
            int trailingUnloadedCount = totalCount - position - data.size();
            mCallbackHelper.dispatchResultToReceiver(
                    new PageResult<>(data, position, trailingUnloadedCount, 0));
        } else {
            mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
        }
    }
}

if else 中最终都是调用的 dispatchResultToReceiver 方法,我们进入这个方法看下:

java 复制代码
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
    Executor executor;
    synchronized (mSignalLock) {
        mHasSignalled = true;
        executor = mPostExecutor;
    }

    if (executor != null) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                mReceiver.onPageResult(mResultType, result);
            }
        });
    } else {
        mReceiver.onPageResult(mResultType, result);
    }
}

最终都是调用的 onPageResult 方法,只不过一个是子线程,一个是主线程,我们进入这个方法看下:

kotlin 复制代码
PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
    @AnyThread
    public void onPageResult(int resultType, @NonNull PageResult<V> pageResult) {
        if (pageResult.isInvalid()) {
            ContiguousPagedList.this.detach();
        } else if (!ContiguousPagedList.this.isDetached()) {
            List<V> page = pageResult.page;
            if (resultType == 0) {
                // 核心逻辑1
                ContiguousPagedList.this.mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, ContiguousPagedList.this);
                .... 省略部分代码
            } else {
                .... 省略部分代码
                if (resultType == 1) {
                    if (skipNewPage && !trimFromFront) {
                        .... 省略部分代码
                    } else {
                        // 核心逻辑2
                        ContiguousPagedList.this.mStorage.appendPage(page, ContiguousPagedList.this);
                    }
                } else {
                    .... 省略部分代码
                    if (skipNewPage && trimFromFront) {
                        .... 省略部分代码
                    } else {
                        // 核心逻辑3
                        ContiguousPagedList.this.mStorage.prependPage(page, ContiguousPagedList.this);
                    }
                }
                .... 省略部分代码
            }
            .... 省略部分代码
        }
    }
};

三个比较核心的调用 初始化数据:this.mStorage.init ,向下翻页:this.mStorage.appendPage ,向上翻页:this.mStorage.prependPage

我们主要来看下 init 方法

less 复制代码
void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
        @NonNull Callback callback) {
    init(leadingNulls, page, trailingNulls, positionOffset);
    // 核心逻辑
    callback.onInitialized(size());
}

我们接着看下 onInitialized 的具体实现:

scss 复制代码
public void onInitialized(int count) {
    // 核心逻辑
    notifyInserted(0, count);
    mReplacePagesWithNulls =
            mStorage.getLeadingNullCount() > 0 || mStorage.getTrailingNullCount() > 0;
}

这里调用了 notifyInserted 方法,我们进入看下:

arduino 复制代码
void notifyInserted(int position, int count) {
    if (count != 0) {
        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
            final Callback callback = mCallbacks.get(i).get();
            if (callback != null) {
                // 核心逻辑
                callback.onInserted(position, count);
            }
        }
    }
}

最终调用的是 onInserted 方法,我们进入这个方法看下:

less 复制代码
public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, @NonNull AsyncDifferConfig<T> config) {
    class NamelessClass_1 extends PagedList.Callback {
        public void onInserted(int position, int count) {
            // 核心逻辑
            AsyncPagedListDiffer.this.mUpdateCallback.onInserted(position, count);
        }
        .... 省略部分代码
    }
    .... 省略部分代码
}

我们继续进入这个 onInserted 方法看下

可以看到,这个 onInserted 方法有多个实现,最终指向的是 AdapterListUpdateCallback 中的 onInserted 方法;

arduino 复制代码
public void onInserted(int position, int count) {
    mAdapter.notifyItemRangeInserted(position, count);
}

最终调用到了 RecyclerView 的 Adapter 的 notifyItemRangeInserted 插入数据的方法,来将初始化数据插入到对应的位置;

这是使用 Paging 来完成数据到 RecyclerView 的填充,当我们结合 Room 使用的时候,是通过

ini 复制代码
studentsDatabase = StudentsDatabase.getInstance(this);
studentDao = studentsDatabase.getStudentDao();
allStudentsLivePaged = new LivePagedListBuilder<>(studentDao.getAllStudents(), 2)
        .build();
allStudentsLivePaged.observe(this, new Observer<PagedList<Student>>() {
    @Override
    public void onChanged(final PagedList<Student> students) {
        pagedAdapter.submitList(students);
    }
});

我们直接构建的 LivePagedListBuilder ,然后在 onChanged 中调用 submitList 来实现数据到 RecyclerView 的填充;

submitList 最终也是会调用到 onInserted 方法;

ini 复制代码
mUpdateCallback.onInserted(0, pagedList.size());

总结 :本质上就是用 DataSource 来对接开发者的不同需求,然后用不同的 PagedList 来封装不同的数据,不管封装的是哪一种数据,最终通过 AsyncPagedListDiffer (这个同步机制) 来和 RecyclerView 的 API 对接;

好了,Paging 就讲到这里吧~

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

相关推荐
alexhilton2 天前
借助RemoteCompose开发动态化页面
android·kotlin·android jetpack
QING6182 天前
Jetpack Compose Brush API 简单使用实战 —— 新手指南
android·kotlin·android jetpack
QING6182 天前
Jetpack Compose Brush API 详解 —— 新手指南
android·kotlin·android jetpack
QING6184 天前
Jetpack Compose 中 Flow 收集详解 —— 新手指南
android·kotlin·android jetpack
ljt27249606614 天前
Compose笔记(五十七)--snapshotFlow
android·笔记·android jetpack
QING6184 天前
kotlin 协程: GlobalScope 和 Application Scope 选择和使用 —— 新手指南
android·kotlin·android jetpack
QING6185 天前
Kotlin 协程中Job和SupervisorJob —— 新手指南
android·kotlin·android jetpack
天花板之恋5 天前
Compose中的协程:rememberCoroutineScope 和 LaunchedEffect
android jetpack
我命由我123455 天前
Android 开发问题:布局文件中的文本,在预览时有显示出来,但是,在应用中没有显示出来
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
ljt27249606615 天前
Compose笔记(五十八)--LinearOutSlowInEasing
android·笔记·android jetpack