Reputation: 14571
I have an AsyncTask which launches a DatabaseHelper class from DoinBackground which copies an SQLite database from the /assets directory to the application directory (/databases).
In preExecute(), I startup a progressDialog. As pieces of the DB helper class complete, the DoinBackground process updates the progressDialog.
When the device is rotated, as I understand it, I need to dismiss the dialog box, cancel the AsyncTask, and then restart both again in onResume(), after the rotation is finished.
The first problem is when I call AsyncTask.cancel(), my onCancel() event fires, but the AsyncTask keeps running. I know because LogCat is showing output from my DB Helper long after the rotation is finished. The UI is usable after the rotation because the progressDialog is gone but the DB still appears to be mucking around copying so this is no good.
One piece of info I googled said you can't cancel an AsyncTask, so do I just let the thing run in the background on not worry about it? Is there a way to hook up the (still executing) DoinBackground process to the progressDialog again?
Thanks
package com.test.helloasync;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
public class HelloAsync extends Activity
{
myVars v;
protected fileCopyTask fct;
private final String TAG = "** HelloAsync **";
private final String DBNAME = "foo.png"; // need png extension: Data exceeds UNCOMPRESS_DATA_MAX (5242880 vs 1048576)
@Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.main);
restoreFromObject ();
}
/* (non-Javadoc)
* @see android.app.Activity#onResume()
*/
@Override
protected void onResume ()
{
// TODO Auto-generated method stub
Log.d (TAG, "onResume()");
// only start NEW task if not cancelled and file has not been copied yet
if ((v.taskCancelled == false) && (v.fileCopied == false))
{
fct = new fileCopyTask ();
fct.execute ();
}
// show progressdialog if we were cancelled becuase asynctask will continue by itself
if ((v.taskCancelled == true) && (v.fileCopied == false))
{
Log.d (TAG, "onAttachedToWindow(): launching fct");
v.pd.show ();
// may need to check status here to make sure it was cancelled and not something else...
Log.d (TAG, "fct cancel status is " + fct.isCancelled ());
}
super.onResume ();
}
/**
* saves myVars class during rotation
*/
@Override
public Object onRetainNonConfigurationInstance ()
{
Log.d (TAG, "onRetainNonConfigurationInstance(): saving myVars objects");
// close db transfer dialogs if showing so we don't cause a UI error
if (v.pd != null)
if (v.pd.isShowing ())
{
v.taskCancelled = true;
v.pd.cancel ();
}
// save task to myVars so we can use it onRestoreInstanceState
v.fct = fct;
return (v);
}
/*
* restores myVars class after rotation
*/
private void restoreFromObject ()
{
Log.d (TAG, "restoreFromObject(): retrieving myVars object");
v = (myVars) getLastNonConfigurationInstance ();
// initialize myVars object (v) first time program starts
if (v == null)
v = new myVars ();
else
{
Log.d (TAG, "myVars already initialized");
fct = (fileCopyTask) v.fct;
}
}
/**
*
* copy a file from /assets to /data/data/com.blah.blah/databases
*
*/
private class fileCopyTask extends AsyncTask<Integer, Void, Void>
{
// on UI thread here
protected void onPreExecute ()
{
Log.d (TAG, "onPreExecute()" );
// only show this when db has not been copied
// set initDbDone to false prir to call if downloading a new DB
v.pd = new ProgressDialog (HelloAsync.this);
v.pd.setProgressStyle (ProgressDialog.STYLE_HORIZONTAL);
v.pd.setMessage ("Initializing Database");
v.pd.show ();
}
/**
* opens file in assets/ directory and counts the bytes in it so we can have an actual progress bar
*
* @return size
*/
private int getAssetSize ()
{
int size = 0;
try
{
InputStream fin = getBaseContext ().getAssets ().open (DBNAME);
byte [] buffer = new byte [1024];
int length = 0;
while ((length = fin.read (buffer)) > 0)
size += length;
fin.close ();
}
catch (IOException ioe)
{
Log.d (TAG, "fileCopyTask(): asset size failed: ioex :" + ioe);
size = 0;
}
Log.d (TAG, " fileCopyTask(): asset size is " + size);
return (size);
}
@Override
protected Void doInBackground (Integer... params)
{
Log.d (TAG, "doInBackground: +++++++++++++++++++++++++++++++++");
try
{
int inputsize = getAssetSize ();
// this is the input file in the assets directory. We have no idea how big it is.
InputStream fin = getBaseContext ().getAssets ().open (DBNAME);
// this is the destination database file on the android device
File dbFile = getBaseContext ().getDatabasePath (DBNAME);
// check if destination directory exists
String parent = dbFile.getParent ();
// create the desitination directory if it does not exist in /data/data/blah.. (on device)
if (dbFile.exists () == false)
{
boolean result = new File (parent).mkdir ();
Log.d (TAG, " fileCopyTask(): mkdir() result is " + result);
}
// this is the output file in the databases/ subdirectory of the app directory (on device)
OutputStream fout = new FileOutputStream (dbFile);
// transfer bytes from the inputfile to the outputfile
byte [] buffer = new byte [4096];
int length;
int bytesCopied = 0;
while ((length = fin.read (buffer)) > 0)
{
fout.write (buffer, 0, length);
bytesCopied += length;
float b = (float) (bytesCopied * 1.0);
float i = (float) (inputsize * 1.0);
float pctdone = (float) ((b / i) * 100.0);
int pct = (int) (pctdone);
// updating every 4k seems to really slow things down so do it every 5 chunks
if ((pct % 5) == 0)
v.pd.setProgress ((int) pctdone);
}
fout.flush ();
fin.close ();
fout.close ();
}
catch (IOException e)
{
Log.d (TAG, "fileCopyTask(): DB copy blew up. IOE: " + e.getMessage ());
// just in case, attempt to cancel the process in event of cataclysm
this.cancel (true);
e.printStackTrace ();
}
return null;
}
// can use UI thread here
protected void onPostExecute (final Void unused)
{
Log.d (TAG, "fileCopyTask():onPostExecute()");
// set progress bar to 100% when done.
v.pd.setProgress (100);
v.pd.dismiss ();
// set the state flags to show a succesful completion
v.taskCancelled = false;
v.fileCopied = true;
}// onPostExecute()
}// fileCopyTask()
}
///////////////////////////////////////
// myVars.java
//////////////////////////////////////
/**
*
*/
package com.test.helloasync;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.ProgressDialog;
import android.content.DialogInterface;
public class myVars
{
ProgressDialog pd;
boolean taskCancelled = false;
boolean fileCopied = false;
Object fct;
}
Upvotes: 3
Views: 2159
Reputation: 14571
I tried cancelling the asynctask, but had trouble when I would create a new instance and execute it. The previous AsyncTask seemed to survive across the rotation and would begin updating the progressDialog as soon as the NEW task was fired-off.
Thought I'd just let android manage the AsyncTask itself since it seems to want to keep it running in the background. So, what I ended up doing was just closing the progressDialog on the rotation, and then popping it up again in onResume(). The AsyncTask seems to just pick up where it left off.
I'll update the code above - hope it helps someone else.
Upvotes: 1
Reputation: 8176
Typically you'd set a flag (e.g. boolean mIsRunning;
) that your doInBackground()
method checks periodically. If it's cleared, stop processing. When you wish to cancel the task, set the flag to false.
Upvotes: 0