There are many higher level components, both in the Android SDK and in open-source libraries, that implement asynchronous programming techniques. When you use these tools, you don’t have to create your own threads or AsyncTask objects; the API is doing it for you. One important example is the Loader architecture.
- [Instructor] There are many higher level components both in the Android SDK and in open source libraries that implement asynchronous programming techniques. When you use these tools, you don't have to create your own threads or async task objects. The API does it for you. One important example is the loader architecture. Loaders have been part of Android since its early days. A loader let's you acquire data in a background thread and provide it to view components, such as ListView and RecyclerView.
Typical sources of data for loaders include web services, local data in the device's persistent storage, and data from content providers that are managed by other apps on the device. In this demonstration, I'm going to use a cursor loader, a loader that retrieves data from some data source and provides access to it through a cursor object, and I'll get the data from the device's contacts repository, a data source that's connected to a Google account. Now, retrieving that sort of data requires a permission, and in this application's manifest I've included a uses-permission element referencing the READ_CONTACTS permission.
That's a dangerous permission, so I also have to ask for permission at runtime. So, this application's main activity has code that looks for the permission in the onCreate method, and if the permission has been granted it just tells me that, and otherwise it asks for permission asynchronously. In the asynchronous response, I check whether permission was granted and if so I note that by setting a boolean flag to true and then I try to load the data.
The method to load the data doesn't have any code in it yet. This is where I'll be putting the code to start loading the data with a loader object. Now, when I run the app on a device for the first time, I'll see that request for permission, and if I click Allow then I see the message permission was granted. If I back out of the application and then run it again, I see the Toast message permission already granted. So either way, the device can now look at the contacts data from the repository.
The first step in implementing a loader is to implement an interface called LoaderCallbacks. Then I'll choose the LoaderManager class. Notice that there are two versions of the class, one from the core SDK and one from the support library. I'll choose the version in the support library. And then I'll select the member interface LoaderCallbacks. This callback method can take a generic type declaration, and I'll set that to Cursor. So that means that when I retrieve data I'm going to be returning a cursor object and I'll be able to read the data through the cursor.
My class header now has an arrow condition, and I'll use an intention action and implement the required methods of the interface. There are three of them. They're named onCreateLoader, onLoadFinished, and onLoaderReset. So now my activity is the callback object that will respond to requests from the loader framework. My next step is to setup a few fields that contain information that the loader will need. I'll add these as fields of the class, and they'll all be private, static, and final.
I'll make three of those sets of declarations and I'll separate them out by line feeds. And for the first one I'll declare an array of strings, and it'll be called PROJECTION. A projection is a declaration of a set of columns that you'll be retrieving from the data source. And to identify those columns I'll use constants. These constants are member of a class called Contacts, which is a member class of ContactsContract.
The first column I need is called _ID. Now to make this a little bit easier to type I'll move the cursor back to the parent class, ContactsContract, and I'll use an intention action and add an import for that class. Now, it can refer to the constant as simply being a member of Contacts. That's the first column I'm requesting. I need two more. The second one is Contacts.LOOKUP_KEY, and the third is Contacts.DISPLAY_NAME_PRIMARY.
And I'm missing an equals operator up here and I'll fix that problem. Now, I'm only going to be displaying one value from the Contacts, the value in the column DISPLAY_NAME_PRIMARY, but the ID and LOOKUP_KEY will be important if I extend this application later on to work with Contacts more intensively. So, that's the first field. Here's the second one. This is also an array of strings, and this one is called FROM_COLUMNS. This is a list of the columns that I want to look at just for display purposes, and I'm only going to display one column, NAME_PRIMARY, so I'll just copy and paste that.
And then my third field is going to be an integer array and that one is going to be named TO_VIEWS. When I get the data from DISPLAY_NAME_PRIMARY I need to indicate which text view I want to display it in, and this will be a resource ID that I'll get from the Android SDK with android.R.id.text1. That's the ID of a text view in a particular layout file that I'll be using to display the data. So, my fields are complete and now I'm ready to start the process of loading the data.
When the data is returned, I'll be using something called a cursor adapter to bind the data to the ListView that's already a part of this application. I'll declare that adapter here with private CursorAdapter mCursorAdapter. I won't initialize the object here. I'm just declaring it. Now we'll go down here to the loadContactsData method and I'll initialize that adapter here. First, I'll check to make sure that I have permission.
I'll look at the boolean field mPermissionGranted, and if it's true I'll initialize mCursorAdapter with new SimpleCursorAdapter and I'll pass in a bunch of arguments. I'll pass in this for the context, and for the layout ID I'll use a layout that's included in the Android SDK with android.R.layout.simple_list_item_1. The next argument is the cursor object that provides access to the data.
I don't have that yet though, so I'll set that as null initially, and then I'll provide the two fields that I created indicating the names of the columns I'm getting data from and the view objects that I'm using to display the data. That's FROM_COLUMNS and TO_VIEWS. The last argument is an integer called the flags value, and I'll just pass in a value of zero for that. So, now I have my adapter object. Next, I need to bind the adapter to the ListView. The ListView is represented with the variable mListView.
And I'll call mListView.SetAdapter and I'll pass in mCursorAdapter. And those two are now bound together for the lifetime of the activity. And I'm ready to initialize my loader object. I'll call a method called getSupportLoaderManager, and from there I'll call initLoader and I'll pass in a loader ID of zero. I don't need to track that, so zero will work fine. I'll pass in null for the bundle of arguments. I'm not passing any arguments in. And then I'll pass in this as the callback object.
That's the object that has the callback methods. So now as the application starts up I've made the request for the data. When the data comes back, this method will be called, onCreateLoader. So now I've asked to initialize the loader. That results in calling the method onCreateLoader. And instead of returning null I'll return an instance of the CursorLoader class. Once again, I have a bunch of arguments to provide. I'll pass in this as the context, and for the URI that designates the data source I use Contacts.CONTENT_URI.
Next is the projection, the list of columns I'm retrieving, and I already have a field for that. The next two arguments, the selection and selection args, will be null, because I'm retrieving everything. And finally, I can set the sort order by once again using Contacts.DISPLAY_NAME_PRIMARY. So, I initialize the loader and then I indicate which loader object I want to use. When the data comes back, onLoadFinished will be called, and this will return both the loader object and the data, which is exposed through a cursor object.
And I only need one line of code here with mCursorAdapter.swapCursor and I'll pass in data. And now my code is complete. I'll run the application and let's see the result. As the app loads, it goes and gets the data from the Contacts repository on this device. And let's take a look at the original data source. This is the Android Contacts app and it shows exactly the same data. And then I'll run my own application and show that I'm retrieving the data from exactly the same place.
With this small amount of data, the load happens almost instantaneously, but if there's more data to retrieve and it takes longer, that's okay. The loader is doing its work in a background thread and it lets the user interface remain responsive. The loader architecture represents yet another way of managing asynchronous operations in Android. It's particularly useful when working with content providers, such as the one used to access contacts' information on an Android device, but you can create your own loaders to access data from the app's persistent storage or to execute arbitrary tasks in the background.
First, discover how to create and start simple background threads, and how to use handlers to manage a thread's message queue. Then, learn various methods for optimizing the scheduling and performance of background tasks in Android with AsyncTask, intent services, and the JobScheduler API. Plus, explore tools that help you implement multithreading for different tasks in Android: Loader, for asynchronous data loading, and the open-source API Retrofit, for making HTTP requests.
Note: To get the most out of this course, you should be comfortable programming with Java, and should understand the most basic skills that are needed to build Android apps with the Android SDK and Android Studio.
- Creating and running a background thread
- Sending messages to the UI from threads
- Managing multiple background threads
- Managing threads with AsyncTask
- Managing long-running tasks with services
- Scheduling background tasks with JobScheduler
- Using other APIs for concurrent programming