Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

My Android app is damaging SD Cards

Tags:

android

I released an app on the Android market that I've since had to take down because approximately half of the comments were people complaining of damaged SD cards. I've gone over the code a few times and can't find anything that could damage an SD card. All that happens that involves external storage is the saving of streams as images, which then get read into ImageView's.

This is what is called in the root activity to create the folders. The directory paths are stored in public static variables.

//Get the SD Card directory
    String external = Environment.getExternalStorageDirectory().getAbsolutePath() + "/appfolder/";

    CACHE_DIRECTORY = external + ".cache/";
    SAVED_DIRECTORY = external + "saved/";

    File cache = new File(CACHE_DIRECTORY);
    File saved = new File(SAVED_DIRECTORY);
    cache.mkdirs();
    saved.mkdirs();

And the following is the code for downloading images and copying them (for when they are being moved to the saved directory).

public static void saveImage(File file, URL url) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(url.openStream());
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
    int bytes;
    while ((bytes = bis.read()) != -1) {
        bos.write(bytes);
    }
    bos.close();
    bis.close();
}

public static void copy(File fileIn, File fileOut) throws IOException {
    BufferedInputStream bin = new BufferedInputStream(new FileInputStream(fileIn));
    BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(fileOut));
    int bytes;
    while ((bytes = bin.read()) != -1) {
        bout.write(bytes);
    }
    bin.close();
    bout.close();
}

And this is a background thread for network I/O

public void run() {
    for (String url : thumbnails) {
        if (url != null) {
            String[] urlParts = url.split("/");
            String imageName = urlParts[urlParts.length - 1];
            File file = new File(Main.CACHE_DIRECTORY + imageName);
            if (!file.exists() || file.length() == 0) {
                try {
                    Image.saveImage(file, new URL(url));
                } catch (IOException e) {}
            }
        actx.runOnUiThread(reload);
        }
    }
}

Where reload is a runnable to update an adapter, thumbnails is an array of string urls and image name is a unique 10-11 digit number with an image extension (.jpeg, .png, .gif specifically).

And this is similar code run in the background of an asynctask.

String imageUrl = (String)params[0];
    String[] imageUrlParts = imageUrl.split("/");
    String imageName = imageUrlParts[imageUrlParts.length - 1];
    URL fullImageUrl;
    try {
        fullImageUrl = new URL(imageUrl);
    } catch (MalformedURLException me) {
        cancel(true);
        return null;
    }

    File file = new File(Main.CACHE_DIRECTORY + imageName);
    try {
        URLConnection ucon = fullImageUrl.openConnection();
        int requestedSize = ucon.getContentLength();
        long fileSize = file.length();
        //Either the file does not exist, or it exists but was cancelled early due to
        //User or IOException, so it needs to be redownloaded
        if (!file.exists() || ((file.exists()) && fileSize < (requestedSize * 0.8))) {
            mLoad.setMax(requestedSize);
            BufferedInputStream bis = new BufferedInputStream(ucon.getInputStream());
            BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(file));
            int bytes;
            int count = 0;
            while ((bytes = bis.read()) != -1) {
                bout.write(bytes);
                count++;
                //Updates in increments of 2kb
                if (count % 2048 == 0) {
                    publishProgress(count);
                }
            }
            bis.close();
            bout.close();
        }

        if (save) {
            File saveFile = new File(Main.SAVED_DIRECTORY + imageName);
            copy(file, saveFile);
        }
    } catch (IOException e) {
        cancel(true);
        return null;
    } catch (OutOfMemoryError e) {
        cancel(true);
        return null;
    }

The only instance I could find of damaged SD cards is http://code.google.com/p/android/issues/detail?id=2500

The app is built on Android 1.6 and the bug isn't recreatable via emulator or personal testing on 2.1update1 with HTC Desire.

EDIT: I've looked at some other questions and could the problems be arising from me not flushing the buffered output streams? Is that a big deal?

like image 705
daniel Avatar asked Jul 20 '10 12:07

daniel


People also ask

Why does my phone keep corrupting my SD card?

Why Is My Android SD Card Corrupted? SD and MicroSD cards can sometimes trigger a corruption error message when they've been used on multiple types of devices or have been physically removed from a device while a file was being accessed or transferred.

How do I fix a corrupted SD card app?

You can use CHKDSK for Android by connecting your corrupted SD card and pointing the command towards it. The below instructions will show you how to repair an SD card in Android using CHKDSK. Right-click Start and click PowerShell (Admin). If prompted by UAC for confirmation, click Yes.


1 Answers

I see two things that might be related:

  • You should call .close() on the streams within a finally{ } block so they are closed in case there is an error or force close while writing.
  • Catching OutOfMemoryError is usually not a good idea. The VM has run out of memory and a lot of things will be in a bad unpredictable state, best to abort in those cases.

My bet is on the finally block, possibly related to an OutOfMemoryError happening and not aborting the app due to the catch, causing some error further down.

like image 99
Løkling Avatar answered Sep 25 '22 22:09

Løkling