What are the benefits of using Paging

  • Advantage 1: the paging library can be more easily in the application of RecyclerView step by step and elegant loading data;
  • Benefits 2: Data requests consume less network bandwidth and fewer system resources;
  • Benefit 3: The application continues to respond quickly to user input even during data updates and refreshes;
  • Benefit four: however much waste, display how much to use how much;

The use of the Paging

  1. Role 1: DataSource (is a DataSource, including various forms, e.g. Room source, PositionalDataSource source, PageKeyedDataSource source, ItemKeyedDataSource source)
  2. Role 2: PagedList (UIModel data layer, get data source via Factory)
  3. Role 3: PagedAdapter (note that it is not a RecycleView adapter, but a PagedListAdapter supporting Paging)
  4. RecycleView role 4: RecycleView (RecycleView, only setAdapter binding adapter is PagedAdapter)

public class Student {

    private String id;
    private String name;
    private String sex;

    public String getId(a) {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex(a) {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    // Compare the function
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null|| getClass() ! = o.getClass())return false;
        Student student = (Student) o;
        return id.equals(student.id) &&
                name.equals(student.name) &&
                sex.equals(student.sex);
    }

    // Compare the function
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public int hashCode(a) {
        returnObjects.hash(id, name, sex); }}Copy the code

Role 1 Data source

Data source is the source of data, which can be multiple sources, such as “network data”, “local data”, “database data”.

public class StudentDataSource extends PositionalDataSource<Student> {

    /** * this function is used to load the first page of data. * Figuratively speaking, when we first open the page, we need to call back to this method to get the data. *@param params
     * @param callback
     */
    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
        // @1 Data source @2 location @3 total size
        callback.onResult(getStudents(0, Flag.SIZE), 0.1000);
    }

    /** * This method is called when data needs to be loaded while sliding after initialization. *@param params
     * @param callback
     */
    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Student> callback) {
        @2 size(size = 1)
        callback.onResult(getStudents(params.startPosition, params.loadSize));
    }

    /** * It can be understood that this is the data source, the source of the data (database, file, web server response, etc.) *@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 number is:" + i);
            student.setName("My name :" + i);
            student.setSex("My gender :" + i);
            list.add(student);
        }
        returnlist; }}Copy the code

Role 2 Data factory

Create a factory to manage data sources. Why have a factory, in addition to creating data sources, for subsequent extensions

/** * data factory */
public class StudentDataSourceFactory extends DataSource.Factory<Integer.Student> {

    @NonNull
    @Override
    public DataSource<Integer, Student> create(a) {
        StudentDataSource studentDataSource = new StudentDataSource();
        returnstudentDataSource; }}Copy the code

Role 3 Data model

A data model is actually a ViewModel, which is used to manage data

PagedList: Data retrieved from the data source is ultimately hosted by PagedList. The way to think about PagedList is, it’s just a collection of data on a page. Each page requested is a new PagedList object.

public class StudentViewModel extends ViewModel {

    @1 listLiveData = @1 listLiveData
    private final LiveData<PagedList<Student>> listLiveData;

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

        // Initialize the ViewModel
        this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, Flag.SIZE).build();
    }

    // TODO exposes data
    public LiveData<PagedList<Student>> getListLiveData() {
        returnlistLiveData; }}Copy the code

Role 4 Adapter

This Adapter is a RecyclerView Adapter. However, we use paging to achieve the effect of RecyclerView paging loading, can not inherit the Adapter of RecyclerView directly, but need to inherit the PagedListAdapter.

LiveData observed data, induced data to the adapter, the adapter bind RecyclerView, then the RecyclerView list data changed

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

    // TODO compares the behavior
    private static DiffUtil.ItemCallback<Student> DIFF_STUDNET = new
            DiffUtil.ItemCallback<Student>() {

                // It is a unique content, ID
                @Override
                public boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
                    return oldItem.getId().equals(newItem.getId());
                }

                // Compare the object itself
                @Override
                public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
                    returnoldItem.equals(newItem); }};protected RecyclerPagingAdapter(a) {
        super(DIFF_STUDNET);
    }

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

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

        // When the item view is displayed and the paging library is still loading data, I show that the Id is loading
        if (null == student) {
            holder.tvId.setText("Id loading");
            holder.tvName.setText("Name in load");
            holder.tvSex.setText("Sex loading");
        } else{ holder.tvId.setText(student.getId()); holder.tvName.setText(student.getName()); holder.tvSex.setText(student.getSex()); }}// Item optimization ViewHolder
    public 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); // ID
            tvName = itemView.findViewById(R.id.tv_name); / / name
            tvSex = itemView.findViewById(R.id.tv_sex); / / gender}}}Copy the code

Display the results

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    RecyclerPagingAdapter recyclerPagingAdapter;
    StudentViewModel viewModel;

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

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

        // The latest version initializes the viewModel
        viewModel = new ViewModelProvider(this.new ViewModelProvider.NewInstanceFactory())
                .get(StudentViewModel.class);

        // LiveData observer updates
        viewModel.getListLiveData().observe(this.new Observer<PagedList<Student>>() {
            @Override
            public void onChanged(PagedList<Student> students) {
                // Update adapter data hererecyclerPagingAdapter.submitList(students); }}); recyclerView.setAdapter(recyclerPagingAdapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); }}Copy the code

The roles of Paging

  • A source of data;
  • Datasource. Factory: The Factory class provides an instance of a DataSource that you can use to customize a DataSource.
  • PagedList: data center, request loading data from DataSource as needed, and pass the data to PagedListAdapter;
  • PagedListAdapter: data adapter, here in addition to play a general interface load adapter, more important is according to the sliding display of coordinates, play to determine when required to load data to the PagedList;
  • Diffutil. ItemCallback: Checks whether the data has changed to determine whether the interface is updated;

Data source detail

DataSource is an abstract class, but we cannot directly inherit it and implement its subclasses. However, the Paging library provides three subclasses of it for us to inherit implementations for different scenarios:

PositionalDataSource: the data is loaded from a specific location. Key is the location information of type Integer, and T is the Value. Let’s say you start with 1200 entries in your database and add 20 entries.

ItemKeyedDataSource<Key, Value> The loading of target data depends on the information of a specific item, that is, the Key field contains the information of item. For example, the data of the N+1 item needs to be loaded according to the information of the NTH item, and the ID of the NTH item needs to be passed in the parameter transmission. This scenario mostly occurs in the request of the forum class to apply comment information.

PageKeyedDataSource<Key, Value> : applies to scenarios where target data requests data based on page information, that is, the Key field is page-related information. For example, the parameters of the requested data contain information like the number of next/Pervious pages.

Source code analysis of Paging

The relationship between class

Abstract class DataSource<Key, Value> :

Abstract class ItemKeyedDataSource<Key, Value> :

Abstract Class PageKeyedDataSource<Key, Value> :

Abstract Class PositionalDataSource: The data source subclass we just used

DataSource has three subclasses:

PageKeyedDataSource: If the page needs to implement the previous page, the next page, the requested Token needs to be passed to the next page using ItemKeyedDataSource: The PositionalDataSource is used when the program needs to fetch the next data based on the previous data information (ID) : the data page needs to be retrieved from any location in the data store; For example, a request might return 20 data items starting with position 1200

Of course, the analysis starts at the place where the data is taken. The execution of Paging components starts from the creation of LiveData. Our source code analysis also starts from the creation of LiveData to explore the logic behind Paging.

Initialization work

Private Final LiveData<PagedList> listLiveData; “How this variable is created:

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

Click to enter build function analysis:

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

Enter create function analysis:

Use LivePagedListBuilder to configure Factory and Config, and then call Build to create the instance. Create LiveData directly by calling create () in the build method

	@AnyThread
    @NonNull
    private 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) {
        
        // Note: Create the ComputableLiveData abstract class here
        return new ComputableLiveData<PagedList<Value>>(fetchExecutor) { 
 
            @Nullable
            private PagedList<Value> mList; 
            @Nullable
            private DataSource<Key, Value> mDataSource;
 
            private final DataSource.InvalidatedCallback mCallback =
                    new DataSource.InvalidatedCallback() {
                        @Override
                        public void onInvalidated(a) { invalidate(); }};// Notice that we are overriding the compute method here, which is the PagedList
      
        we need
      
            @Override
            protected PagedList<Value> compute(a) { 
                @Nullable Key initializeKey = initialLoadKey;
                if(mList ! =null) {
                    //noinspection unchecked
                    initializeKey = (Key) mList.getLastKey();
                }
 
                do {
                    if(mDataSource ! =null) {
                        mDataSource.removeInvalidatedCallback(mCallback);
                    }
                    // Create the DataSource from the Factory passed in Builder
                    mDataSource = dataSourceFactory.create();
                    mDataSource.addInvalidatedCallback(mCallback);
                   / / create the PagedList
                    mList = new PagedList.Builder<>(mDataSource, config)
                            .setNotifyExecutor(notifyExecutor)
                            .setFetchExecutor(fetchExecutor)
                            .setBoundaryCallback(boundaryCallback)
                            .setInitialKey(initializeKey)
                            .build();
                } while (mList.isDetached());
                return mList;
            }
        }.getLiveData();
    }
Copy the code

In create (), which returns an instance of ComputableLiveData directly, some major operations are performed in the compute overridden by the ComputableLiveData instance:

Call Factory create () to create a DataSource instance. Create and return PagedList instance; Pagedlist.build () & pagedList.create () is the following code (details);

mList = new PagedList.Builder<>(mDataSource, config)
                            .setNotifyExecutor(notifyExecutor)
                            .setFetchExecutor(fetchExecutor)
                            .setBoundaryCallback(boundaryCallback)
                            .setInitialKey(initializeKey)
                            .build();
Copy the code
public PagedList<Value> build(a) {
            // TODO: define defaults, once they can be used in module without android dependency
            if (mNotifyExecutor == null) {
                throw new IllegalArgumentException("MainThreadExecutor required");
            }
            if (mFetchExecutor == null) {
                throw new IllegalArgumentException("BackgroundThreadExecutor required");
            }

            //noinspection unchecked
            return PagedList.create(
                    mDataSource,
                    mNotifyExecutor,
                    mFetchExecutor,
                    mBoundaryCallback,
                    mConfig,
                    mInitialKey);
        }
Copy the code

The PagedList creation process calls pagedList.create () in pagedlist.build (), so the actual creation takes place in 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 newTiledPagedList<>((PositionalDataSource<T>) dataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, (key ! =null)? (Integer) key :0); }}Copy the code

See from the above code according to the conditions (dataSource. IsContiguous () | |! Config. EnablePlaceholders) of different create ContiguousPagedList and TiledPagedList respectively, actually this is to distinguish the type of the above three custom DataSource (three sources), If PositionalDataSource creates TiledPagedList, the other returns ContiguousPagedList, and we check isspagedlist () methods in the three DataSource in turn:

PositionalDataSource class:

@Overrideboolean isContiguous() {   
    return false;
}
Copy the code

ItemKeyedDataSource and PageKeyedDataSource both inherit and access to a ContiguousDataSource, and only check the class of ContiguousDataSource:

@Overrideboolean isContiguous() {    
    return true;
}
Copy the code

Back again, starting with LivePagelistBuilder.build:

ComputableLiveData (LiveData) : ComputableLiveData (LiveData) : ComputableLiveData (LiveData) : ComputableLiveData (LiveData) : ComputableLiveData (LiveData) : ComputableLiveData See what logic is executed in the Runnable interface:

public ComputableLiveData(@NonNull Executor executor) {        
    mExecutor = executor;        
    mLiveData = new LiveData<T>() {            
        @Override            
        protected void onActive(a) { mExecutor.execute(mRefreshRunnable); }}; }Copy the code
 final Runnable mRefreshRunnable = new Runnable() {       
     @WorkerThread        
     @Override        
     public void run(a) {            
         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() is executed here; function
                         // Compuet was called to create the PagedList
                         value = compute();                        
                     }                        
                     if (computed) {                            
                         // Set the LiveData valuemLiveData.postValue(value); }}finally {                        
                     // release compute lock                        
                     mComputing.set(false); }}... }while(computed && mInvalid.get()); }};Copy the code

The PagedList is created by calling the compute () method of ComputableLiveData in mRefreshRunnable, so the Value here is the PagedList, and then the PagedList is initialized for mLiveData.

If you are careful, notice that the last line of the create () method above calls getLiveData() to obtain the LIveData created in the ComputableLiveData constructor:

@SuppressWarnings("WeakerAccess")    
@NonNull    
public LiveData<T> getLiveData(a) {        
    return mLiveData;    
}
Copy the code

At this point, LiveData is finally created

Data loading

We use the spagedlist as a starting point

When we customize the implementation of ItemKeySource, the PagedList created is actually a ContiguousPagedList, check the source code of the constructor of 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; }Copy the code

DispatchLoadInitial () for ItermKeyDataSource

Perform some logic in the constructor, so keep tracing the code:

First point: create PagedStorage instance, mainly according to the sliding position to show whether to continue loading data

The second point: call the DataSource. DispatchLoadInitial method, this time using the ItermKeyDataSource dispatchLoadInitial method

@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);    
}
Copy the code

ItermKeyDataSource dispatchLoadInitial () calls loadInitial() in the dispatchLoadInitial () method. Thus, the initialization data of the Paging component is loaded

Data display work

After loading the data in the loadInitial () of the custom ItemDataSource, callback.onResult(it? .data!! .datas!!) Method, where callback is the LoadInitialCallback implementation LoadInitialCallbackImpl, In the onResult () method call again 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) {            
        mCallbackHelper = new LoadCallbackHelper<>(                    
            dataSource, PageResult.INIT, null, receiver);           
        mDataSource = dataSource;           
        mCountingEnabled = countingEnabled;        
    }        
    
    @Override        
    public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,                
                         @Nullable Key nextPageKey) {            
        if(! mCallbackHelper.dispatchInvalidResultIfInvalid()) { mDataSource.initKeys(previousPageKey, nextPageKey); mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0.0.0)); }}Copy the code

Mp5: 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(a) { mReceiver.onPageResult(mResultType, result); }}); }else{ mReceiver.onPageResult(mResultType, result); }}Copy the code

In dispatchResultToReceiver () method, called PageResult) Receiver. OnPageResult () method, The mReceiver here is in the call mDataSource. DispatchLoadInitial () is introduced to the last parameter, the realization of his anonymous created in ContiguousPagedList:

final PageResult.Receiver<T> mReceiver;        // 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;        
}
Copy the code

ContiguousPagedList:

  private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {        
      // 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<V> pageResult) {                         
          List<V> 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); }}}};Copy the code

In the onPageResult () method, the three data types of the PageResult correspond to the three methods of the ItemKeyDataSource:

INIT loadBefore: Indicates the initialization state pageresult. PREPEND loadAfter: indicates the initialization state pageresult. APPEND

This parse initializes with a callback of type pageresult.init that calls the INIT () method of PagedStorage:

mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,                        
              pageResult.positionOffset, ContiguousPagedList.this);
Copy the code
void init(int leadingNulls, 
          @NonNull List<T> page, int trailingNulls, int positionOffset,            
          @NonNull Callback callback) {        
    init(leadingNulls, page, trailingNulls, positionOffset);        
    callback.onInitialized(size());    
}
Copy the code

Another init () method is first called in the init () method to record where it was loaded and save the loaded data, then callback.oninitialized () is called, and notifyInserted () is called in the onInitialzed () method, Iterate over onInserted () of the mCallbacks callback in notifyInserted ()

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(a);        
    void onEmptyAppend(a);    
}
Copy the code

Continue to trace the source:

//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); }}}}// Interface of PagedList :public abstract void onInserted(int position, int count);
Copy the code

The above source code, let us understand:

One: the loaded data is saved in PagedStorage, and the loading location information is recorded

Callback () notifies callback.oninserted () of the number and position of data changes after the load is complete

At last there was light:

So where did CallBack come from? Where does it need to register callbacks, think about where changes in data location are useful, where are preferences handled based on position and count? The answer is in the PagedListAdapter and we’re finally going to see the PagedListAdapter

PagedListAdapter

Of course, we can also simply trace the code to the 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); }}Copy the code

ListUpdateCallback:

public interface ListUpdateCallback {...void onInserted(int position, int count);
}
Copy the code

AdapterListUpdateCallback:

@Overridepublic void onInserted(int position, int count) {  	
    mAdapter.notifyItemRangeInserted(position, count);
}
Copy the code

The reverse way, look at the text yourself

Reverse source closure:

In the example we started writing using Paging, submitList() is used to set the data, and submiList () calls mDiffer. SubmitList (pagedList) directly:

public void submitList(PagedList<T> pagedList) {        
    mDiffer.submitList(pagedList);
}
Copy the code
 public void submitList(final PagedList<T> pagedList) {        
     if (mPagedList == null && mSnapshot == null) {            
         // fast simple first insert            
         mPagedList = pagedList;            
         pagedList.addWeakCallback(null, mPagedListCallback);            
         return; }}Copy the code

AddWeakCallback () is called to add the Callback instance 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); }};Copy the code

Source above, mPagedListCallback onInserted () callback mUPdateCallback. OnInserted (), MUPdateCallback here is Differ, created in the PagedListAdapter constructor in AsyncPagedListDiffer directly initialized in the constructor of the AdapterListUpdateCallback object

 public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,            
                             @NonNull DiffUtil.ItemCallback<T> diffCallback) {        
     mUpdateCallback = new AdapterListUpdateCallback(adapter);        
     mConfig = new AsyncDifferConfig.Builder<T>(diffCallback).build();
 }
Copy the code

So the program execution to the AdapterListUpdateCallback, In AdapterListUpdateCallback. OnInserted () in the direct call to the Adapter notifyItemRangeInserted (position, count) to realize data update, the Adapter is here