Android Jetpack架构组件之 Paging(使用、源码篇)

1、前言

最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。

Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:

  •  带你领略Android Jetpack组件的魅力
  • Android Jetpack 架构组件之 Lifecycle(使用篇)
  • Android Jetpack 架构组件之 Lifecycle(源码篇)
  • Android Jetpack 架构组件之 ViewModel (源码篇)
  • Android Jetpack 架构组件之 LiveData(使用、源码篇)
  • Android Jetpack架构组件之 Paging(使用、源码篇)
  • Android Jetpack 架构组件之 Room(使用、源码篇)
  • Android Jetpack 架构组件之Navigation
  • Android Jetpack架构组件之WorkManger
  • 实战:从0搭建Jetpack版的WanAndroid客户端​​​​​​​

上述时Android Architecture所提供的架构组件,本文主要从使用和源码的角度分析Paging组件

2、Paging简介

  • 分页库概述
  1. Paging Library中DataSource,PagedList,PagedAdapter三者之间的关系以及数据加载到数据展示的流程

Android Jetpack架构组件之 Paging(使用、源码篇)_第1张图片

比如当一条新的item插入到数据库,DataSource会被初始化,LiveData后台线程就会创建一个新的PagedList。这个新的PagedList会被发送到UI线程的PagedListAdapter中,PagedListAdapter使用DiffUtil在对比现在的Item和新建Item的差异。当对比结束,PagedListAdapter通过调用RecycleView.Adapter.notifyItemInserted()将新的item插入到适当的位置

  • PagedList:是Paging Library的关键组件,它是一个异步加载应用程序数据块或页面的集合
  • Data
  1. 每个PagedList实例都会加载应用程序DataSource的最新数据
  2. 数据从应用程序的后端或数据库流入PagedList对象
  • UI:PagedList类与 PagedListAdapter一起加载 数据到RecyclerView中
  • Paging组件好处
  1. 分页库使您可以更轻松地在应用程序中的RecyclerView逐步和优雅地加载数据
  2. 数据请求消耗的网络带宽更少,系统资源更少
  3. 即使在数据更新和刷新期间,应用程序仍会继续快速响应用户输入

3、Paging组件的使用

3.1、添加依赖

def paging_version = "1.0.0"
implementation "android.arch.paging:runtime:$paging_version"
testImplementation "android.arch.paging:common:$paging_version"
implementation "android.arch.paging:rxjava2:1.0.0-rc1"

3.2、Paging使用步骤

  • 定义分页配置
  1. Page size :页面大小即每次加载时加载的数量
  2. Prefetch distance:预取距离,给定UI中最后一个可见的Item,超过这个item应该预取一段数据
  3. UI占位符:setEnablePlaceholders()
val myPagingConfig = PagedList.Config.Builder()       // 分页设置
        .setPageSize(50)
        .setPrefetchDistance(150)
        .setEnablePlaceholders(true)
        .build()
  • 使用分页配置创建LiveData ,传入DataSource.Factory,用于创建DataSource,从DataSource中加载数据到PagedList中
val concertList = LivePagedListBuilder(myConcertDataSource, myPagingConfig)
        .setFetchExecutor(myExecutor)
        .build()
  • 观察LiveData,在数据改变时调用适配器刷新数据
viewModel.concertList.observe(this, { pagedList ->
                adapter.submitList(pagedList) })

3.3、Paging和Room的使用

  • 在Room的@Dao中添加查询方法
@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll() : DataSource.Factory  // 返回DataSOurce.Factory配合PagingList使用
}
  • 在ViewModel中查询数据库并创建LiveData
class ViewModelUser(application: Application) : AndroidViewModel(application) {
      val dao = UserDataBase.getInstence(application)?.getDao()
////传入Room返回的DataSource.Factory
      var liveArray : LiveData> = 
LivePagedListBuilder(dao!!.getAll(),PagedList.Config.Builder() 
              .setPageSize(10)
              .setPrefetchDistance(10)
              .setEnablePlaceholders(true)
              .build()).build()
}
  • 创建PagedListAdapter的实现类,加载并显示数据
class Adapter : PagedListAdapter(diffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
      return UserViewHolder(layoutInflater.inflate(R.layout.item,parent,false))
     }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position)
        if (user != null){
            holder.bind(user)
        }else{
            holder.clear()
        }
      }

    companion object {
        val diffCallback = object : DiffUtil.ItemCallback() {
            override fun areItemsTheSame(oldItem: User?, newItem: User?): Boolean {
              return  oldItem?.id == newItem?.id
            }
            override fun areContentsTheSame(oldItem: User?, newItem: User?): Boolean {
                return oldItem == newItem
               }
        }
    }
    class UserViewHolder(val view : View) :
            RecyclerView.ViewHolder(view){
        private val  tvId : TextView = view.findViewById(R.id.tvId)
        private val  tvName : TextView = view.findViewById(R.id.tvName)

        fun bind(user: User){
            tvId.text = user.id.toString()
            tvName.text = user.name
        }

        fun clear(){
            tvName.text = null
            tvId.text = null
        }
}

这里说一下传入的DiffUtil.ItemCallback<> 实例,当数据加载到PagedListAdapter时,会回调DiffUtil.ItemCallback中两个抽象方法,确认数据和之前是否发生了改变,如果改变则调用Adapter更新数据:

  1. areItemTheSame():是否为同一个Item
  2. areContentsTheSame():数据内容是否发生变化
  • 添加LiveData观察者
 val viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java!!)
        val recyclerView = findViewById(R.id.concert_list)
        val adapter = ConcertAdapter()
        viewModel.concertList.observe(this, { pagedList ->
               ... })
        recyclerView.setAdapter(adapter)

这里使用ViewModel中保存加载到的LivaData(关于ViewModel点击查看另一篇Android Jetpack架构组件之 ViewModel (源码篇)),为LiveData添加观察者,当数据发生改变时回调方法,将数据发送到PagedListAdapter中更新界面UI

  • 将 LiveData 实例连接到 PagedListAdapter
 adapter.submitList(pagedList)
  • Room和PagedList配合使用有点
  1. Room数据库查询数据作为PagedList的数据源,实现列表的自动加载和下拉加载更多数据
  2. 当数据库改变时会自动回调观察者从而刷新界面

3.4、使用RxJava2观察分页数据

Paging除了支持LiveData加载数据外,还支持RxJava2观察数据,通过创建一个Observable或Flowable实例,观察数据的改变并发送数据到Adapter,下面用Flowable代替LiveData:

var liveArray : Flowable> = RxPagedListBuilder(dao!!.getAll(),PagedList.Config.Builder()
              .setPageSize(10)
              .setPrefetchDistance(10)
              .setEnablePlaceholders(true)
              .build()).buildFlowable(BackpressureStrategy.LATEST)

和创建LiveData一样传入DataSource.Factory和PagedList.Config,并配置背压策略,创建Flowable后像RxJava正常使用一样订阅观察者即可获取数据:

viewModel.concertList.subscribe({
                flowableList -> adapter.submitList(flowableList)

3.5、Paging使用的注意事项

  • 构建可观察者列表
  1. 观察对象的创建一般在ViewModel中,UI代码观察ViewModel中的LiveData对象,实现列表和内容的联系
  2. 向LivePagedListBuilder 或RxPagedListBuilder 传入DataSource.Factory的实例,创建可观察的PagedList对象
  3. 一个DataSource对象为一个PagedList加载页面
  4. 工厂类创建新实例 PagedList以响应内容更新
  • 选择正确的数据类型

Paging组件除了自身创建的DataSource以及Room的配合使用外,还支持自定以实现DataSource,组件提供了一下三种模式的DataSource,我们在使用时只需根据自己的需求选择何时的实现子类:

  1. PageKeyedDataSource:如果页面需要实现上一页、下一页,需要将请求的Token传递到下一步
  2. ItemKeyedDataSource:程序需要根据上一条数据信息(ID)获取下一条数据时
  3. PositionalDataSource:需要从数据存储中选择的任何位置获取数据页;例如,请求可能返回以位置1200开头的20个数据项
  • 数据无效时通知
  1. 在加载数据时,很多都会配合数据刷新和数据时效性,使用分页库时,数据层由表或行变得陈旧时通知应用程序的其他层,使用DataSource.invalidate()通知数据刷新
  2. 应用的UI可以使用下拉刷新模型触发数据失效功能
  • 内容更新

构建可观察 PagedList对象时,需要考虑内容的更新方式,按数据的来源分为本地加载和网络加载:

  1. 从Room数据库加载数据,在数据路内容发生改变时,则会自动将更新推送到您应用的UI
  2. 如果分页网络API数据,则通常会进行用户交互;例如:“滑动刷新”作为当前DataSource失效,并请求新数据的信号

4、Paging自定义DataSource

在开发过程中,除了查询本地数据库和Room配合使用不需要自定义DataSource外,其他加载网络数据时可能都需要自定义来控制数据的获取,下面以实现ItemKeyedDataSource为例分析使用自定义DataSource

  • 实现ItemKeyedDataSource的子类
class ItemDataSource : ItemKeyedDataSource() {
    
    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun loadBefore(params: LoadParams, callback: LoadCallback) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun getKey(item: ArticleBean): Int {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

实现ItemKeyedDataSource的子类,这里传入的两个泛型:Key表示每一个Item的标志,这里使用Int即每个Item的id,Value :加载的数据,这里传入数据类型Bean,然后重写四个方法:

  1. getKey:记录当前加载时最新的Item的Key
  2. loadInitial:执行每次进入界面的第一次加载,即数据刷新
  3. loadBefore:在指定LoadParams中Key之前加载列表数据
  4. loadAfter:在指定LoadParams中Key之后加载列表数据

实现方法中加载数据的逻辑:

override fun getKey(item: M) = item.id

   /**
     * 初始化时的加载
     */
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
        api.getArticleList(0)  //初始化加载第一页
                .compose(RxHelper.rxSchedulerHelper())
                .subscribe({    
                    callback.onResult(it?.data!!.datas!!)

                }, {
                    refreshFailed(it.message, params, callback)
                })
    }



    /**
     * 加载更多
     */
    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
        api.getArticleList(page)  // 下拉加载更多数据
                .compose(RxHelper.rxSchedulerHelper())
                .subscribe({
                    callback.onResult(it.data!!.datas!!)
                }, {
                    networkFailed(it.message, params, callback)
                })
    }
  • 创建具体DataSource.Factory实现类,用于创建自定义的DataSource
class ItemDataSourceFactory() :
        DataSource.Factory() {
    val sourceLiveData = MutableLiveData()

    override fun create(): DataSource {
        val source = ItemDataSource()
        sourceLiveData.postValue(source)
        return source
    }
}

实现了DataSource.Factory,重写onCreate()方法创建DataSource实例,这里使用了LiveData包装了DataSource的实例,这样做的好处就是可以对创建的DataSource的时效性进行控制,例如我们在刷新的时候只需调用

ItemDataSourceFactory.sourceLivaData.value?.invalidate()

这里会通知DataSource的数据失效,就会重新初始化加载数据

  • 使用自定义的DataSource和Factory

使用的方式和前面的一致,唯一的区别就是Factory的来源不同,前面Factory是从数据库Room中查询生成,这里直接传入实现的Factory的实例即可:

val concertList = LivePagedListBuilder(
ItemDataSourceFactory, 20).build()

到此Paging组件执行时会根据传递的Factory创建DataSource,然后调用DataSource中重写的方法初始化和加载数据到PagedList,然后使用数据刷新界面

  • 总结

从上面的使用可以看出Paging的组件的成员的职责

  1. DataSource:数据的来源,需要设置初始化和加载更多的逻辑以及每次加载变化的Key
  2. DataSource。Factory:工厂类提供DataSource的实例
  3. PagedList.Config:PagedList的配置类,设置初始化数量和界面预取数量等
  4. PagedList:数据集散中心;根据需要向DataSource索取加载数据,并将得到的数据传递到PagedListAdapter
  5. PagedListAdapter:数据适配器,这里处了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向PagedList加载数据(后面源码分析)
  6. DiffUtil.ItemCallback:判断数据是否发生改变以相应界面的更新

5、源码分析

无论是使用Room还是自定义Datasource,Paging组件的开始执行都是从创建LiveData开始的,所以我们源码的分析也从LiveData的创建开始

5.1、LiveData>

  • 创建LiveData>

Android Jetpack架构组件之 Paging(使用、源码篇)_第2张图片

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

  • create()
@AnyThread
    @NonNull
    private static  LiveData> create(
            @Nullable final Key initialLoadKey,
            @NonNull final PagedList.Config config,
            @Nullable final PagedList.BoundaryCallback boundaryCallback,
            @NonNull final DataSource.Factory dataSourceFactory,
            @NonNull final Executor notifyExecutor,
            @NonNull final Executor fetchExecutor) {
          // 创建ComputableLiveData类 
        return new ComputableLiveData>(fetchExecutor) { 

            @Nullable
            private PagedList mList; 
            @Nullable
            private DataSource mDataSource;

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

            @Override
            protected PagedList compute() { // 重写compute方法
                @Nullable Key initializeKey = initialLoadKey;
                if (mList != null) {
                    //noinspection unchecked
                    initializeKey = (Key) mList.getLastKey();
                }

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

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

  1. 创建DataSource的刷新回调
  2. 调用传入的Factory的create()创建DataSource实例
  3. 创建并返回PagedList实例
  • PagedList.build() & PagedList.create()

先来看一下PagedList的创建过程,在PagedList.build()中调用了PagedList.create(),所以真正的创建是在create()中发生的,

 private static  PagedList create(...) {
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
            ......
            return new ContiguousPagedList<>(contigDataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    key,
                    lastLoad);
        } else {
            return new TiledPagedList<>((PositionalDataSource) dataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    (key != null) ? (Integer) key : 0);
        }
    }

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

  1. PositionalDataSource
@Override
    boolean isContiguous() {
        return false;
    }
  1. ItemKeyedDataSource和PageKeyedDataSource都继承与ContiguousDataSource,只查看ContiguousDataSource中
@Override
boolean isContiguous() {
    return true;
}
  • ComputableLiveData
public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }

带着对ComputableLiveData作用和何时执行compute这两个疑问,查看ComputableLiveData源码,发现在ComputableLiveData的构造函数中创建LiveData实例,并在onActive()中执行了Runnable接口(关于onActive()请查看Android Jetpack架构组件之 LiveData),下面查看Runnable接口中执行了哪些逻辑:

 @VisibleForTesting
    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            boolean computed;
            do {
                computed = false;
              others.
                if (mComputing.compareAndSet(false, true)) {
                   
                    try {
                        T value = null;
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            value = compute(); // 调用了compuet创建了PagedList
                        }
                        if (computed) {
                            mLiveData.postValue(value); // 设置LiveData的值
                        }
                    } finally {
                     
                        mComputing.set(false);
                    }
                }
                
            } while (computed && mInvalid.get());
        }
    };

在Runnable中调用了ComputableLiveData的compute()方法创建了PagedList,所以此处的Value就是PagedList,然后为mLiveData初始化赋值PagedList,细心的同学会留意到,在上面的create()方法最后一句调用了getLiveData()获取到的就是ComputableLiveData构造函数中创建的LIveData

  @SuppressWarnings("WeakerAccess")
    @NonNull
    public LiveData getLiveData() {
        return mLiveData;
    }

到此LiveData的创建就完成了。

5.2、数据初始化加载

  • ContiguousPagedList

从上面的执行过程中,我们知道当我们自定义实现ItemKeySource时,创建的PagedList实际为ContiguousPagedList,查看ContiguousPagedList构造函数源码:

 super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor,
                boundaryCallback, config);
        mDataSource = dataSource;
        mLastLoad = lastLoad;

        if (mDataSource.isInvalid()) {
            detach();
        } else {
            mDataSource.dispatchLoadInitial(key,
                    mConfig.initialLoadSizeHint,
                    mConfig.pageSize,
                    mConfig.enablePlaceholders,
                    mMainThreadExecutor,
                    mReceiver);
        }

在构造函数中执行一下逻辑:

  1. 创建PagedStore实例,主要根据滑动的位置显示是否要继续加载数据
  2. 调用DataSource.dispatchLoadInitial方法,此时使用的时ItermKeyDataSource的dispatchLoadInitial方法
  @Override
    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver receiver) {
        LoadInitialCallbackImpl callback =
                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
    }

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

5.3、数据的显示

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

  • LoadCallbackHelper.dispatchResultToReceiver()
void dispatchResultToReceiver(final @NonNull PageResult result) {
            Executor executor;
        
            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中:

  private PageResult.Receiver mReceiver = new PageResult.Receiver() {
        // Creation thread for initial synchronous load, otherwise main thread
        // Safe to access main thread only state - no other thread has reference during construction
        @AnyThread
        @Override
        public void onPageResult(@PageResult.ResultType int resultType,
                @NonNull PageResult pageResult) {
            

            List page = pageResult.page;
            if (resultType == PageResult.INIT) {
                mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
                        pageResult.positionOffset, ContiguousPagedList.this);
                if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
                    // Because the ContiguousPagedList wasn't initialized with a last load position,
                    // initialize it to the middle of the initial load
                    mLastLoad =
                            pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
                }
            } else if (resultType == PageResult.APPEND) {
                mStorage.appendPage(page, ContiguousPagedList.this);
            } else if (resultType == PageResult.PREPEND) {
                mStorage.prependPage(page, ContiguousPagedList.this);
            } else {
                throw new IllegalArgumentException("unexpected resultType " + resultType);
            }

           
            }
        }
    };

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

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

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

void init(int leadingNulls, @NonNull List page, int trailingNulls, int positionOffset,
            @NonNull Callback callback) {
        init(leadingNulls, page, trailingNulls, positionOffset);
        callback.onInitialized(size());
    }

在init()方法中首先调用另一个init()方法记录加载的位置,并保存加载的数据,主要用于控制下拉加载,这部分我们稍后分析,然后调用callback.onInitialized(),在onInitialzed()方法中调用了notifyInserted(),在notifyInserted()中遍历mCallbacks回调callback的onInserted()

public void onInitialized(int count) {
        notifyInserted(0, count);
    }


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

到此我们可以得出结论

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

那CallBack是从哪来的呢?应该是哪里需要哪里才会注册回调,想想数据位置的变化在哪个地方能用得着,哪个地方优惠根据position和count处理呢?答案就时Adapter

  • PagedListAdapter

在前面的实例中,使用submitList()设置数据,而submiList()直接调用了mDiffer.submitList(pagedList)

 public void submitList(final PagedList pagedList) {
        if (mPagedList == null && mSnapshot == null) {
            // fast simple first insert
            mPagedList = pagedList;
            pagedList.addWeakCallback(null, mPagedListCallback);
            return;
        } 
    }

此处调用了addWeakCallback()添加Callback实例mPagedListCallback,

private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
        @Override
        public void onInserted(int position, int count) {
            mUpdateCallback.onInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            mUpdateCallback.onRemoved(position, count);
        }

        @Override
        public void onChanged(int position, int count) {
            // NOTE: pass a null payload to convey null -> item
            mUpdateCallback.onChanged(position, count, null);
        }
    };

mPagedListCallback的onInserted()直接回调mUPdateCallback.onInserted(),在mUPdateCallback.onInserted()中直接调用Adapter的notifyItemRangeInserted(position, count)实现数据更新

5.4、数据下拉加载

Paging的好处之一就是自动帮我们实现了下拉加载的操作,其时他的实现是依靠adapter的滑动位置,根本的逻辑和平时自己写的滑动到底加载数据大致一致,都是根据可见position和数据量的比较触发加载,在PagingAdapter中的getItem()中直接调用mDiffer.getItem(position),

 public T getItem(int index) {
        mPagedList.loadAround(index); // 调用加载数据
        return mPagedList.get(index);
    }

在getItem()中处了获取到数据以外,还调用了mPagedList.loadAround(index)去加载数据,loadAround()方法中有调用了loadAroundInternal()

 @MainThread
    @Override
    protected void loadAroundInternal(int index) {
        int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
        int appendItems = index + mConfig.prefetchDistance
                - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());

        mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
        if (mPrependItemsRequested > 0) {
            schedulePrepend();
        }

        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
        if (mAppendItemsRequested > 0) {
            scheduleAppend();
        }
    }

根据现在显示的index和设置的Config计算需要请求的数量,调用scheduleAppend()加载更多数据,

@MainThread
    private void scheduleAppend() {
   
        mBackgroundThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (isDetached()) {
                    return;
                }
                if (mDataSource.isInvalid()) {
                    detach();
                } else {
               //调用DataSource的加载更多方法
                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
                            mMainThreadExecutor, mReceiver);
                }
            }
        });
    }

是不是有发现了熟悉的身影,还记得初始化时调用的mDataSource.dispatchLoadInitial()吗?这里调用mDataSource.dispatchLoadAfter()方法,调用我们实现的loadAfter()加载更多数据,之后数据的显示都和初始化一致将PageResult.INIT换成PageResult.APPEND然后想Adapter中追加数据;

到此整个Paging的执行逻辑和原理都分析完了,从整体的流程看架构的设计还是有他独特的魅力的,架构的内涵读者自己体味,下面还有一点,我们知道DataSource的刷新是从调用Invalidate()开始的,有没有相过是如何实现的

  • Invalidate()
@AnyThread
    public void invalidate() {
        if (mInvalid.compareAndSet(false, true)) {
            for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
                callback.onInvalidated();
            }
        }
    }

调用InvalidatedCallback 的onInvalidate(),这里的InvalidatedCallback其实在我们代码分析的第一步就添加了,还记得吗?在创建PageList时,调用了Factory.create(),之后就给DataSource添加了CallBack()

 // 创建CallBack
private final DataSource.InvalidatedCallback mCallback =
                    new DataSource.InvalidatedCallback() {
                        @Override
                        public void onInvalidated() {
                            invalidate();
                        }
                    };
// 添加CallBack
  mDataSource.addInvalidatedCallback(mCallback);
  • invalidate()
public void invalidate() {
        ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
    }

 @VisibleForTesting
    final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            boolean isActive = mLiveData.hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    mExecutor.execute(mRefreshRunnable);
                }
            }
        }
    };

在invalidate()中执行了mInvalidationRunnable 中的run(),run()方法中有重新执行了mRefreshRunnable,还记的mRefreshRunnable执行了什么吗?对就是冲新创建了DataSource和PagedLIst,然后冲新加载数据,然后上面所有过程再来一次!!

本篇时整个组件的最后一篇其实也是最长的一篇(因为我最后写的),,因为Paging组件也是Jetpack组件中比较复杂的一个,使用频率也非常高,后期会针对自定义DataSource进行封装,好了这篇真的花了好长时间,感觉整个下午都在写这个,希望对自己对大家都有所帮助!

 

 

你可能感兴趣的:(Android,Jetpack)