写点什么

Jetpack 之 Paging,android 面试自我介绍

用户头像
Android架构
关注
发布于: 18 小时前
  • @param params

  • @param callback*/@Overridepublic 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;}}

角色 2 数据工厂

创建管理数据源的工厂,为什么有一个工厂,除了可以去创建数据源之外,为了后续的扩展



/**


  • 数据的工厂*/public class StudentDataSourceFactory extends DataSource.Factory<Integer, Student> {


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

角色 3 数据模型

数据模型其实就是 ViewModel,用来管理数据


PagedList: 数据源获取的数据最终靠 PagedList 来承载。对于 PagedList,我们可以这样来理解,它就是一页数据的集合。 每请求一页,就是新的一个 PagedList 对象。



public class StudentViewModel extends ViewModel {


// 看源码:@1 listLiveData 数据怎么来的 private final LiveData<PagedList<Student>> listLiveData;


public StudentViewModel() {StudentDataSourceFactory factory = new StudentDataSourceFactory();


// 初始化 ViewModelthis.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, Flag.SIZE).build();}


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

角色 4 适配器

这个 Adapter 就是一个 RecyclerView 的 Adapter。不过我们在使用 paging 实现 RecyclerView 的分页加载效果,不能直接继承 RecyclerView 的 Adapter,而是需要继承 PagedListAdapter。


LiveData 观察到的数据,把感应到的数据给适配器,适配器又绑定了 RecyclerView,那么 RecyclerView 的列表数据就改变了



public class RecyclerPagingAdapter extends PagedListAdapter<Student, RecyclerPagingAdapter.MyRecyclerViewHolder> {


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


// 一般是比较 唯一性的内容, ID@Overridepublic boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {return oldItem.getId().equals(newItem.getId());}


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


protected RecyclerPagingAdapter() {super(DIFF_STUDNET);}


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


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


// item view 出来了, 分页库还在加载数据中,我就显示 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 优化的 ViewHolderpublic 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); // IDtvName = itemView.findViewById(R.id.tv_name); // 名称 tvSex = itemView.findViewById(R.id.tv_sex); // 性别}}


}

展示结果


public class MainActivity extends AppCompatActivity {


private RecyclerView recyclerView;RecyclerPagingAdapter recyclerPagingAdapter;StudentViewModel viewModel;


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


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


// 最新版本初始化 viewModelviewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(StudentViewModel.class);


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


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

Paging 的各个角色职责

  • DataSource:数据的来源;

  • DataSource.Factory:工厂类提供 DataSource 的实例,在自定义 DataSource 时使用;

  • PagedList:数据集散中心,根据需要向 DataSource 索取加载数据,并将得到的数据传递到 PagedListAdapter;

  • PagedListAdapter:数据适配器,这里除了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向 PagedList 加载数据;

  • DiffUtil.ItemCallback:判断数据是否发生改变以确定界面是否更新;

数据源详解

DataSource 是一个抽象类,但是我们不能直接继承它实现它的子类。但是 Paging 库里提供了它的三个子类供我们继承用于不同场景的实现:


第一种:PositionalDataSource:适用于目标数据总数固定,通过特定的位置加载数据,这里 Key 是 Integer 类型的位置信息,T 即 Value。 比如从数据库中的 1200 条开始加在 20 条数据。


第二种:ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定 item 的信息, 即 Key 字段包含的是 Item 中的信息,比如需要根据第 N 项的信息加载第 N+1 项的数据,传参中需要传入第 N 项的 ID 时,该场景多出现于论坛类应用评论信息的请求。


第三种:PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,即 Key 字段是页相关的信息。比如请求的数据的参数中包含类似 next / pervious 页数的信息。

Paging 的源码分析

类之关系

abstract class DataSource<Key, Value>:



abstract class ItemKeyedDataSource<Key, Value>:



abstract class PageKeyedDataSource<Key, Value>:



abstract class PositionalDataSource: 我们刚刚使用的是这个 数据源子类



DataSource 的三个子类:


PageKeyedDataSource:如果页面需要实现上一页、下一页,需要将请求的 Token 传递到下一步时使用 ItemKeyedDataSource:程序需要根据上一条数据信息(ID)获取下一条数据时使用 PositionalDataSource:需要从数据存储中选择的任何位置获取数据页;例如,请求可能返回以位置 1200 开头的 20 个数据项


当然是在拿取数据的地方开始分析,Paging 组件的开始执行都是从创建 LiveData 开始的,我们源码的分析也从 LiveData 的创建开始一探 Paging 背后的逻辑,我们开始分析吧:

初始化工作


开始查看 ”private final LiveData<PagedList> listLiveData;“ 此变量是如何创建的:


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


点击进入 build 函数分析:


@NonNull@SuppressLint("RestrictedApi")public LiveData<PagedList<Value>> build() {return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);}


进入 create 函数分析:


使用 LivePagedListBuilder 配置 Factory 和 Config,然后调用 build 创建实例,在 build 方法中直接调用了 create()方法创建 LiveData


@AnyThread@NonNullprivate 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) {


// 注意:在这里创建 ComputableLiveData 抽象类 return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {


@Nullableprivate PagedList<Value> mList;@Nullableprivate DataSource<Key, Value> mDataSource;


private final DataSource.InvalidatedCallback mCallback =new DataSource.InvalidatedCallback() {@Overridepublic void onInvalidated() {invalidate();}};


// 注意,在这里重写 compute 方法, 是我们需要的 PagedList<Value>@Overrideprotected PagedList<Value> compute() {@Nullable Key initializeKey = initialLoadKey;if (mList != null) {//noinspection uncheckedinitializeKey = (Key) mList.getLastKey();}


do {if (mDataSource != null) {mDataSource.removeInvalidatedCallback(mCallback);}// 从 Builder 中传入的 Factory 中创建 DataSourcemDataSource = dataSourceFactory.create();mDataSource.addInvalidatedCallback(mCallback);// 创建 PagedListmList = new PagedList.Builder<>(mDataSource, config).setNotifyExecutor(notifyExecutor).setFetchExecutor(fetchExecutor).setBoundaryCallback(boundaryCallback).setInitialKey(initializeKey).build();} while (mList.isDetached());return mList;}}.getLiveData();}


在 create()中直接返回了 ComputableLiveData 的实例,在 ComputableLiveData 实例重写的 compute 中执行了一些主要操作:


? 一:调用传入的 Factory 的 create()创建 DataSource 实例; ? 二:创建并返回 PagedList 实例; ? 三:PagedList.build() & PagedList.create() 就是如下代码(细节);


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


public PagedList<Value> build() {// TODO: define defaults, once they can be used in module without android dependencyif (mNotifyExecutor == null) {throw new IllegalArgumentException("MainThreadExecutor required");}if (mFetchExecutor == null) {throw new IllegalArgumentException("BackgroundThreadExecutor required");}


//noinspection uncheckedreturn PagedList.create(mDataSource,mNotifyExecutor,mFetchExecutor,mBoundaryCallback,mConfig,mInitialKey);}


Page


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


dList 的创建过程,在 PagedList.build()中调用了 PagedList.create(),所以真正的创建是在 create()中发生的:


private static <K, T> PagedList<T> create(...) {if (dataSource.isContiguous() || !config.enablePlaceholders) {......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);}}


从上面的代码中看出根据 条件(dataSource.isContiguous() || !config.enablePlaceholders)的不同分别创建 ContiguousPagedList 和 TiledPagedList,其实这里就是区分上面的三个自定义 DataSource 的类型(三个数据源),如果是 PositionalDataSource 创建 TiledPagedList,其他的返回 ContiguousPagedList,我们依次查看三个 DataSource 中的 isContiguous()方法:


PositionalDataSource 类中的:


@Overrideboolean isContiguous() {


return false;}


ItemKeyedDataSource 和 PageKeyedDataSource 都继承与 ContiguousDataSource,只查看 ContiguousDataSource 类中的:


@Overrideboolean isContiguous() {


return true;}


又回来,从 LivePageListBuilder .build 开始看:


new ComputableLiveData有什么用 与 何时执行 compute 函数, 这两个疑问,查看 ComputableLiveData 源码,发现在 ComputableLiveData 的构造函数中创建 LiveData 实例,下面查看 Runnable 接口中执行了哪些逻辑:


public ComputableLiveData(@NonNull Executor executor) {


mExecutor = executor;


mLiveData = new LiveData<T>() {


@Override


protected void onActive() {


mExecutor.execute(mRefreshRunnable);


}


};


}


final Runnable mRefreshRunnable = new Runnable() {


@WorkerThread


@Override


public void run() {


boolean computed;


do {


computed = false;


// compute can happen only in 1 thread but no reason to lock others.if (mComputing.compareAndSet(false, true)) {


// as long as it is invalid, keep computing.try {


T value = null;


while (mInvalid.compareAndSet(true, false)) {


computed = true;


// 这里会执行 compute(); 函数// 调用了 compuet 创建了 PagedListvalue = compute();


}


if (computed) {


// 设置 LiveData 的值 mLiveData.postValue(value);


}


} finally {


// release compute lockmComputing.set(false);


}


} .......


} while (computed && mInvalid.get());


}


};


在 mRefreshRunnable 中调用了 ComputableLiveData 的 compute()方法创建了 PagedList,所以此处的 Value 就是 PagedList,然后为 mLiveData 初始化赋值 PagedList。


细心的会留意到,在上面的 create()方法最后一句调用了 getLiveData()获取到的就是 ComputableLiveData 构造函数中创建的 LIveData:


@SuppressWarnings("WeakerAccess")


@NonNull


public LiveData<T> getLiveData() {


return mLiveData;


}


到这里为止,LiveData 终于创建完成了

数据的加载工作

ContiguousPagedList 作为出发点

当我们自定义实现 ItemKeySource 时,创建的 PagedList 实际为 ContiguousPagedList,查看 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;


}

ItermKeyDataSource 的 dispatchLoadInitial()

在构造函数中执行一下逻辑,所以继续追踪代码:


? 第一点:创建 PagedStorage 实例,主要根据滑动的位置显示是否要继续加载数据


? 第二点:调用 DataSource.dispatchLoadInitial 方法,此时使用的时 ItermKeyDataSource 的 dispatchLoadInitial 方法


@Override


final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,


boolean enablePlaceholders,@NonNull Executor mainThreadExecutor,


@NonNull PageResult.Receiver<Value> receiver) {


LoadInitialCallbackImpl<Value> callback = new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver); loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);


callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);


}


上面代码在 ItermKeyDataSource 的 dispatchLoadInitial()方法中调用了抽象函数 loadInitial(),根据前面的学习我们知道在 loadInitial() 中设置了初始化的网络请求,到此实现了 Paging 组件初始化数据的加载

数据的显示工作

在自定义 ItemDataSource 的 loadInitial()中加载数据后,调用了 callback.onResult(it?.data!!.datas!!)方法,此处的 callback 是 LoadInitialCallback 的实现类 LoadInitialCallbackImpl,在 onResult()方法中又调用了 LoadCallbackHelper.dispatchResultToReceiver()


static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {


final LoadCallbackHelper<Value> mCallbackHelper;


private final PageKeyedDataSource<Key, Value> mDataSource;


private final boolean mCountingEnabled;


LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,


boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Jetpack之Paging,android面试自我介绍