Chapter 7. Image Handling and Memory Management

In this chapter, we will take a look at how to show images downloaded from a URL. We will discuss how to do this using the Android native SDK as well as the commonly used third-party libraries. We will consider key concepts and features such as download, compression, cache systems, and storage in memory or disk.

We will also discuss what a nine patch is and how to create it, and we will speak about the different size and density folder for drawables by introducing vector drawables.

The final section will be focused on memory management. Identifying memory leaks in our app is a critical task, which usually happens while working with images. We will take a look at the common mistakes that can lead to these leaks as well as general tips on how to prevent them.

  • Displaying images from the network
    • The traditional way
    • Volley ImageDownloader
    • Picasso
  • Images
    • Vector drawables
    • Animated vector drawables
    • Nine patch
  • Memory management
    • Detecting and locating leaks
  • Preventing leaks

Downloading images

Downloading an image and displaying it with the help of ImageView can be done in a single line. Since Android development started, this is something that every developer has done. Android is a technology that is more than five years old, so we can expect this technique to be quite advanced and to find-third party solutions that facilitate it. That said, this book wouldn't be called Mastering Android if it didn't explain the process of downloading an image and displaying it without any third-party library.

It is good to use the latest library in your apps, but it is better to understand the solution that you are implementing, and it is even better to be able to build this library yourself.

While working with images, we need to handle everything from network connection to the downloading of array bytes and their conversion to Bitmap. On some occasions, it makes sense to store the images on a disk so that the next time we open the app, these images will already be there.

Even if we are able to display an image, the matter doesn't finish here; we should be able to manage the downloading of images inside a list view. The downloading, storing, and displaying of systems need to be in sync for the app to work without glitches and have a fluent list that can scroll without problems. Keep in mind that when we scroll through a list, the views are recycled. This means that if we scroll fast, we might start the downloading of an image. By the time this download finishes, the view will not be visible on the screen anymore, or it will be recycled in another view.

The traditional way of downloading images

To display an image without using any third-party libraries (an image hosted on the Internet with a URL), we need to establish a connection using HttpURLConnection. We would need to open an input stream and consume the information, which can be transformed into a Bitmap image with the factory method, BitmpapFactory.decodeStream(InputStream istream). We could convert it from an input stream to a file so that the image could be stored in the disk and accessed later. For the moment, let's try to download it first and convert it into a Bitmap image, which we will keep in the memory and show in ImageView.

We will show the logo of the company in OfferDetailActivity for every offer. Remember that in Parse, we created a database, and with it we created a field called imageLink. You just need to fill that field with the URL of the logo of that company.

The traditional way of downloading images

We need to have the image link in OfferDetailActivity; for this, we need to send an extra parameter in the intent in JobOfferAdapter for when we tap on a card. Use the following code:

@Override
public void onClick(View view) {
  Intent intent = new Intent(view.getContext(), OfferDetailActivity.class);
  JobOffer offer = mOfferList.get(getPosition());
  intent.putExtra("job_title", offer.getTitle());
  intent.putExtra("job_description",offer.getDescription());
  intent.putExtra("job_image",offer.getImageLink());
  view.getContext().startActivity(intent);
}

The method in charge of the image download will be a static method that can be called from anywhere in the app. This method will be placed in the ImageUtils class inside a package called utils. We will first check whether the URL is correct, and after this, we will consume the content from HttpURLConnection, converting the input stream into a Bitmap image as we explained before:

public static Bitmap getImage(String urlString) {
  
  URL url = null;
  
  try {
    url = new URL(urlString);
  } catch (MalformedURLException e) {
    return null;
  }
  
  HttpURLConnection connection = null;
  try {
    connection = (HttpURLConnection) url.openConnection();
    connection.connect();
    int responseCode = connection.getResponseCode();
    if (responseCode == 200) {
      return BitmapFactory.decodeStream(connection.getInputStream());
    } else
      return null;
  } catch (Exception e) {
    return null;
  } finally {
    if (connection != null) {
      connection.disconnect();
    }
  }
}

We will create a method called displayImageFromUrl() that receives ImageView and a string with the link to do all the work instead of having all this logic inside onCreate. In onCreate, we just need to retrieve the parameters and call the method:

String imageLink = getIntent().getStringExtra("job_image");
ImageView imageViewLogo = (ImageView) findViewById(R.id.logo);

displayImageFromUrl(imageViewLogo,imageLink);

At this stage, we can be tempted to call ImageUtils.getImage(link) and set Bitmap to ImageView. However, we are missing one thing; we can't just call the method that opens a network connection in the main activity thread. We need to do this in the background, or we would get an exception. An AsyncTask method is a nice solution to this problem:

String imageLink = getIntent().getStringExtra("job_image");
ImageView imageViewLogo = (ImageView) findViewById(R.id.logo);

displayImageFromUrl(imageViewLogo,imageLink);

public void displayImageFromUrl(ImageView imageView, String link){
  
  new AsyncTask<Object,Void,Bitmap>(){
    
    ImageView imageView;
    String link;
    
    @Override
    protected Bitmap doInBackground(Object... params) {
      imageView = (ImageView) params[0];
      link = (String) params[1];
      
      return ImageUtils.getImage(link);
    }
    
    @Override
    protected void onPostExecute(Bitmap bitmap) {
      super.onPostExecute(bitmap);
      imageView.setImageBitmap(bitmap);
    }
    
  }.execute(imageView, link);
}

Depending on the shape and background of the images used, it will look better with the ImageView attribute, scaleType, with the centerInside or centerCrop value. The CenterInside value will scale down the image to ensure that it fits in the recipient while keeping the proportions. The CenterCrop value will scale up the image until it fills the smallest side of the recipient. The rest of the image will go out of the bounds of ImageView.

At the beginning of the chapter, I mentioned that this could have been done just with a single line of code, but as you can see, doing it by ourselves takes much more than one line and involves different concepts such as background threading, HttpURLConnection, and so on. This is just the beginning; we implemented the simplest possible scenario. If we were setting the image in the same way in the rows of a list view, we would have problems. One of these problems would be firing infinite AsyncTask calls while scrolling. This could be controlled if we had a queue with a maximum number of AsyncTask and a cancellation mechanism to ignore or cancel the requests of the views that are not on the screen.

When we launch the AsyncTask, we have a reference to ImageView, and in PostExecute, we set Bitmap to it. This downloading operation can take some time so that ImageView can be recycled while scrolling. This means that we are downloading an image for ImageView that is recycled in a different position on the list to display a different element. For instance, if we had a list of contacts with their faces, we would see the faces of people with the wrong names. To solve this, what we can do is set the String with the image link to ImageView as a tag, myImageView.setTag(link). If the view is recycled, it will have a different item with a new link; therefore, we can check in onPostExecute, just before displaying the image, whether the link that we have now is the same as the one in the ImageView tag.

These are two common problems and their respective solutions, but we haven't finished here. The most tedious thing, if we continue down this road, is to create a cache system. Depending on the application and on the situation, we might want to permanently store a downloaded image. For instance, if we were creating a music app with a list of your favorite albums, it would make sense to store the cover of an album in the disk. If you are going to see the list of favorites every time you open the app and we know that the cover is not going to change, why not store the image permanently so that the next time we open the app, it loads much quicker and doesn't consume any data? For the user, it would mean seeing the first screen loaded instantly all the time and be a huge improvement to the user's experience. To do this, we need to download the image on a file and have a third method to read the image from the file later, including the logic to check whether we already have this image downloaded or it's the first time that we have asked for it.

Another example can be a newsfeed reader app. We know that the images are going to change almost every day, so there is no point in keeping them on the disk. However, we might still want to keep them in memory while navigating through the app not to download them again in the same session while coming back to an activity from another. In this case, we need to keep an eye on the memory usage.

It's time to introduce some third-party libraries to help us with this topic. We can start with Volley, the same Volley that we implemented for network requests.

Downloading images with Volley

Volley offers two mechanisms to request images. The first mechanism, ImageRequest, is very similar to what we have just done with an AsyncTask using Volley's request queue and resizing the image on demand. This is the constructor for a request:

public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, Config decodeConfig, Response.ErrorListener errorListener) { … }

The maxWidth and maxHeight params will be used to resize the image; if we don't want to resize, we can set the value to 0. This is a method in our example used to fetch the image:

public void displayImageWithVolley(final ImageView imageView, String url){
  
  ImageRequest request = new ImageRequest(url,
  new Response.Listener<Bitmap>() {
    @Override
    public void onResponse(Bitmap bitmap) {
      imageView.setImageBitmap(bitmap);
    }
  }, 0, 0, null,
  new Response.ErrorListener() {
    public void onErrorResponse(VolleyError error) {
      
    }
  });
  
  MAApplication.getInstance().getRequestQueue().add(request);
}

The second mechanism, the really interesting one, is ImageLoader. It handles multiple requests at the same time and is the mechanism to use in a list view for the reasons we explained in the previous section. We can create the cache mechanism that we want it to use—memory or disk.

It works using a special type of ImageView: NetworkImageView. When the ImageLoader object is ready, we can simply download an image with one line using NetworkImageView:

myNetworkImageView.setImage(urlString, imageloader);

It allows us to perform different operations such as setting a default image or setting an image in case the request fails. Use the following code:

myNetworkImageView.sesetDefaultImageResId(R.id.default_image);
myNetworkImageView.setErroImageResId(R.id.image_not_found);

The complexity here, if there is any, comes when we implement ImageLoader. First, we need to create it in the same way that we did with RequestQueue in the Application class so that it can be accessed from anywhere in our app:

@Override
public void onCreate() {
  super.onCreate();
  
  sInstance = this;
  
  mRequestQueue = Volley.newRequestQueue(this);
  
  mImageLoader = new ImageLoader(mRequestQueue, new myImageCache());

The constructor needs a cache implementation. Google is an example of a memory-based cache whose size is equal to three screens worth of images:

public class LruBitmapCache extends LruCache<String, Bitmap>
implements ImageCache {
  
  public LruBitmapCache(int maxSize) {
    super(maxSize);
  }
  
  public LruBitmapCache(Context ctx) {
    this(getCacheSize(ctx));
  }
  
  @Override
  protected int sizeOf(String key, Bitmap value) {
    return value.getRowBytes() * value.getHeight();
  }
  
  @Override
  public Bitmap getBitmap(String url) {
    return get(url);
  }
  
  @Override
  public void putBitmap(String url, Bitmap bitmap) {
    put(url, bitmap);
  }
  
  // Returns a cache size equal to approximately three screens worth of images.
  public static int getCacheSize(Context ctx) {
    final DisplayMetrics displayMetrics = ctx.getResources().
    getDisplayMetrics();
    final int screenWidth = displayMetrics.widthPixels;
    final int screenHeight = displayMetrics.heightPixels;
    // 4 bytes per pixel
    final int screenBytes = screenWidth * screenHeight * 4;
    
    return screenBytes * 3;
  }
}

We can see that choosing between cache implementations is a manual process; we have to create the class with the implementation required and set it in the constructor of ImageLoader. That is why, the next library that we are going to see was a revolution when it came out.

Introducing Picasso

The same people that created OkHttp brought Picasso to the Android community. Picasso allows us to download and display an image in one line of code without creating an ImageLoader and with a cache implementation that automatically works using disk and memory. It includes image transformation, ImageView recycling, and request cancellations. All of this is free. It is unbelievable what the people at Square are bringing to the community.

If this is not enough, the debug mode will display indicators in the images, a small triangle in the corner with different colors to indicate when we download an image for the first time (which is when it comes from the network), when it comes from the memory cache, and when it comes from the disk cache:

Introducing Picasso
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.214.155