Architecture Component—Paging 的使用及源码阅读

本文介绍了Android的Paging Library,用于高效加载和展示数据。详细阐述了DataSource、PagedList和PagedListAdapter的用法,并通过源码阅读解释了数据加载、UI更新的流程。此外,还展示了如何在实际项目中结合Dagger Android使用Paging Library。
摘要由CSDN通过智能技术生成

Paging Libray

简单来说,Paging Libary用于数据的加载,然后显示到UI上。它是很有趣并且很方便于我们的一个库,比如在后台线程加载好数据,然后在主线程中将数据展示出来。实现了数据的加载处理与UI分离开,并且可以在用户不知不觉中加载更多的数据,使之能够无限的加载更多,不用待到以后滑到底再去加载更多。当然,它的功能不止上述。

Paging Libray的相关类

DataSource

DataSource是提供数据源的一个类;针对不同的场景,提供了三个子类:

  • PageKeyedDataSource:
  • ItemKeyedDataSource:
  • PositionalDataSource:
    它们都实现了loadInitial()方法,PageKeyedDataSource和ItemKeyedDataSource都须实现 loadBefore()和 loadAfter()方法,PositionalDataSource须实现loadRange(),这几个方法中分别都会传进来封装好的参数。
PagedList

PagedList用于从数据源获取数据,可以通过Config配置它加载的数量以及预加载的数量等。并且可以将获取到的数据发送出去。

PagedListAdapter

PagedListAdapter继承与RecyclerView.Adapter,用于展示PagedList的数据,其实根据源码可知它只是一个空架,它的功能其实是别人帮它实现的。

使用:

以本地数据库+网络数据,以下代码是结合Dagger Android构建对象的。

添加Paging的配置:

 def paging_version='1.0.0'
 implementation "android.arch.paging:runtime:$paging_version"

首先创建一个实现PageKeyedDataSource的类做数据源

public class HomeDataSource extends PageKeyedDataSource<Integer, ProjectInfo> {

    private static final String TAG = "HomeDataSource";

    private HomeService mHomeService;
    private ProjectInfoDao mProjectInfoDao;

    private final static MutableLiveData<LoadStatus> mNetworkState = new MutableLiveData<>();
    private static List<ProjectInfo> mData = null;


    public HomeDataSource(HomeService homeService, ProjectInfoDao projectInfoDao) {
        mHomeService = homeService;
        mProjectInfoDao = projectInfoDao;
    }

    public static MutableLiveData<LoadStatus> getmNetworkState() {
        return mNetworkState;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params,
                            @NonNull final LoadInitialCallback<Integer, ProjectInfo> callback) {
        /*将从网络中获取到的数据直接添加进数据库*/

        //加载网络数据
        mNetworkState.postValue(LoadStatus.LOADING);

        List<ProjectInfo> projectInfoList = loadFromServer();

        if (projectInfoList != null) {
            mData = projectInfoList;
            callback.onResult(mData, null, 2);
            updateDB(mData);
            return;
        } else if (mData == null || mData.size() == 0) {

            mData = loadFromDB();
            Log.d(TAG, "loadInitial: "+mData);
            if (mData.size() == 0) {
                mNetworkState.postValue(LoadStatus.EMPTY);
            }
        }
        callback.onResult(mData, null, 2);
    }

    private List<ProjectInfo> loadFromDB() {
        List<ProjectInfo> list = mProjectInfoDao.loadProjectInfosFromDB();
        return list;
    }

    private void updateDB(List<ProjectInfo> mData) {
        mProjectInfoDao.delete(mData);
        mProjectInfoDao.save(mData);
    }

    private List<ProjectInfo> loadFromServer() {

        Call<List<ProjectInfo>> call = mHomeService.loadProjectInfos();
        List<ProjectInfo> projectInfoList = null;

        try {
            Response<List<ProjectInfo>> response = call.execute();

            if (response.isSuccessful()) {
                projectInfoList = response.body();
                if (projectInfoList.size() == 0) {
                    mNetworkState.postValue(LoadStatus.EMPTY);
                } else {
                    mNetworkState.postValue(LoadStatus.SUCCESS);
                }
            } else {
                mNetworkState.postValue(LoadStatus.FAILED);
            }
        } catch (IOException e) {
            e.printStackTrace();
            mNetworkState.postValue(LoadStatus.FAILED);
        } finally {
            if (call != null)
                call.cancel();
        }
        return projectInfoList;
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params,
                          @NonNull LoadCallback<Integer, ProjectInfo> callback) {
        //toDo:load more
        Log.d(TAG, "loadAfter: ");
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params,
                           @NonNull LoadCallback<Integer, ProjectInfo> callback) {
    }

}

创建一个工厂类用于构建数据源

@Singleton
public class HomeDataSourceFactory extends DataSource.Factory {

    private static final String TAG = "HomeDataSourceFactory";
    private HomeService mHomeService;
    private ProjectInfoDao mDao;

    @Inject
    public HomeDataSourceFactory(HomeService homeService, ProjectInfoDao dao) {
        this.mHomeService = homeService;
        this.mDao = dao;

    }

    @Override
    public DataSource<Integer, ProjectInfo> create() {

        DataSource homeDataSource = new HomeDataSource(mHomeService, mDao);

        return homeDataSource;
    }

}

Repository类来负责数据的加载等操作:

public class HomeRepository implements IRepository<PagedList<ProjectInfo>> {

    private static final String TAG = "HomeRepository";


    private DataSource.Factory mHomeDataSourceFactory;

    private LiveData<PagedList<ProjectInfo>> mLiveData;


    @Inject
    public HomeRepository(DataSource.Factory factory) {
        this.mHomeDataSourceFactory = factory;
    }

    @Override
    public LiveData<PagedList<ProjectInfo>> getLiveData() {
//这里构建一个Config来配置数据源的加载,然后构建一个LiveData用来传送数据。
        if (mLiveData == null) {
            PagedList.Config config = new PagedList.Config.Builder()
                    .setInitialLoadSizeHint(Config.PAGE_SIZE)//首次加载10
                    .setPageSize(Config.PAGE_SIZE)
                    .setEnablePlaceholders(true)
                    .setPrefetchDistance(Config.DISTANCE_SIZE)//距离最后多少个就去加载更多的数据
                    .build();

            mLiveData = new LivePagedListBuilder(mHomeDataSourceFactory, config).build();
        }

        return mLiveData;
    }

    @Override
    public void refreshData() {
        mLiveData.getValue()
                .getDataSource()
                .invalidate();
    }

    @Override
    public LiveData<LoadStatus> getState() {
        return HomeDataSource.getmNetworkState();
    }

}

创建一个Adapter继承于PagedListAdapter
这里使用了DataBinding

public class ProjectListAdapter extends
        PagedListAdapter<ProjectInfo, ProjectListAdapter.ProjectListViewHolder> {

    private Context mContext;

    public ProjectListAdapter(Context context) {
        super(DIFF_CALLBACK);
        this.mContext = context;
    }

    @NonNull
    @Override
    public ProjectListViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {

        View view = View.inflate(mContext, R.layout.home_fragment_item, null);
        return new ProjectListViewHolder(view);
ProjectListViewHolder.create(LayoutInflater.from(viewGroup.getContext()), viewGroup);
    }

    @Override
    public void onBindViewHolder(@NonNull ProjectListViewHolder projectListViewHolder, int i) {

        ProjectInfo projectInfo = getItem(i);
        projectListViewHolder.bindTo(projectInfo);
    }

    public static final DiffUtil.ItemCallback<ProjectInfo> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<ProjectInfo>() {

                @Override
                public boolean areItemsTheSame(@NonNull ProjectInfo oldpi, @NonNull ProjectInfo newpi) {
                    return oldpi.getId() == newpi.getId();
                }

                @Override
                public boolean areContentsTheSame(@NonNull ProjectInfo oldpi, @NonNull ProjectInfo newpi) {
                    return oldpi.equals(newpi);
                }
            };


    static class ProjectListViewHolder extends RecyclerView.ViewHolder {

        protected ImageView ivProjectIcon;
        protected TextView tvProjectName;
        HomeFragmentItemBinding mHomeFragmentItemBinding;
        public ProjectListViewHolder(View itemView) {
            super(itemView);
            mHomeFragmentItemBinding = DataBindingUtil.bind(itemView);
            ivProjectIcon = itemView.findViewById(R.id.iv_project_icon);
            tvProjectName = itemView.findViewById(R.id.tv_project_name);
        }

        public void bindTo(ProjectInfo projectInfo) {
            mHomeFragmentItemBinding.setProjectInfo(projectInfo);
            mHomeFragmentItemBinding.executePendingBindings();//刷新

        }
    }
}

现在数据源有了,创建数据源的Factory有了,接下来就可以设置Adapter了

public class HomeFragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener {

    private static final String TAG = "HomeFragment";
    HomeFragmentBinding mHomeFragmentBinding;
    private ProjectListAdapter mProjectListAdapter;

    private HomeViewModel mHomeViewModel;
    @Inject
    ViewModelProvider.Factory mFactory;

    private View mEmptyView;

    @Override
    protected void initData() {
     mHomeViewModel = ViewModelProviders.of(this, mFactory).get(HomeViewModel.class);
        mHomeViewModel.getProjectInfos().observe(this,
                new Observer<PagedList<ProjectInfo>>() {
                //这里就是从数据源获取数据后回调的方法,将其设置大盘Adapter
                    @Override
                    public void onChanged(@Nullable PagedList<ProjectInfo> projectInfos) {
                        mProjectListAdapter.submitList(projectInfos);
                    }
                });

       ......
    }

    @Override
    protected View initView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                            @Nullable Bundle savedInstanceState) {
       ......
       /*省略部分是结合DataBinding的初始化*/
        return mHomeFragmentBinding.getRoot();
    }

    @Override
    public void onRefresh() {
        mHomeViewModel.refresh();
    }
}

最后由ViewModel来连接它们,代码如下

public class HomeViewModel extends ViewModel {

    private final String TAG = "HomeViewModel";

    private IRepository<PagedList<ProjectInfo>> mRepository;

    @Inject
    public HomeViewModel(IRepository<PagedList<ProjectInfo>> mRepository) {
        this.mRepository = mRepository;
    }

    /**
     * 获取项目信息
     *
     * @return 项目集合的PagedList
     */
    public LiveData<PagedList<ProjectInfo>> getProjectInfos() {

        if (mRepository == null || mRepository.getLiveData() == null) {
            return new MutableLiveData<>();
        }
        return mRepository.getLiveData();
    }

    /**
     * 下拉刷新
     */
    public void refresh() {
        mRepository.refreshData();
    }
    /**
     * 刷新的状态
     *
     * @return
     */
    public LiveData<LoadStatus> getLoadState() {
        if (mRepository == null || mRepository.getState() == null) {
            return new MutableLiveData<>();
        }
        return mRepository.getState();
    }
    @Override
    protected void onCleared() {
        super.onCleared();
    }
}

以上是使用了Dagger2框架,所以一些创建的细节没有贴出来。

源码阅读

刚开始要阅读他的源码时,发现不知从哪里进入合适些,通过观察后,便从LiveData的构建开始进入。

LiveData<PagedList<ProjectInfo>> liveData =
                    new LivePagedListBuilder(homeDataSourceFactory, config).build();

从build方法会进入LivePagedListBuilder类,然后会内部调用create方法,并且会传入一些列重要的参数,其中含有与数据加载相关的Key、配置类Config、数据源工厂DataSource.Factory和线程池等。create中会创建ComputableLiveData并返回其对应的LiveData。
那么这个ComputableLiveData是干什么的呢?我们从它的构造函数入手:

 public ComputableLiveData(@NonNull Executor executor) {
 //初始化线程池
        mExecutor = executor;
 //初始化LiveData
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
            //执行任务
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }

从以上代码可得知它初始化了线程池和初始化LiveData,并且在内部执行一个任务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;
        //mInvalid在这里做了比较,当它为true时,返回结果为true,还有更新为false,这两部操作时原子操作。
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            //compute方法里边执行了许多的逻辑
                            value = compute();
                        }
                        if (computed) {
                        //将获取到的数据发送出去,这里的mLiveData就是最开始咋们手动构建的LiveData对象,所以在这里获取到的数据在我们创建的LiveData注册的Observer中会收到。
                            mLiveData.postValue(value);
                        }
                    } finally {
                        mComputing.set(false);
                    }
                }
            } while (computed && mInvalid.get());
        }
    };

现在我们回过头看看刚刚在create方法中的ComputableLiveData对象中对compute方法的实现:

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

                do {
                    if (mDataSource != null) {//移除回调
                       mDataSource.removeInvalidatedCallback(mCallback);
                    }
//通过我们传进来的那个数据源工厂类来获取一个数据源。
                    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;
            }

到这里可以发现用了好几次的Builder模式,使得代码容易看懂;compute函数里边获取数据源,数据源移除老的回调并且重新添加(当数据源连续调用两次同个回调对象的onResult方法时会抛异常);最后同个一些列的参数构建了PagedList对象。
接下来进入PagedList的构建,看看它是如何构建的。

//PagedList.Builder

  @NonNull
        public PagedList<Value> build() {
            if (mNotifyExecutor == null) {
                throw new IllegalArgumentException("MainThreadExecutor required");
            }
            if (mFetchExecutor == null) {
                throw new IllegalArgumentException("BackgroundThreadExecutor required");
            }

            return PagedList.create(
                    mDataSource,
                    mNotifyExecutor,
                    mFetchExecutor,
                    mBoundaryCallback,
                    mConfig,
                    mInitialKey);
        }
    }

首先进行检验,然后调用了PagedList中的create方法,这跟前面的方式基本相同,跟进create方法:

 @NonNull
    private 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) {//同样是把我们构建LiveData对象时传进来的参数
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
         //获取一个ContiguousPagedList对象
            ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
            return new ContiguousPagedList<>(contigDataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    key,
                    lastLoad);
        } else {
        //获取TiledPagedList对象
            return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    (key != null) ? (Integer) key : 0);
        }
    }

根据上面的代码可知根据数据源工厂创建的数据源的配置和我们最开始传进的配置(Config)来决定要获取那个PagedList,由于我们是禁用了使用null占位,所以这里会执行构建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);
        }
    }

由于是首次使用的数据源,所以它的isInvalid方法会返回false,所以它就会调用数据源的dispatchLoadInitial方法;进入到我们继承的数据源的类里面找到此方法

//PageKeyedDataSource类

   @Override
    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        LoadInitialCallbackImpl<Key, Value> callback =
                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
       //调用loadInitial方法,所以这就为什么在最开始介入数据源的时候会调用此方法去初始化数据
        loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);

        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
    }

到此可得知数据源的初始化的方法的调用时机。但是我们还需要调用传进去的callback的onResult方法,才能实现数据的传送。
这里我们看下三个参数的LoadInitialCallbackImpl的onResult():

 @Override
        public void onResult(List<Value> data, Key previousPageKey,Key nextPageKey) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
            //初始化前页和后页的Key
                mDataSource.initKeys(previousPageKey, nextPageKey);
                //分发
                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
            }
        }

分发的过主要是执行以下的这句代码:

mReceiver.onPageResult(mResultType, result);

这个mReceiver是ContiguousPagedList中的成员,它直接创建并实现了onPageResult方法,此方法里边进行一些页面的计算和拼装,主要都交给了PagedStorage处理(分别进行了初始化数据集合、页面条目数量等)。然后调用append方法进行添加新的数据和计算页面的大小,然后调用了Callback的onPageAppended方法,其实这里的Callback的其中一个实现就是ContiguousPagedList本身,接下来关注下该方法的实现:

 @MainThread
    @Override
    public void onPageAppended(int endPosition, int changedCount, int addedCount) {
        // consider whether to post more work, now that a page is fully appended

        mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
        mAppendWorkerRunning = false;
        if (mAppendItemsRequested > 0) {
            // not done appending, keep going
            scheduleAppend();
        }

        // finally dispatch callbacks, after append may have already been scheduled
        notifyChanged(endPosition, changedCount);
        notifyInserted(endPosition + changedCount, addedCount);
    }

很明显最后通知数据的改变:

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);
                }
            }
        }
    }
//通知数据改变量
    void notifyChanged(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.onChanged(position, count);
                }
            }
        }
    }

两个方法的具体操作逻辑都交给了Callback,它的方法实现在AsyncPagedListDiffer类中,然后它又将具体逻辑交给了mUpdateCallback,UpdateCallback的具体实现:

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

    public void onRemoved(int position, int count) {
        this.mAdapter.notifyItemRangeRemoved(position, count);
    }

    public void onMoved(int fromPosition, int toPosition) {
        this.mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    public void onChanged(int position, int count, Object payload) {
        this.mAdapter.notifyItemRangeChanged(position, count, payload);
    }

这里的mAdapter就是我们代码创建的Adapter,再跟踪下去便是RecyclerView中的Adapter,它的逻辑就是通知添加条目或者删除条目。到这里整个流程基本走完。最后回头看看获取到的pagedList是在哪里发送到绑定的地方的:

//ComputableLiveData.java
while (mInvalid.compareAndSet(true, false)) {
   computed = true;
   value = compute();
 }
if (computed) {//从这儿发送出去
   mLiveData.postValue(value);
}

在这里我们可以看看LiveData是怎么样把数据发送到绑定的Observer中的:

    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            //初始化数据
            mPendingData = value;
        }
        ......
//执行任务,再主线程中执行        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

再往下看会发现该人物中执行了LiveData的setValue方法:

  @MainThread
    protected void setValue(T value) {
    //验证是否再主线程中执行
        assertMainThread("setValue");
        mVersion++;
        mData = value;//更新要发送的数据
        dispatchingValue(null);
    }

dispatchingValue方法中比较主要的代码如下:

//便利所有绑定的Observer
 for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
 //调用此方法进入将数据回调给Observer
      considerNotify(iterator.next().getValue());
      if (mDispatchInvalidated) {
          break;
      }
}

considerNotify方法:

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
     //这里便是最开始再Fragment中注册的Observer
        observer.mObserver.onChanged((T) mData);
    }

这样便将数据发送给绑定的Oberver,并且在数据更新的时候便可以更新UI。

总结
  • 源码中用到了好多次Builder模式,便于我们的理解和阅读,而且很简洁。
  • 使用多个任务池,灵活地将任务或者数据在主线程和工作线程上切换。
  • 该库方便于我们的开发,为我们减少了很多的工作,例如数据源的加载、数据的传送、线程的切换和数据更新到界面等。

从起稿到现在有一周左右,如果您看到这里,也真的很不容易,那就顺便点个赞吧。❤❤

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值