1

I'm using the following method to download images from a server to serve in an Android application.

public static void downloadToFile(File file, URL url, int connectTimeout, 
        int readTimeout) throws IOException {
    InputStream in = null;
    OutputStream out = null;
    try {
        URLConnection ucon = url.openConnection();
        ucon.setConnectTimeout(connectTimeout);
        ucon.setReadTimeout(readTimeout);

        in = ucon.getInputStream();
        out = new FileOutputStream(file);
        byte[] buffer = new byte[BUFFER_SIZE];
        int count = 0;
        while ((count = in.read(buffer)) != -1) {
            out.write(buffer, 0, count);
        }
        out.flush();
    } finally {
        if (in != null) {
            in.close();
        }
        if (out != null) {
            out.close();
        }
    }
}

When a file has been deleted from the server but I try to download it anyway, I receive a FileNotFoundException, which is perfectly valid. In my application, the call to this method is wrapped in a try catch handling both IOException and Exception so I can just continue on.

The problem is that in the Android Developer Console I've received a few crash reports and the FileNotFoundException seems to be ignoring the catch blocks and force closing the app.

java.io.FileNotFoundException: http://foo.org/1234.jpg
at   org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:1162)
at com.package.IOUtil.downloadToFile(Unknown Source)

The Unknown Source is because of Proguard but I can force the exception when testing by putting in non-existant file URLS which does throw the exception. When I test on my phone (SGS2 2.3.3) and tablet (TF101 3.2) the try catch block does catch the exception. The stack trace below points to the URL#getInputStream() as what is throwing the exception but that doesn't explain why it isn't being caught for some (only a few).

java.io.FileNotFoundException: http://foo.org/1234.jpg
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:521)
at com.package.IOUtil.downloadToFile(IOUtil.java:142)

I've also had the same problem with a SocketException from the same method/callee not being caught.

java.net.SocketTimeoutException: Connection timed out
at org.apache.harmony.luni.platform.OSNetworkSystem.connect(Native Method)
at dalvik.system.BlockGuard$WrappedNetworkSystem.connect(BlockGuard.java:357)
at org.apache.harmony.luni.net.PlainSocketImpl.connect(PlainSocketImpl.java:204)
at org.apache.harmony.luni.net.PlainSocketImpl.connect(PlainSocketImpl.java:437)
at java.net.Socket.connect(Socket.java:1002)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:75)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:48)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection$Address.connect(HttpConnection.java:322)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnectionPool.get(HttpConnectionPool.java:89)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getHttpConnection(HttpURLConnectionImpl.java:285)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.makeConnection(HttpURLConnectionImpl.java:267)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.retrieveResponse(HttpURLConnectionImpl.java:1018)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:512)
at com.package.IOUtil.downloadToFile(Unknown Source)

The downloading is done in a background thread if that matters.

Am I missing something? Could it be phone specific, or Proguard (even though it's caught by almost everybody else)?

Edit: Code that catches

private void download(List<URL> urls, int attempts) {
    List<URL> failedDownloads = new ArrayList<URL>();
    for (URL url : urls) {
        if (isInterrupted()) {
            return;
        }

        String imageName = Util.getImageName(url);
        File cacheFile = cache.getCacheFile(imageName);
        try {
            // Bad downloads often don't throw an IOException but
            // leave the file with a length of 0.
            if (cacheFile.length() == 0) {
                Util.safeDelete(cacheFile);
            }

            if (!cacheFile.exists()) {
                IOUtil.downloadToFile(cacheFile, url);

                if (cacheFile.length() == 0) {
                    //See above
                    Util.safeDelete(cacheFile);
                    failedDownloads.add(url);
                } else {
                    if (putToCache) {
                        cache.get(imageName);
                    }
                    handler.sendEmptyMessage(0);
                }
            } else {
                // Exists already, move it to cache
                if (putToCache) {
                    cache.get(imageName);
                }
            }
        } catch (IOException ioe) {
            Util.safeDelete(cacheFile);
            failedDownloads.add(url);
        } catch (Exception e) {
            // Continue on to next download - don't bother trying this
            // URL again.
        }
    }

    if (!failedDownloads.isEmpty() && attempts < maxAttempts) {
        attempts++;
        download(failedDownloads, attempts);
    }
}

Util.safeDelete is just a delete wrapped in a try catch so that shouldn't be related.

daniel
  • 11
  • 3

4 Answers4

1

Firstly, don't catch and squash Exception. Don't do it. Not ever. If something unexpected goes wrong, you are throwing away all of the evidence.

Secondly, recompile your code with debug information included, and you'll get a file name and line number in the stacktrace for your methods. Get rid of Proguard while you are developing / testing. (Preferably, get rid of it entirely because it is not giving you any real protection.)

Finally, the download method (as you've copied it) cannot possibly result in an uncaught IOException. If it did:

  • you clearly DO catch the exception, and
  • there would be a compilation error for that method because it doesn't declare IOException, and
  • the download method would be part of the stack trace, and

Therefore it must be something like ...

  • you've declared and imported another IOException class into that file, or
  • the culprit is your safeDelete method, or
  • the download method is overloaded and the exception is happening in a different overload, or
  • you are calling downloadToFile somewhere outside of download, or
  • the code you copied is not accurate, or
  • you've made a mistake in compiling / deploying the code; e.g. forgotten to recompile after editing.
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • the IOException is from the url.getInputStream(). I only added Exception to the try catch as a test because IOException wasn't being caught – daniel Sep 08 '11 at 10:12
  • @daniel - well get rid of it. The IOException **clearly** is not coming from there. And the catch of Exception might be hiding the real problem. – Stephen C Sep 08 '11 at 10:32
  • java.io.IOException. safeDelete is just delete but catching SecurityException. download is unique, downloadToFile has overloads but the one posted is correct. downloadToFile is specified to that specific thread type higher up in the stacktrace. There's been 6 reports on 4 different versions over 2 months. This doesn't even seem possible, I'll update the proguard like you suggested and do another release to see if I can get some more information. – daniel Sep 08 '11 at 11:01
0

Solution (in my case)

In case when server response code is >= HTTP_BAD_REQUEST (greater than 400) method getInputStream(), of class HttpURLConnectionImpl throws FileNotFoundException (so you cannot open input stream).

Even if this file exist, your object will not give you input stream, because of server response code is >=400 - change response code on server or use annother class to connect.

09-07 16:08:08.749: WARN/System.err(360): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:1162)

Fragment of source code: http://www.docjar.com/html/api/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java.html

  867       @Override
  868       public InputStream getInputStream() throws IOException {
  869           if (!doInput) {
  870               throw new ProtocolException(Messages.getString("luni.28")); //$NON-NLS-1$
  871           }
  872   
  873           // connect before sending requests
  874           connect();
  875           doRequest();
  876   
  ...
  883           if (responseCode >= HTTP_BAD_REQUEST) {
  884               throw new FileNotFoundException(url.toString());
  885           }
  886   
  887           return uis;
  888       }
Avadhani Y
  • 7,566
  • 19
  • 63
  • 90
fider
  • 1,976
  • 26
  • 29
0

The downloading is done in a background thread if that matters.

Yeah this matters. The exception is thrown in the background thread and effectively disappears in to thin air.

I think the solution can be found here: How to throw a checked exception from a java thread? The answer involving putting your background thread in a Callable might be the best approach

cheers

Community
  • 1
  • 1
Steve
  • 363
  • 2
  • 11
  • Can you explain this please? I thought the way it would work right now is: -downloadToFile fails and throws IOException -caller deletes whatever was downloaded before IOException occurred and adds to a list of failed downloads which reattempt after. It would be better if it did just disappear into thin air instead of not being caught and causing a force close – daniel Sep 08 '11 at 10:15
  • Sure, should have used a better choice of words. The exception is thrown by the background thread so can't be caught by your catch blocks as they belong to the parent thread. – Steve Sep 08 '11 at 10:50
  • Sorry, that was bad explaining on my part, download is called from run in the thread. – daniel Sep 08 '11 at 11:03
0

It is wierd that the catch exception is not catching the FileNotFoundException. Looks like you have already caught exception in download method.

You could try catching the exception in downloadToFile and see what happens. See if its a bug somewhere else...

Sid Malani
  • 2,078
  • 1
  • 13
  • 13