David Kibblewhite
David Kibblewhite

Reputation: 132

AsyncTask is still blocking UI thread

I my main activity I want to autosave whenever the user leaves the app or changes to another activity. So I implemented the save function in the activity's OnPause method, which works fine. Problem is the save process can take a few seconds, so I wanted a progressdialog while it happens. So I implenmeted that with an ASyncTask like this:

@Override
public void onPause()
{
    super.onPause();

    // save sheet when user is leaving activity
    SaveTask task = new SaveTask(PlayCharacterActivity.this);

    task.execute();
}

private class SaveTask extends AsyncTask <Void, Void, Void> {
    private ProgressDialog dialog;

    public SaveTask(PlayCharacterActivity activity) {

    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        dialog = new ProgressDialog(PlayCharacterActivity.this);
        dialog.setMessage("Saving...");
        dialog.show();
        dialog.setIndeterminate(true);
        dialog.setCancelable(false);

    }

    @Override
    protected void onPostExecute(Void result) {
        if (dialog.isShowing()) {
            dialog.dismiss();
        }
        super.onPostExecute(result);
    }

    @Override
    protected Void doInBackground(Void... params) {
        try {
            //Log.d("plch:OnPause", "starting save");
            Sheet.getInstance().Save();
            //Log.d("plch:OnPause", "finished save");
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        return null;
    }

}

Problem is, the dialog doesn't appear until after the background task has completed. And I can see in logcat that the main thread is still being blocked. I believe it happens in the middle of the save function when it does the serialisation.

Save is like this:

public void Save()
{       
    long dtMili = System.currentTimeMillis();
    Date d = new Date(dtMili);
    CharSequence s  = DateFormat.format("hh:mm:ss", d.getTime());

    // use this flag to know whether to back up the saved file or not
    boolean saveSuccessful = false;

    //_xml = new String("");
    dtMili = System.currentTimeMillis();
    d = new Date(dtMili);
    Log.d("Load/Save", "started serialising: " + DateFormat.format("hh:mm:ss", d.getTime()));
    _xml = _instance.Serialise();
     dtMili = System.currentTimeMillis();
    d = new Date(dtMili);
    Log.d("Load/Save", "finished serialising: " + DateFormat.format("hh:mm:ss", d.getTime()));
    try
    {
        //---SD Card Storage---
        File sdCard = Environment.getExternalStorageDirectory();
        File directory = new File (sdCard.getAbsolutePath() + "/RPGenius");
        directory.mkdirs();
        File file = new File(directory, _name + ".rpg");
        FileOutputStream fOut = new FileOutputStream(file);
        Log.d("Saving to: ", file.getAbsolutePath());

        //---write the string to the file---
        OutputStreamWriter osw = new OutputStreamWriter(fOut);
        //DebugHelper.DebugMessage("File contents: " + _xml);

        dtMili = System.currentTimeMillis();
        d = new Date(dtMili);
        Log.d("Load/Save", "started writing file: " + DateFormat.format("hh:mm:ss", d.getTime()));

        osw.write(_xml);
        osw.flush();
        osw.close();

        dtMili = System.currentTimeMillis();
        d = new Date(dtMili);
        Log.d("Load/Save", "finished writing file: " + DateFormat.format("hh:mm:ss", d.getTime()));

        saveSuccessful = true;
    }
    catch (NullPointerException npe)
    {
        npe.printStackTrace();
    }
    catch (IOException ioe)
    {
        ioe.printStackTrace();
    }

    // if the save was completely successfully, back it up
    if (saveSuccessful)
    {
        File sdCard = Environment.getExternalStorageDirectory();
        File directory = new File (sdCard.getAbsolutePath() + "/RPGenius");
        File file = new File(directory, _name + ".rpg");

        if (file.exists())
        {
            // locate backup directory and create if not present
            File backupDirectory = new File (sdCard.getAbsolutePath() + "/RPGenius/Backups");
            backupDirectory.mkdirs();

            // create target file location/name
            File backupFile = new File(backupDirectory, _name + ".rpg");

            // attempt to copy file
            try {
                FileInputStream inStream = new FileInputStream(file);
                FileOutputStream outStream = new FileOutputStream(backupFile);
                FileChannel inChannel = inStream.getChannel();
                FileChannel outChannel = outStream.getChannel();
                inChannel.transferTo(0, inChannel.size(), outChannel);
                inStream.close();
                outStream.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }
}

Serialise is like this:

@Override
public String Serialise() {
    // produce xml string to represent this object
    _indents = 0;
    _xml = new String("");
    StartXmlItem("Sheet");
    //AddSimpleXmlItem("Name", _name);

    StartXmlItem("Stats");
    for (XmlSerialisable stat: _stats)
    {
        AddComplexXmlItem(stat);
    }
    EndXmlItem("Stats");

    StartXmlItem("Effects");
    for (XmlSerialisable effect: _effects)
    {
        AddComplexXmlItem(effect);
    }
    EndXmlItem("Effects");

    StartXmlItem("Pages");
    for (XmlSerialisable page: _pages)
    {
        AddComplexXmlItem(page);
    }
    EndXmlItem("Pages");

    EndXmlItem("Sheet");

    return _xml;
}

I'm not immediately interested in improvements to the save/serialise methods unless they are relevant to the progressdialog problem. Can anyone help?

Upvotes: 0

Views: 2600

Answers (2)

user1712200
user1712200

Reputation: 349

Try moving the dialog to the outer class and opening it outside the AsyncTask and by the static method

dialog = ProgressDialog.show(...)

just before

task.execute();

It should be possible to close it from onPostExecute

Upvotes: 0

donfuxx
donfuxx

Reputation: 11323

I propose the following change of your AsyncTask:

First of all remove the progressbar field and the useless constructor from the AsyncTask:

private ProgressDialog dialog; //should be declared as field in Activity

public SaveTask(PlayCharacterActivity activity) { //don't pass the activity to AsyncTask

}

Then in the onPreExecute() just do:

@Override
protected void onPreExecute() {
     showDialog(); //call a method in your Activity that shows your dialog as you want
}

Remember that AsyncTask's onPreExecute method is run in the UI thread so you can deal with views from the Activity here. See documentation: http://developer.android.com/reference/android/os/AsyncTask.html#onPreExecute()

Upvotes: 1

Related Questions