Detailed explanation of the use of LoaderManager (3) --- Implementing Loaders

This text will introduce the Loader<D> class, and introduce the implementation of custom Loader. This is the third article in this series.

1: The world before Loaders
2 : Understanding LoaderManager
3: Implementing Loaders
4: Example: AppListLoader is the

top priority, if you haven't read the previous two articles, I suggest you read those two articles before going deeper. Let’s start with a brief summary of what this blog covers. The World Before Loader (Part 1) describes the data loading methods before Android 3.0 and the lengthy query operations performed in the UI main thread. These UI-unfriendly APIs lead to poor app responsiveness. The general situation is to understand the motivation for the writing of LoaderManager (Part 2). This article introduced the LoaderManager class and talked about its role in asynchronously loading data. LoaderManager manages Loaders in the declaration cycle of Activity and Fragment, and maintains loaded data when configuration changes.

Loader Basic

Loades are responsible for executing queries in a separate thread, monitoring data source changes, and sending the query result set to a registered listener (usually LoaderManager) when a change is detected. The following features make Loaders a powerful tool in the Android SDK:

1. It encapsulates the actual data loading. Activity/Fragment no longer needs to know how to load data. In fact, the Activity/Fragment delegates this task to the Loader, which executes the query request in the background and returns the result to the Activity/Fragment.

2. The client does not need to know how the query is executed. Activity/Fragment does not need to worry about how the query is executed in a separate thread, Loder will automatically perform these query operations. This approach not only reduces code complexity but also eliminates the potential for thread-related bugs.

3. It is event-driven for security. Loader detects the underlying data, and when changes are detected, it automatically executes a new load to obtain the latest data. This makes it easy to use the Loader, and the client can trust that the Loader will automatically update its data by itself. All the Activity/Fragment needs to do is initialize the Loader and respond to any data that comes back. Other than that, everything else is handled by the Loader.

Loaders is a more advanced topic and may require more time to use it. In the next section, we'll start by analyzing its four defined properties.

What does Loader consist of?

There are a total of four characteristics that ultimately determine the behavior of a Loader:

1. Execute asynchronous loading tasks. In order to ensure that the loading operation is performed in a separate thread, the subclass of Loader must extend the AsyncTaskLoader<D> class instead of the Loader<D> class. AsyncTaskLoader<D> is an abstract Loader that provides an AsyncTask to do its execution. When defining subclasses, implement asynchronous tasks by implementing the abstract method loadInBackground method. This method will perform the data load operation in a worker thread.

2. Receive the result of loading completion in a registered listener (see Note 1). For each Loader, the LoaderManager registers an OnLoadCompleteListener<D>, which will make the Loader deliver the result to the client by calling the onLoadFinished(Loader<D> loader, D result) method. The Loader delivers the result to the registered listeners by calling Loader#deliverResult(D result).

3. Three different states (see Note 2). Any Loader will be in three states, started, stopped, reset:
a. A Loader in the started state will perform the load operation and deliver the result to the listener at any time. The Loader that has been started will listen for data changes and perform a new load when a change is detected. Once started, the Loader will remain started until transitioning to stopped and reset. This is the only state where onLoadFinished will always be called.
b. The Loader in the stopped state will continue to listen for data changes, but will not return the result to the client. In the stopped state, the Loader may be started or restarted.
c. When the Loader is in the reset state, no new load operations will be performed, no new result sets will be sent, and no data changes will be detected. When a Loader enters the reset state, it must dereference the corresponding data to facilitate garbage collection (similarly, the client must ensure that all references to that data are removed after the Loader is invalidated). Usually, reset Loaders are not called twice; however, in some cases they may start, so they must be able to reset in time if necessary.

4. There is an observer to receive notifications of data source changes. Loader must implement one of these Observers (such as ContentObserver, BroadcastReceiver, etc.) to detect changes in the underlying data source. When a data change is detected, the observer must call Loader#onContentChanged(). Two different operations are performed in this method: a. If the Loader is already started, a new load operation is performed; b. A flag is set to indicate that the data source has changed, so that when the Loader starts again, it knows that it should Reloaded data.

By now, you should have a basic idea of ​​how Loaders work. If not, I suggest you put it away and re-read it later (read this doc,). That said, let's start with the actual code and write it down.

Implementing Loaders

As I stated before, there are many things to be aware of when implementing custom Loaders. Subclasses must implement the loadInBackground() method, and must override onStartLoading(), onStoppLoading(), onReset(), onCanceled() and deliverResult(D results) to implement a fully functional Loader. It is very important to override these methods, LoaderManager will call different methods in different declaration cycles of Activity/Fragment. For example, when an Activity starts for the first time, it will have the LoaderManager start each Loaders it has in Activity#onStart(). If a Loader is not started, the LoaderManager will call the startLoading() method, which puts the Loader into the started state and immediately calls the Loader's onStartLoading() method. In other words, a lot of the work that LoaderManager does in the background is based on the correct implementation of Loader, so don't underestimate the importance of implementing these methods.

The following code is a boilerplate for a typical implementation of Loader. The SampleLoader query result is a list containing SampleItem objects, and returns the query result list List<SampleItem> to the client:
public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {  
  
  // We hold a reference to the Loader’s data here.  
  private List<SampleItem> mData;  
  
  public SampleLoader(Context ctx) {  
    // Loaders may be used across multiple Activitys (assuming they aren't  
    // bound to the LoaderManager), so NEVER hold a reference to the context  
    // directly. Doing so will cause you to leak an entire Activity's context.  
    // The superclass constructor will store a reference to the Application  
    // Context instead, and can be retrieved with a call to getContext().  
    super(ctx);  
  }  
  
  /****************************************************/  
  /** (1) A task that performs the asynchronous load **/  
  /****************************************************/  
  
  @Override  
  public List<SampleItem> loadInBackground() {  
    // This method is called on a background thread and should generate a  
    // new set of data to be delivered back to the client.  
    List<SampleItem> data = new ArrayList<SampleItem>();  
  
    // TODO: Perform the query here and add the results to 'data'.  
  
    return data;  
  }  
  
  /********************************************************/  
  /** (2) Deliver the results to the registered listener **/  
  /********************************************************/  
  
  @Override  
  public void deliverResult(List<SampleItem> data) {  
    if (isReset()) {  
      // The Loader has been reset; ignore the result and invalidate the data.  
      releaseResources(data);  
      return;  
    }  
  
    // Hold a reference to the old data so it doesn't get garbage collected.  
    // We must protect it until the new data has been delivered.  
    List<SampleItem> oldData = mData;  
    mData = date;  
  
    if (isStarted()) {  
      // If the Loader is in a started state, deliver the results to the  
      // client. The superclass method does this for us.  
      super.deliverResult(data);  
    }  
  
    // Invalidate the old data as we don't need it any more.  
    if (oldData != null && oldData != data) {  
      releaseResources(oldData);  
    }  
  }  
  
  /*********************************************************/  
  /** (3) Implement the Loader’s state-dependent behavior **/  
  /*********************************************************/  
  
  @Override  
  protected void onStartLoading() {  
    if (mData != null) {  
      // Deliver any previously loaded data immediately.  
      deliverResult(mData);  
    }  
  
    // Begin monitoring the underlying data source.  
    if (mObserver == null) {  
      mObserver = new SampleObserver();  
      // TODO: register the observer  
    }  
  
    if (takeContentChanged() || mData == null) {  
      // When the observer detects a change, it should call onContentChanged()  
      // on the Loader, which will cause the next call to takeContentChanged()  
      // to return true. If this is ever the case (or if the current data is  
      // null), we force a new load.  
      forceLoad ();  
    }  
  }  
  
  @Override  
  protected void onStopLoading() {  
    // The Loader is in a stopped state, so we should attempt to cancel the   
    // current load (if there is one).  
    cancelLoad();  
  
    // Note that we leave the observer as is. Loaders in a stopped state  
    // should still monitor the data source for changes so that the Loader  
    // will know to force a new load if it is ever started again.  
  }  
  
  @Override  
  protected void onReset() {  
    // Ensure the loader has been stopped.  
    onStopLoading();  
  
    // At this point we can release the resources associated with 'mData'.  
    if (mData != null) {  
      releaseResources(mData);  
      mData = null;  
    }  
  
    // The Loader is being reset, so we should stop monitoring for changes.  
    if (mObserver != null) {  
      // TODO: unregister the observer  
      mObserver = null;  
    }  
  }  
  
  @Override  
  public void onCanceled(List<SampleItem> data) {  
    // Attempt to cancel the current asynchronous load.  
    super.onCanceled (data);  
  
    // The load has been canceled, so we should release the resources  
    // associated with 'data'.  
    releaseResources(data);  
  }  
  
  private void releaseResources(List<SampleItem> data) {  
    // For a simple List, there is nothing to do. For something like a Cursor, we   
    // would close it in this method. All resources associated with the Loader  
    // should be released here.  
  }  
  
  /*********************************************************************/  
  /** (4) Observer which receives notifications when the data changes **/  
  /*********************************************************************/  
   
  // NOTE: Implementing an observer is outside the scope of this post (this example  
  // uses a made-up "SampleObserver" to illustrate when/where the observer should   
  // be initialized).   
    
  // The observer could be anything so long as it is able to detect content changes  
  // and report them to the loader with a call to onContentChanged(). For example,  
  // if you were writing a Loader which loads a list of all installed applications  
  // on the device, the observer could be a BroadcastReceiver that listens for the  
  // ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular   
  // Loader whenever the receiver detects that a new application has been installed.  
  // Please don’t hesitate to leave a comment if you still find this confusing! :)  
  private SampleObserver mObserver;  
}

Summary

I hope this article was useful to you and provided a good understanding of how Loaders and LoaderManager work together to perform asynchronous tasks and automatically update query results. Remember, Loader is your friend. . . If you use them, your app will benefit from the corresponding performance and the amount of code required. I hope that by enumerating their details, I can reduce its learning curve.

Side note
1. You don't need to worry about registering listeners for your Loader unless you are not going to use it with LoaderManager. The LoaderManager acts as a "listener" and passes any results returned by the Loader to the LoaderCallbacks#LoadFinished method.
2. The Loader may also be in the "abandoned" state (Translator's Note: Abandoned state?). This is an optional intermediate state between the stopped state and the reset state. For a more concise understanding, the discarding state is not discussed here. That said, in my experience, it is usually not necessary to implement the onAbandon() method.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327028689&siteId=291194637