Universal Image loader fails to load images sometimes

Datenshi picture Datenshi · May 17, 2013 · Viewed 10.7k times · Source

I'm using universal image loader and I get quite large numbers of images failing to load for users every day. I'm using this code to get my errors to analytics.

public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
  try
  {
    String fail = failReason.getType().toString();
    String fail4 = failReason.getCause().toString();
    String sum = fail + " " + fail4;
    EasyTracker.getTracker().sendException(sum, false);
  }
  catch (Exception e)
  {
    EasyTracker.getTracker().sendException(e.getMessage(), false);
  }
}

Most of the time it catches exception, as getType, or getCause is null. This issue is seen on devices with 2.1-2.3 android versions, but there are some reports from newer version like 4.0.4 or even 4.2.2. So I can't really tell what caused the image failing to load

Another issue is IO_ERROR java.io.EOFException, which is mostly seen on newer android versions.

Third of most common errors are out_of_memory errors... The images I am trying to load are no bigger than 1mb, but I need to have ScaleType.Exactly, but while loading larger images I am not caching them in memory or disc, to reduce possibility of out_of_memory, but it still does occur pretty often.

My configuration:

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(xxx.getApplicationContext())
.threadPoolSize(4)
.memoryCache(new WeakMemoryCache())
.imageDownloader(new BaseImageDownloader(xxx.getApplicationContext(),10 * 1000, 30 * 1000)) 
.build();

if(!ImageLoader.getInstance().isInited())
            ImageLoader.getInstance().init(config);

// options is used for images smaller in size (5kb-150kb)
options = new DisplayImageOptions.Builder()
.cacheInMemory()
.cacheOnDisc()
.showStubImage(R.drawable.stub)
.showImageOnFail(R.drawable.failed)
.imageScaleType(ImageScaleType.EXACTLY)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();

// options2 is used for images big in size (300kb-1,2mb)
options2 = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.stub)
.showImageOnFail(R.drawable.failed)
.imageScaleType(ImageScaleType.NONE) // NONE because I need to have full size bitmap loaded
.bitmapConfig(Bitmap.Config.RGB_565)
.build();

Could anyone tell me how could I optimize my imageLoading to get less failed to load images? Because I feel like I am loosing users some because of these constant failings to load images.

Update As I changed the code as nostra suggested on onLoadingFailed, I'm now seeing that all the reports which does not have .getCause() are "DECODING_ERROR" and all these are reported by android 2.2-2.3.6 versions, none from newer ones. Yet still large portion of my users are on older androids, any idea how to reduce this decoding_error? I checked app myself on older androids, and images do load most of the time, but DECODING_ERROR is reported most often on analytics. Second on most popular reasons is still the same IO_ERROR java.io.EOFException

Update 2

Customized Downloader as nostra suggested, reduced threadPoolSize to 3 set up an extra loading - if loading failed try to load again for one time before giving up. I see failings to load decreased about 30%. But still do occur - 100 decoding errors (exclusively only on 2.2-2.3.6 versions) , and 160 EOF errors (4.0 and up) in 3 days from 500 daily active users.

Update 3

Latest updated version gets far less decoding errors and EOFExceptions, I think mainly because I try to reload the same image if it fails to load the first time. But.. I am now face another issue: No space left on device java.io.IOException: write failed: ENOSPC (No space left on device). I am using LimitedDiscCache.

Answer

nostra13 picture nostra13 · May 20, 2013

failReason.getType() can't be null but failReason.getCause() can. You should check it to prevent NPE.

failReason.getCause() can be null if you deny network by ImageLoader.denyNetworkDownloads(true) or if decoding error occurred. This is because Android can't decode image by some reason.

BTW I recommend you to use .cacheOnDisc() even for big images (options2). And maybe try other memory cache implementation? E.g. LruMemoryCache.

I don't know the reason of java.io.EOFException but can you detect which network is used in that time? Mobile or WiFi? Maybe try use ImageLoader.handleSlowNetwork(boolean) to switch between network types.

UPD: Also try to reduce thread pool size. Maybe it helps to prevent DECODING ERRORS. UPD2: Decoding error can be caused by web site redirect to web page. You can try extend BaseImageDownloader and add empty string for "User-Agent" header in request.

public class MyImageDownloader extends BaseImageDownloader {

private static final int MAX_REDIRECT_COUNT = 5;

public MyImageDownloader(Context context) {
    super(context);
}

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
    HttpURLConnection conn = connectTo(imageUri);

    int redirectCount = 0;
    while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
        conn = connectTo(conn.getHeaderField("Location"));
        redirectCount++;
    }

    return new BufferedInputStream(conn.getInputStream(), BUFFER_SIZE);
}

protected HttpURLConnection connectTo(String url) throws IOException {
    String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
    HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
    conn.setConnectTimeout(connectTimeout);
    conn.setReadTimeout(readTimeout);
    conn.setRequestProperty("User-Agent", "");
    return conn;
}
}

Or since UIL 1.8.5:

public class MyImageDownloader extends BaseImageDownloader {
    @Override
    protected HttpURLConnection createConnection(String url) throws IOException {
        HttpURLConnection conn = super.createConnection(url);
        conn.setRequestProperty("User-Agent", "");
        return conn;
    }
}