Bitmap objects let coders create classes and lists so that they can store information and retrieve it later with an HTTP request. This process is often faster than loading images and data at the same time. Learn how to load images in response to list requests in Android apps with RESTful in this online tutorial.
In the previous movie, I described how to load images at the same time as you load data from a web service. But as you saw in the demonstration, when you code the app that way, it results in a long delay when you first load the data, because you're not just loading the JSON or XML, you're loading all the images at the same time. Once everything's loaded, everything scrolls smoothly, but you might want to speed up the initial loading and delay the loading of the images until they're needed.
This is a process that sometimes called lazy loading. Essentially the strategy is to take the Async Task that's loading the images and take it out of the main activity and instead put it into the adapter that's being used to provide views for the list. Each time a new item is scrolled to in the list, that results in a call to the get view method. Right now, we're loading up the image in the get view method from memory, from the list of flowers.
But let's assume that the image hasn't been loaded yet. I'll go back to this main activity class. And I'm going to go down here to where I'm loading the images, and I'm going to chop that bit of code out. For the moment, I'm just going to comment it. And now, when I get into the flower adapter class, the image won't be in the flower object and I won't be able to provide it to the image view. And so, I'm going to detect that condition and use a new Async task that I'll define here within the adapter.
And that Async task will execute its own HTTP request to go get the image for the current view. Here are some basic steps. First of all to architect all of this, I'm going to be passing a couple of things around. I need to pass an instance of the flower object, that's my data object. I need to pass an instance of the view and I need to pass a bitmap, but the Async task class can only handle one object at a time, for inputs and results. So, I'm going to create a new class here, that will serve as a container for all those three objects.
I'm going to name the class flower and view, and I'll give it three public fields. The first will be an instance of the flower class, named flower. The second in instance of the Android view class named view. And, the third in instance of the bitmap, class named bitmap. So now I have a class that I can use to pass all this data around. I'll be sure to import the bitmap class from Android.graphics. Next, I'll define my Async task. I'll make it private and give it a name of image loader and I'll extend Async task.
When I auto complete the name of the class, I see the generic declarations. The parameter type will be flower and view, the new class that I just defined. I won't be working with the progress value, so I'll pass in a value of void, with an upper case V. And for the result, I'll once again use flower and view. After completing the braces, I'll move the cursor back up to the class declaration and I'll use a quick fix and add unimplemented methods and that creates my doInBackground method. When the doInBackground is called, it'll receive a set of parameters, and the way I'm using it, it'll receive one parameter, an instance of the flower and view container. So here, I'll declare a reference to that object, so I can address it easily. I'll declare it as flower and view, and name it container. And I'll get its value by calling params with an index of 0. Next, I'll get a reference to the flower object that's within the container, with flower. And I'll name is flower in all lowercase, and I'll get its reference with container.flower. Now I'm ready to get the image from the web. I'll go back to my main activity class, and I'll take some of this code that I commented out and copy it to the clipboard. I don't need the for loop. I only need the code that's retrieving a single image. So I'll select and copy that, come back to the adapter class, paste it in, and then select and uncomment it. I'll select everything and press control or command I, to reformat and get my indentation back. Notice that in this code, I'm referring to photos base URL.
A constant that I had declared in the main activity class. I'll go back to the main activity class, go to the top of the class, and I'll make this constant public. Then I'll go back to the flower adapter class and add main activity as a prefix to the constant name. And now that line of code is working. Next, I need to add some imports. I'll go to each of these lines where an error is showing up and I'll use quick fixes. And I'll import, input stream, and I'll import URL. And on the next line, I'll import bitmap factory. And so one at a time, I'm getting rid of all the errors. Finally at the end of this code, I'll take the bitmap that's been retrieved and I'll save it in the container with container.bitmap equals bitmap, and then I'll return the container. The asynchronous task should now be complete. If I get down to the bottom of the code, I'll return null. Meaning something went wrong. So now I'll deal with the result. Just as before with an Async task, I'll do that with a call to the method on post execute. I'll type the beginning of the method name and press control space and choose it from the list. I'll remove all of the code that's generated, and here's how I'll handle the result. I'll go get a bit of code that already exists up here, at lines 45 and 46. This is the code that's saving the bitmap, and displaying it in the image view. I'll copy that to the clipboard and go down to my onPostExecute method and paste it in.
I'll deal with this first error by adding result to the beginning. Remember that the flower end view container will contain a reference to the current view and so result.view will reference the current view correctly. Next I'll deal with this error. Remember that I saved the bitmap to the flower and view object before I returned it. So now to set the bitmap in the image view, I'll use the expression result.bitmap and that displays the image.
And finally, I'll save the bitmap object for future use by adding it to the flower object. Once I've saved it with a flower object, it'll stay in memory and then I won't have to retrieve it from the web again. I'll do that with this code. Result.flower.setbitmap and I'll pass in result.bitmap. So the coded line 83 is displaying the image and the coded line 84 is saving it for future use.
My Async task in now complete. Now it's time to use it. I'll go back up to my get view method where I'm displaying the flower photo. And I'm going to wrap the existing code with an if condition. The condition will look like this, if (flower.getBitmap() != null). And I'll take this bit of code where I'm setting the image from the flower object and I'll move it into the conditional block. But, if the Bitmap is null, then I need to use my asynchronous task. So I'll create an instance of my flower in view class and I'll name it container.
And I'll instantiate it with its no arguments constructor. Then I'll populate it with the current flower and the current view with container.flower equals flower and container.view equals view. Now I have an object I can pass around with the Async task. I'll create an instance of image loader, My Async task. I'll name it Loader, and instantiate it with its no arguments constructor. And then I'll execute the task, with Loader.execute and I'll pass in the container.
So here's the logic. If I already have a bitmap in memory, I'll simply display it. But if I don't, I'll package it up along with the reference to the current view and then I'll create my asynchronous task and execute it. The asynchronous task in its doing background method will use all of the same code as before to retrieve the bitmap from the web. And then, in the onPostExecute method, it'll display the image and save it for future use. I'll come back to my main activity class and do a little bit of clean up. Now I'll totally get rid of this code, I don't need that anymore. And then I'll take a look at my imports up at the top and I'll press ctrl + shift + O or command + shift + O on Mac, to organize imports and eliminate the imports that I'm no longer using.
And now I'm ready to test the app. I'll run it in the emulator. And this time when it runs, watch what happens. The data from the json feed appears almost instantly. But then the images fill one at a time as they're retrieved from the web. As I scroll down, notice there's a slight delay in new images appearing. But then as I scroll back up again, you'll see that the images flow and display smoothly. And that's because I'm saving the bit maps in the flower objects, and those are staying in memory. So, this strategy works great when you have a small amount of content, and where the number of images that you have to store in memory is guaranteed not to overload device memory.
But in apps where you're dealing with a lot of images, you have to take a more sophisticated approach. And I'll show you how to do that in the next movie.
- Working with background threads
- Defining background tasks
- Requesting content with HTTP requests
- Parsing web service responses
- Downloading data, including images
- Sending HTTP parameters to the server
- Using higher-level client libraries like Volley and OkHttp
- Managing an app's networking abilities