5

I have an Android app that allows the user to upload profile pics. They are stored 300px X 300px on the server.

In the app, I use them in either 40dp X 40dp or sometimes 100dp X 100dp. The 40dp's images are in ListView's. I actually use a very popular third party LazyList to store the images (using an ImageLoader class) in a cache. Here is the exact library.

I simply show image in my adapter like this:

imageLoader.DisplayImage(profileUrl, holder.iv1);

I take the web URL and display it directly into the ImageView 40dp x 40dp.

Sometimes I notice image degradation. Where the image is partially loaded, then is corrupted.

One theory is that going from a large image on the server and trying to store it in a small area in the actual app, then doing this many times in a ListView, is causing some loading issues.

The question: What is the best way to handle this situation when you have a much larger image in the server than you need to display? I am assuming I may need to use BitMapFactory or a similar class to build a new image. But I am not sure. I feel like I am missing a step.

Here is an example of an image in a ListView that has become corrupted, notice this does not happen to all. (And yes that is Tobias from Arrested Development.)

Follow-up: And how do we take in consideration file size for xxhdpi, xhdpi, etc when the image is coming from server and not the resources folder?

enter image description here

TheLettuceMaster
  • 15,594
  • 48
  • 153
  • 259

2 Answers2

5

The library you are using looks like it is barely maintained anymore (last update is at least a year old). There are several newer and likely better libraries that solve the same problem. My favorite is Square's Picasso. It is very simple to use and solves the image loading inside a ListView problem very well.

To more directly answer some of your questions:

What is the best way to handle this situation when you have a much larger image in the server than you need to display?

You need to subsample the Bitmap down to the target size. Google has a pretty good write up on all of this here.

First you have to decode the bitmap setting the inJustDecodeBounds boolean to get the bitmap size.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Then you can compute your sample size based on your target height and width.

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Then you can use the computed sample size to load the down sampled Bitmap. The final code would look something like

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

And how do we take in consideration file size for xxhdpi, xhdpi, etc when the image is coming from server and not the resources folder?

You can compute the target pixel size you want to downsize your bitmap to using the following snippet:

float pixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, "10", context.getResources().getDisplayMetrics());

TL;DR

Use Square's Picasso and it will solve all of your problems.

James McCracken
  • 15,488
  • 5
  • 54
  • 62
  • That library is perfect! and it is worth the 250 points alone! – TheLettuceMaster May 09 '14 at 23:29
  • Well, it's almost perfect, I wish there was an easy way to clear the cache. – TheLettuceMaster May 10 '14 at 20:35
  • I did see that, but I am not sure how to access the code since it is a this library is a jar file. I am sure I can figure it out, but in the end, I didn't really need this function as much as I thought I needed. Either way, sounds like the Picasso people are going to implement it soon. – TheLettuceMaster May 12 '14 at 15:15
  • You just need to create that PicassoTools class in the package com.squareup.picasso. That will give you access to the package-private cache. – James McCracken May 12 '14 at 16:31
  • @JamesMcCracken i am using picasso resize with centrecrop but my recyclerview is hanging i mean pics are already cached but recyclerview is hanging can you please tell me what is the possible problem or instead of resize what should i use ?? – aman verma Oct 16 '15 at 20:57
  • @amanverma it would probably be best to create a new question for your problem as it doesn't specifically pertain to this question. – James McCracken Oct 16 '15 at 21:01
  • my question is here if you know the answer then please post http://stackoverflow.com/questions/33179102/load-images-for-different-screen-sizes-from-the-server-using-picasso/33179164#33179164 – aman verma Oct 16 '15 at 21:02
4

I'd suggest adding an argument to the URL you use to request the image (e.g. &size=small) and having your server keep multiple sizes of each image around. Some benefits to this are:

  1. Less data usage from the app
  2. Better image quality (not having to resample the iamge)
  3. Better performance (large bitmaps take much longer to load)

The downsides are:

  1. More complexity (logic for determining which size to request)
  2. More storage requirements on your server

I think the pros far outweigh the cons, and you'll end up with a much better user experience.

For your followup, you could conceivably even have arguments for each density (e.g. &density=mdpi) which would serve the appropriate size (and you could just switch on getResources().getDisplayMetrics().densityDpi to get the argument value).

For example:

int densityDpi = getResources().getDisplayMetrics().densityDpi;
if (densityDpi <= DisplayMetrics.DENSITY_MEDIUM) {
    return "mdpi";
} else if (densityDpi <= DisplayMetrics.DENSITY_HIGH) {
    return "hdpi";
} else if (densityDpi <= DisplayMetrics.DENSITY_XHIGH) {
    return "xhdpi";
} else if (densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
    return "xxhdpi";
} else {
    return "xxxhdpi";
}
Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274