Using the Volley library's NetworkImageView is a convenient way to handle showing images from the web.
However, it has some bugs (as i've written here).
One of the issues that you can have by using it is that it doesn't decode the images from the web in a memory efficient way.
This means that if you use a gridView with multiple NetworkImageView in it, and each shows an image that has an unknown resolution (could be small, could be large), you would end up having an OOM .
as an example, you can set the url of this object to be this one and see for yourself how much memory the app uses after showing the bitmap, compared to how much it used before.
How can i modify the way that NetworkImageView decodes the bitmap ?
One way I could change it is to make it decode the bitmap while downscaling it to the needed size (or at least set the max of it to the screen size), for example using this method of downscaling .
Volley has a built in method for fitting an image to a given width and height like you mentioned. You need to stop using the convenience methods of loading images provided by NetworkImageView
which don't use it. I suggest using the following methods to decrease the chance for OOM errors:
NetworkImageView
. Use a regular ImageView
and implement the listener to apply the image when it is available. This is a prerequisite for step 2. Using a NetworkImageView
with the get()
method may lead to problems in my experience`.
ImageLoader
and use the get()
method which receives an ImageRequest
. Use the optional constructor that takes in a maxHeight and maxWidth as parameters if you can.get()
method in the ImageLoader
, save the ImageContainer
reference that method returns so you'll be able to cancel a request if the view is recycled before the request completes.ImageCache
in the ImageLoader
constructor. That'll lower the redundancy in decoding bitmaps that are already available.recycle()
method on the bitmaps, but be careful not to recycle the ones you might still need. Code snippet for (2) + (4)
// assuming sRequestQueue is your static global request queue
// and `BitmapCache` is a good implementation for the `ImageCache` interface
sImageLoader = new ImageLoader(sRequestQueue, new BitmapCache());
Code snippet for (3) assuming the ViewHolder pattern and imageContainer
is a member of the ViewHolder
class. The principal applies to any architecture.
// when applying a new view cancel the previous request first
if (imageContainer != null) {
imageContainer.cancelRequest();
}
// calculate the max height and max width
imageContainer = sImageLoader.get(imageUrl,
new DefaultImageListener(image), maxWidth, maxHeight);
The default image loader (you can do what you here):
private class DefaultImageListener implements ImageListener {
private ImageView imageView;
public DefaultImageListener(ImageView view) {
imageView = view
}
@Override
public void onErrorResponse(VolleyError error) {
//handle errors
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
imageView.setImageBitmap(response.getBitmap());
}
}
}
I found a better solution in my search : -)
NetworkImageView knows its width at line no - 104 and height at line no - 105 in the link NetworkImageView.java
Below is the exact code at NetworkImageView.java
private void loadImageIfNecessary(final boolean isInLayoutPass) {
int width = getWidth(); // at line no 104
int height = getHeight(); // at line no 105
you only need to forward this information to the image loader.
On line 141 NetworkImageView.java calls the ImageLoader#get(String requestUrl, final ImageListener listener) method without width and height. Change this call to ImageLoader#get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight).
Replace the code from line no 141 to 172 of NetworkImageView.java with below code
ImageContainer newContainer = mImageLoader.get(mUrl,
new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (mErrorImageId != 0) {
setImageResource(mErrorImageId);
}
}
@Override
public void onResponse(final ImageContainer response, boolean isImmediate) {
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
onResponse(response, false);
}
});
return;
}
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
} else if (mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
}
}, width, height);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With