4

I've just released an Android app that parses a local file and make some process with the data. Some days ago one of my customers has reported me an error, each time he tries to process his file the app crashes.

This is the error log he sent me:

java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:300)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.NullPointerException: lock == null
    at java.io.Reader.<init>(Reader.java:64)
    at java.io.InputStreamReader.<init>(InputStreamReader.java:120)

And the code related to this is this:

// EDIT 1: Following greenapps comment I will put a more realistic version of this 
// method (The first version was invented because I wanted to be breaf)
public void selectFile()
{
    List<File> files = getDocsToParse();
    this.listview.setAdapter(this.myadapter);
    this.listview.setOnItemClickListener(new OnItemClickListener()
    {
        ...
        @Override
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
        parseFile(files.get(position));
                }
        ...
    }
    this.myadapter.addFiles(files);
}

public static List<File> getDocsToParse() {
    File sdcard = Environment.getExternalStorageDirectory();
    File subdir = new File(sdcard, "MyAppFolder/Files/");
    // EDIT 2: I'm using  subdir.mkdirs(); because I want to 
    // create MyAppFolder/Files/ folders the first time the user use the app. 
    // Is this not correct? Should I create these folders any other way? 
    if (!subdir.exists()) {
        subdir.mkdirs();
    }
    File files[] = subdir.listFiles();
    List<File> filterFiles = new ArrayList<File>();
    for (int i = 0; i < files.length; i++) {
        File file = files[i];
        filterFiles.add(file);
    }
    return filterFiles;
}

public void parseFile(File fileToParse)
{
    long totalSize = 0;
    InputStream is = null;
    try {
            totalSize = fileToParse.length();
            is = new BufferedInputStream(new FileInputStream(fileToParse));
    } catch (IOException e) {
        e.printStackTrace();
    }
    BufferedReader reader = null;
    reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
    String line = "";
    StringTokenizer st = null;
    try {
        while ((line = reader.readLine()) != null) {
           // Here I parse the file
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

The line that crashes is this one:

reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));

I understand that this is because "is" is null and I should have to catch this case so avoiding the app to crash (I will fix this in my next version).

EDIT 3: You are right, greenapps, I will check if is is null before using it and in other case I will not use it.

And I understand that "is" is null because there is a IOException doing this:

 totalSize = fileToParse.length();
    is = new BufferedInputStream(new FileInputStream(fileToParse));

EDIT 4: Of course, if this gives me a IOEXception I will have to change my code to make a complete different thing.

And I can't find on Internet the reasons so these 2 lines of code could throw a IOException.

In think the fileToParse is ok, or at least is not null, because I pass this "files" list to an adapter to show their files names with files.get(i).getName() and the name is shown properly.

I have to add that the file to process is very large and has sensitive personal data so the user can't send it to me so I can test with it.

None of my files give me that error and without the problematic file it's very difficult to me to trace the problem so I have to guess. Any idea of what reasons could cause this error?

Thank you very much and kind regards!

EDIT 5: Following ctarabusi suggestion I've changed my parseFile method to this:

public void parseFile(File fileToParse)
{
    long totalSize = 0;
    InputStream is = null;
    try {
            totalSize = fileToParse.length();
            is = new BufferedInputStream(new FileInputStream(fileToParse));
            BufferedReader reader = null;
            reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
            String line = "";
            StringTokenizer st = null;
            while ((line = reader.readLine()) != null) {
               // Here I parse the file
            }
    } catch (IOException e) {
          Toast.makeText(getApplicationContext(), "Error parsing file", Toast.LENGTH_LONG).show();
        e.printStackTrace();
    } finally {
        if(is != null)
        {
            try
            {
                is.close();
            }
            catch (IOException e)
            {
                Log.e("", e.getMessage(), e);
            }
        }
    }
}

My tests are working properly again, but the user has told me that he sees the "Error parsing file" message, so it's failing yet.

What else could I check?

Wonton
  • 1,033
  • 16
  • 33
  • Do you specify the "READ_EXTERNAL_STORAGE" permission inside your manifest? If your client uses a phone with API 19+ and you do not use "WRITE_EXTERNAL_STORAGE", this option is needed. In addition, you can try to set breakpoints at your assignment lines and chech whether your variables have the expected values. – localhorst Nov 06 '15 at 16:02
  • Hi! I have WRITE_EXTERNAL_STORAGE permission in my Android manifest, so I guess this is ok. – Wonton Nov 06 '15 at 17:26
  • 'understand that this is because "is" is null and I should have to catch this case so avoiding the app to crash'. No you should check if is is null before using it. And not use it if it is. – greenapps Nov 07 '15 at 08:50
  • 'if (!subdir.exists()) { subdir.mkdirs(); }'. Bad code. That should be: if (!subdir.exists()) { if (!subdir.mkdirs()) return null;} But a mkdirs on this place is not logical as you will go and read files. Mkdirs is only needed if you want to create a file. – greenapps Nov 07 '15 at 08:52
  • 'And I understand that "is" is null because there is a IOException doing this:'. If you have an IOException there then you should return from there. Now your code runs further which is not logical of course. – greenapps Nov 07 '15 at 09:01
  • 'public void selectFile(int selected)'. Impossible function. You already give the index for a list that has to be determined yet. And the list can be null -if you follow my suggestion- so check for null before you call parseFile() – greenapps Nov 07 '15 at 09:07

1 Answers1

0

It could be that your problem is that you are not closing your input streams. File descriptors and Streams are limited resources and it is important to release them when you are done with them.

Usually is done in a finally block like:

InputStream inputStream = null;
try 
{
    ... use your input stream
}
catch (IOException e)
{
    Log.e(TAG, e.getMessage(), e);
}
finally
{
    if (inputStream != null)
    {
        try
        {
            inputStream.close();
        }
        catch (IOException e)
        {
            Log.e(TAG, e.getMessage(), e);
        }
    }
}
ctarabusi
  • 343
  • 1
  • 11
  • This could be the problem because I'm not closing that inputstream. I will try this and let you know. Anyway, not closing that inputstream could be a problem even when no other inputstream have been opened before in that app? – Wonton Nov 06 '15 at 17:29