A good practice in creating a responsive application is to ensure that your user interface requires a minimum of processing time. Each potentially long action that your application can hang should be put into a separate thread. Typical examples of such actions are network operations that carry unpredictable delays. Users can put up with small pauses, especially if you inform them about the progress, but the frozen application does not give them any choice but to close it.
In this tutorial, we will create a picture uploader that illustrates this situation. We will populate the
ListView with thumbnails of images downloaded from the web. The created asynchronous process loading images in the background will speed up our application.
Picture Loader
')
Downloading images from the Internet is very simple and is done using the related HTTP class from the framework. Here is one of the implementations:
static Bitmap downloadBitmap( String url) {
final AndroidHttpClient client = AndroidHttpClient.newInstance( "Android" );
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w( "ImageDownloader" , "Error " + statusCode + " while retrieving bitmap from " + url);
return null ;
}
final HttpEntity entity = response.getEntity();
if (entity != null ) {
InputStream inputStream = null ;
try {
inputStream = entity.getContent();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
} finally {
if (inputStream != null ) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (Exception e) {
// IOException IllegalStateException
getRequest.abort();
Log.w( "ImageDownloader" , "Error while retrieving bitmap from " + url, e.toString());
} finally {
if (client != null ) {
client.close();
}
}
return null ;
}
* This source code was highlighted with Source Code Highlighter .
Client and HTTP request created. If the request is successful, then the response flow will contain a picture, which will then be turned into a final icon. For normal operation of your application, its manifest should require
INTERNET .
Caution: a bug in the previous version of
BitmapFactory.decodeStream may interfere with the execution of your code during a slow connection. To avoid this problem, use FlushedInputStream (inputStream). Here is an example of this helper class:
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip( long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in .skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int byte = read();
if ( byte < 0) {
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
It grants that skip () will really miss that many bytes when we reach the end of the file.
If you suddenly use this method directly in the
ListView method of the
ListAdapter , then as a result you will get incredibly intermittent scrolling.
Moreover, this is a very bad idea, because
AndroidHttpClient cannot be started along with the main thread. Instead, this code will display the error “This thread forbids HTTP requests” (“This thread loses HTTP requests”). Better use
DefaultHttpClient , if you really want to put a stick in the wheel.
About asynchronous tasks
The
AsyncTask class provides one of the simplest ways to launch a new task directly from the user interface thread. Let's create an
ImageDownloader class that will be responsible for creating these tasks. It will provide a
download method that will assign the downloaded image with its URL in the ImageView:
public class ImageDownloader {
public void download( String url, ImageView imageView) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
}
}
/* BitmapDownloaderTask, */
}
* This source code was highlighted with Source Code Highlighter .
BitmapDownloaderTask is an asynchronous task that actually downloads the image. It starts using
execute , which returns the value immediately, making this method really fast. Here is an example of the implementation of this class:
class BitmapDownloaderTask extends AsyncTask< String , Void, Bitmap > {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
// Actual download method, run in the task thread
protected Bitmap doInBackground( String ... params ) {
// params comes from the execute() call: params[0] is the url.
return downloadBitmap( params [0]);
}
@Override
// Once the image is downloaded, associates it to the imageView
protected void onPostExecute( Bitmap bitmap) {
if (isCancelled()) {
bitmap = null ;
}
if (imageViewReference != null ) {
ImageView imageView = imageViewReference. get ();
if (imageView != null</fonthttp://habrahabr.ru/edit/topic/99631/#>) {
imageView.setImageBitmap(bitmap);
}}}}
The
doInBackground method actually works on the task in its own process. It will simply use the
downloadBitmap method that we introduced at the beginning of this article.
onPostExecute is launched on the called UI thread when the task is completed. It takes the resulting image as a parameter associated with
imageView from
download and stored in
BitmapDownloaderTask . Remember that
imageView is stored as
WeakReference , so in the process of loading it will not hurt to clear it from the collected garbage. This explains why we need to make sure that both the links and
imageView have a non-zero value (that is, they were not compiled) before using them in
onPostExecute .
This example shows a simplified use of
AsyncTask , and if you try this method, you will see that these few lines of code significantly increase the performance in
ListView , which will now scroll smoothly.
However, the specific
ListView behavior causes a problem in our implementation. Moreover, for efficiency reasons,
ListView recycles images that appear on the screen during scrolling. If one object is skipped, it will be used by
imageView repeatedly. Another time, the pictogram is displayed correctly at boot time, and is replaced with an image after the download. But what is the problem? As with most parallel applications, the whole problem is streamlined. In our case there is no guarantee that the download of tasks will end in the same order in which the download began. The result may be such that the displayed image will refer to the previous object in the list, since it took longer to load. It is not a question if the images you downloaded are linked once, and
ImageViews are set for all, but let's fix this in the general case when they are used as a list.
Concurrency control
To solve this problem, we need to remember the order of the start of downloads, since the last download started is the most effective for displaying (?). Indeed, it is enough for each
ImageView to remember its latest download. We will set this additional information using a special
Drawable subclass that will communicate with the
ImageView while it is loading. Here is the code of our
DownloadedDrawable class:
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference. get ();
}
}
This is an implementation using
ColorDrawable , the result of which in
ImageView will be a black screen when loading. You can use the “loading in process” image to inform the user. Again, note the use of
WeakReference to reduce dependencies.
Let's change our code to take into account the new class. First, the
download method must create an instance of this class, and associate it with
imageView .
public void download( String url, ImageView imageView) {
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url, cookie);
}
}
The
cancelPotentialDownload method stops possible
imageView downloads that will start soon. Please note that this is not enough to ensure that the newest downloads are always displayed, since the task can be completed and waiting for its
onPostExecute method, which can only be completed after the download.
private static boolean cancelPotentialDownload( String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null ) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null ) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel( true );
} else {
// The same URL is already being downloaded.
return false ;
}
}
return true ;
}
cancelPotentialDownload uses the
cancel method from the
AsyncTask class to stop active downloads. It returns
true most of the time, so the download can be started in
download . The only case in which we do not want this is when the download comes from the same URL, then we let it end. Remember that with this implementation, if the
ImageView has garbage collected, the downloads associated with it will not stop.
RecyclerListener must be used for this.
This method uses the
getBitmapDownloaderTask helper function:
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null ) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null ;
}
Finally,
onPostExecute should be modified so that it associates icons only if the
ImageView is still associated with this particular loading process.
if (imageViewReference != null ) {
ImageView imageView = imageViewReference. get ();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
if ( this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
With these modifications, our
ImageDownloader class now provides the basic services we expected from it. Feel free to use it, or an asynchronous template that provides the flexibility of your applications.
Demonstration
The source code of this application
is available on Google Code . You can switch between three different implementations described in this article. Remember that the cache size was limited to ten images in order to better demonstrate how different methods work.

For the future
This code has been simplified in order to focus your attention on concurrency and many other functions that are missing from our implementation. The
ImageDownloader class will benefit from the cache, especially if used in conjunction with a
ListView , which will most likely display the same image as many times as the user will use to scroll the screen up and down. Also, if necessary, you can add thumbnails and resize images.
Download errors and timeouts are correctly displayed in our implementation, it will simply generate an empty picture in this case. If you want, you can show any image that reports an error.
Our HTTP request is very simple. You can add parameters or connect cookies that some sites require.
The
AsyncTask class used in this article is really a very convenient way to reduce user interface load. You can also use the
Handler class to more finely control actions, such as controlling the total number of parallel downloads.
UPD. Thanks for the comments, corrected.