移动 前端 Python Android Java

JetPack (五)之 Paging 分页库

2021-01-25  本文已影响0人  zcwfeng

类关系

2021-01-24 13.03.05.png

DataSource 类关系

paging_类结构.png

abstract 子类 PageKeyedDataSource

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

如果页面需要实现上一页、下一页,需要将请求的Token传递到下一步时使用

abstract 子类 ItemKeyedDataSource

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

程序需要根据上一条数据信息(ID)获取下一条数据时使用

abstract 子类 PositionalDataSource

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

需要从数据存储中选择的任何位置获取数据页;例如,请求可能返回以位置1200开头的20个数据项

源码流程分析

20181021221030916.gif

角色

数据的来源,可以有多种来源渠道,例如:“网络数据”,“本地数据”,“数据库数据”

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

管理 数据源 的工厂,为了后续的扩展

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

PagedList: 数据源获取的数据最终靠PagedList来承载。
对于PagedList,我们可以这样来理解,它就是一页数据的集合。
每请求一页,就是新的一个PagedList对象。
数据集散中心,根据需要向DataSource索取加载数据,并将得到的数据传递到PagedListAdapter

RecyclerView的Adapter需要继承PagedListAdapter

LiveData观察到的数据,把感应到的数据 给 适配器,适配器又绑定了 RecyclerView,那么RecyclerView的列表数据就改变了
数据适配器,这里除了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向PagedList加载数据

Paging的各个角色职责:

DataSource:数据的来源
DataSource.Factory:工厂类提供DataSource的实例,在自定义DataSource时使用
PagedList:数据集散中心,根据需要向DataSource索取加载数据,并将得到的数据传递到PagedListAdapter
PagedListAdapter:数据适配器,这里除了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向PagedList加载数据
DiffUtil.ItemCallback:判断数据是否发生改变以确定界面是否更新

调用和入口

我们用最简单的使用demo,做分析入口。
RecyclerView----ViewModel----LiveData

public class PagingActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    RecyclerViewPageingAdapter recyclerViewPageingAdapter;
    MyViewModel studentViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_paging);
        studentViewModel = new ViewModelProvider(this,
                new ViewModelProvider.NewInstanceFactory())
                .get(MyViewModel.class);
        recyclerView = findViewById(R.id.recycle_view);
        recyclerViewPageingAdapter = new RecyclerViewPageingAdapter();
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(recyclerViewPageingAdapter);

        studentViewModel.getListLiveData().observe(this, new Observer<PagedList<MyStudent>>() {
            @Override
            public void onChanged(PagedList<MyStudent> myStudents) {
                recyclerViewPageingAdapter.submitList(myStudents);
            }
        });

    }
}

核心就是ViewModel获取LiveData数据源,在观察方法合适的时机将数据发射回调到RecyclerView的UI展示

看下我们StudentViewModel中 studentViewModel.getListLiveData() 的源码

public
class MyViewModel extends ViewModel {
    private final LiveData<PagedList<MyStudent>> listLiveData;

    public MyViewModel() {
        StudentDataSourceFactory factory = new StudentDataSourceFactory();
        this.listLiveData = new LivePagedListBuilder<Integer, MyStudent>
                (factory, Config.PAGESIZE)
                .build();
    }


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

分析

先从数据源DataSource入手

LivePagedListBuilder(xxx).build()------>
create() 创建LiveData
ComputableLiveData<PagedList<Value>> 大管家

简单来看最外层架构模型

ComputableLiveData 包装了LiveData和Executor,通过Executor的任务 通过 compute() 方法 返回LiveData---PagedList 的过程。

LivePagedListBuilder(xxx).build()------>
create() 创建LiveData
ComputableLiveData<PagedList<Value>> 大管家

------->
DataSource.Factory#create
创建DataSource

自定义实现数据源创建 StudentDataSourceFactory extends DataSource.Factory<Integer, MyStudent>

------>
DataSoure#addInvalidatedCallback
执行回调 onInvalidated 回调方法,不停的判断,isActive 激活执行任务mInvalidationRunnable

------>
PagedList.Builder#build()

------------>
PagedList#create

----------->
大体上分成两类:ContiguousPagedList 和TiledPagedList
通过条件if (dataSource.isContiguous() || !config.enablePlaceholders) 判断

mEnablePlaceholders 默认true 也就是config.enablePlaceholders 默认true

1. isContigous==true 并且 config.enablePlaceHolders==true
new ContiguousPagedList
创建 ContiguousPagedList

2. isContigous==false 并且 config.enablePlaceHolders==false
PagedList#wrapAsContiguousWithoutPlaceholders
包裹PositionalDataSource
生成ContiguousDataSource包装类

3. isContigous==false 并且config.enablePlaceHolders==true
创建TiledPagedList
new TiledPagedList

-----------> 我们随便走一个人值,回调显示路径差不多,选择路径 1 分析
PageKeyedDataSource#dispatchLoadInitial

----------> 
LoadInitialCallbackImpl实例化
PageResult.Receiver 传入

---------->
loadInitial(LoadInitialCallbackImpl 回调)
留给数据仓库调用实现,例子

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

----------->
调用回调
PageKeyedDataSource#onResult
LoadInitialCallbackImpl#onResult实现细节

----------->
PageKeyedDataSource #LoadCallbackHelper#dispatchResultToReceiver

---------->
PageResult.Receiver#mReceiver#onPageResult

---------->
会有两个回调
1. ContiguousPagedList#onPageResult
2. TiledPagedList#onPageResult

---------->
我们还是根据分析路径选择 路径 1
返回PageResult 有0,1,2,3 type类型
resultType==0,1,2 的时候会执行有3个分支
1.PageList#mStorage#init
2.PageList#mStorage#appendPage
3. PageList#mStorage#prependPage

----------->
随意选择一个,我们跟踪3 路径
PageStorage#Callback#onPagePrepended

----------->
PageList#notifyInserted------>AsyncPagedListDiffer#onInserted

----------->
RecyclerView#notifyItemRangeInserted
终点,RecyclerView 执行相应的方法

image-20200405190707615.png

图片展示调用的流程

细化分析调用

数据的创建

开始查看 ”private final LiveData<PagedList<Student>> listLiveData;“ 此变量是如何创建的:
我们自己定义的方法

public MyViewModel() {
        StudentDataSourceFactory factory = new StudentDataSourceFactory();
        this.listLiveData = new LivePagedListBuilder<Integer, MyStudent>
                (factory, Config.PAGESIZE)
                .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

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

PagedList的创建过程,在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类中的:

@Override
boolean isContiguous() {
    return false;
}

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

@Override
boolean isContiguous() {
    return true;
}

又回来,从 .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创建了PagedList
                            value = compute();
                        }
                        if (computed) {
                            // 设置LiveData的值
                            mLiveData.postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.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;
    }

数据的加载工作

ContiguousPagedList 作为触发点:

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

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

​ 第一点:创建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()

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

在dispatchResultToReceiver()方法中,调用PageResult.Receiver.onPageResult()方法,这里的mReceiver是在调用 mDataSource.dispatchLoadInitial()时传入的最后一个参数,他的实现在ContiguousPagedList中匿名创建:


        // mSignalLock protects mPostExecutor, and mHasSignalled
        private final Object mSignalLock = new Object();
        private Executor mPostExecutor = null;
        private boolean mHasSignalled = false;

        LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
                @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
            mDataSource = dataSource;
            mResultType = resultType;
            mPostExecutor = mainThreadExecutor;
            mReceiver = receiver;
        }

ContiguousPagedList:
在onPageResult()方法中根据resultType的类型执行操作,PageResult的三个数据类型分别对应者ItemKeyDataSource的三个方法:

loadInitial:对应初始化状态PageResult.INIT
loadBefore:对应初始化状态PageResult.PREPEND
loadAfter:对应初始化状态PageResult.APPEND

此出分析初始化,回调的类型为PageResult.INIT,调用了PagedStorage的init()方法:

在init()方法中首先调用另一个init()方法记录加载的位置,并保存加载的数据, 然后调用callback.onInitialized(),在onInitialzed()方法中调用了notifyInserted(),在notifyInserted()中遍历mCallbacks回调callback的onInserted()

interface Callback {
        void onInitialized(int count);
        void onPagePrepended(int leadingNulls, int changed, int added);
        void onPageAppended(int endPosition, int changed, int added);
        void onPagePlaceholderInserted(int pageIndex);
        void onPageInserted(int start, int count);
        void onPagesRemoved(int startOfDrops, int count);
        void onPagesSwappedToPlaceholder(int startOfDrops, int count);
        void onEmptyPrepend();
        void onEmptyAppend();
    }

继续追踪:

ContiguousPagedList:
public void onInitialized(int count) {
        notifyInserted(0, count);
}
 
PagedList:
void notifyInserted(int position, int count) {
        if (count != 0) {
            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
                Callback callback = mCallbacks.get(i).get();
                if (callback != null) {
                    callback.onInserted(position, count);
                }
            }
        }
    }
PagedList 的 接口:
public abstract void onInserted(int position, int count);

一: 加载的数据保存在PagedStorage中,并记录了加载的位置信息
二: 加载完成后根据数据的变化,回调callback.onInserted()通知数据改变的数量和位置

CallBack 是哪来的,我们简单的追踪下代码 能否到PagedListAdapter

AsyncPagedListDiffer:

public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, @NonNull AsyncDifferConfig<T> config) {
        class NamelessClass_1 extends Callback {
            NamelessClass_1() {
            }

            public void onInserted(int position, int count) {
                AsyncPagedListDiffer.this.mUpdateCallback.onInserted(position, count);
            }

            public void onRemoved(int position, int count) {
                AsyncPagedListDiffer.this.mUpdateCallback.onRemoved(position, count);
            }

            public void onChanged(int position, int count) {
                AsyncPagedListDiffer.this.mUpdateCallback.onChanged(position, count, (Object)null);
            }
        }

ListUpdateCallback:

public interface ListUpdateCallback {
   ......
    void onInserted(int position, int count);

AdapterListUpdateCallback:

@Override
public void onInserted(int position, int count) {
    mAdapter.notifyItemRangeInserted(position, count);
}

PS:这个过程细枝末节很复杂,如果我们自己实现根据业务封装核心部分就好。

上一篇下一篇

猜你喜欢

热点阅读