This article introduces the Loader <D> class and introduces the implementation of custom Loader. This is the third article in this series.
I. Loaders World 2: Understanding LoaderManager 3: Implementing Loaders 4: instance: AppListLoader
Top priority: if you haven't read the previous two articles, I suggest you read them before going deeper. First, briefly summarize the content covered by this blog. The world before Loader (Article 1) describes the data loading methods before Android3.0 and the lengthy query operations executed in the UI main thread. These unfriendly APIs cause poor application response. The general situation is to understand the motivation for writing LoaderManager (article 2. This article introduces the LoaderManager class and describes its role in asynchronously loading data. LoaderManager manages Loaders during the Declaration cycles of Activity and Fragment, and maintains the loaded data when the configuration changes. (Note: This avoids data reloading due to restart of Activity ).
Loader Basics
Loades is responsible for executing queries in a separate thread to monitor changes to the data source. When a change is detected, the queried result set is sent to the registered listener (usually LoaderManager ). The following features make Loaders a powerful tool in AndroidSDK:
1. it encapsulates the actual data loading. Activity/Fragment no longer needs to know how to load data. In fact, Activity/Fragment delegates the task to Loader, which executes query requirements in the background and returns the results to Activity/Fragment.
2. The client does not need to know how to execute the query. Activity/Fragment does not need to worry about how the query is executed in an independent thread. Loder will automatically execute these query operations. This method not only reduces Code complexity, but also eliminates the possibility of thread-related bugs.
3. It is a security event-driven approach. Loader detects the underlying data. When it detects a change, it automatically performs a new load to obtain the latest data. This makes it easy to use Loader. The client can believe that Loader will automatically update its data. What Activity/Fragment needs to do is to initialize the Loader and respond to any feedback data. In addition, all other things are solved by Loader.
Loaders is a relatively advanced topic and may need more time to use it. In the next section, we will start by analyzing the features of its four definitions.
What makes Loader?
A total of four features ultimately determine the behavior of a Loader:
1. Execute the asynchronous loading task. To ensure that the load operation is performed in an independent thread, the Loader subclass must inherit the AsyncTaskLoader <D> rather than the Loader <D> class. AsyncTaskLoader <D> is an abstract Loader that provides an AsyncTask to perform its operations. When defining child classes, asynchronous tasks are implemented by implementing the abstract method loadInBackground. This method loads data in a work thread.
2. Receive the returned results after loading in a registration listener (see note 1 ). For each Loader, LoaderManager registers an OnLoadCompleteListener <D>. this object will send the result to the client by calling the onLoadFinished (Loader <D> loader, D result) method. By calling Loader # deliverResult (D result), Loader passes the result to registered listeners.
3. Three different statuses (see note 2 ). Any Loader will be in the following three States: started, stopped, and restarted: a. Loader in the started state will perform the load operation and deliver the result to the listener at any time. The started Loader will listen for data changes and execute new loads when the changes are detected. Once started, the Loader will remain in the started State until it is switched to stopped and restarted. This is the only state in which onLoadFinished will never be called.
B. The Loader in the stopped status will continue to listen for data changes, but will not return the results to the client. In the stopped status, the Loader may be started or restarted. C. When the Loader is restarted, it will not perform a new loading operation, nor send a new result set, nor detect data changes. When a Loader enters the restart state, it must release the corresponding data reference to facilitate garbage collection (similarly, the client must confirm that after the Loader is invalid, all references to the data are removed ). Generally, the Loader will not be restarted twice. However, in some cases, it may be started, so they must be restarted in due time if necessary.
4. An observer is notified of changes to the data source. Loader must implement one of these observers (such as ContentObserver and BroadcastReceiver) to detect changes to the underlying data source. When data changes are detected, the observer must call Loader # onContentChanged (). Perform two different operations in this method:. if the Loader is already started, a new loading operation is executed. B. setting a flag indicates that the data source has changed, so that when the Loader starts again, it will know that the data should be reloaded.
So far, you have basically understood how Loader works. If you do not have one, I suggest you put it first and read it again later (read this document ,). That is to say, let's start with the actual code and write it.
Implement Loader
As I stated earlier, you need to pay attention to the implementation of custom Loader. The subclass must implement the loadInBackground () method, and must override onStartLoading (), onStoppLoading (), onReset (), onCanceled (), and deliverResult (D results) to implement a complete Loader function. 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 is started for the first time, it enables LoaderManager to start each of its Loaders in Activity # onStart. If a Loader is not started, LoaderManager calls the startLoading () method. This method changes the Loader to the started state and immediately calls the onStartLoading () method of the Loader. That is to say, LoaderManager does a lot of work in the background based on the correct implementation of Loader, so do not underestimate the importance of implementing these methods.
The following code is a typical implementation model of Loader. The SampleLoader query result is a List of SampleItem objects, and the List of query results <SampleItem> is returned 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 = data; 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 will be useful to you and give you a good understanding of how Loaders and LoaderManager work together to execute asynchronous tasks and automatically update query results. Remember, Loader is your friend... If you use them, your app will benefit from the performance and amount of code required. I hope to reduce the learning curve by listing their details.
Note 1. You do not need to worry about registering listeners for your Loader unless you are not ready to work with LoaderManager. LoaderManager assumes the "listener" role and sends any result returned by Loader to LoaderCallbacks # LoadFinished method. 2. The Loader may also be in the "abandoned" state ?). This is an optional intermediate state, between the stopped state and the reset state. For a more concise understanding, we will not discuss the discard status here. In other words, in my experience, there is usually no need to implement the onAbandon () method.