美文网首页 移动 前端 Python Android Java
JetPack (五)之 Paging 分页库

JetPack (五)之 Paging 分页库

作者: zcwfeng | 来源:发表于2021-01-25 17:12 被阅读0次

    类关系

    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

    角色

    • 角色1 数据源

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

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

    • 角色2 数据工厂

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

    • 角色3 数据模型

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

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

    • 角色4 适配器

    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:这个过程细枝末节很复杂,如果我们自己实现根据业务封装核心部分就好。

    相关文章

      网友评论

        本文标题:JetPack (五)之 Paging 分页库

        本文链接:https://www.haomeiwen.com/subject/uhgcsktx.html