3

I have a class, DownloadAndSave that extends from AsyncTask. In its doInBackground method, it retrieves data from an http connection and saves the data using OrmLite, but also cleans the old entries from the database. So, something like this:

doInBackground()
{
  clearDb();
  dataList = fetchDataFromHttp();
  saveToDb(dataList);
}

I frequently get a DB exception:

attempt to re-open an already-closed object:SQLiteDatabase

in the clearDb() and saveToDb() functions.

And this is bad since old data from the previous call of DownloadAndSave is mixed with the new data from DownloadAndSave.

In my opinion, I need to make sure that when I start a thread, all of the other treads from the DownloadAndSave class have finished, or in other words I need to run at most one instance of DownloadAndSave at a time. So the question is: how do I make sure that only one instance of DownloadAndSave will run in any point of time?

Gray
  • 7,050
  • 2
  • 29
  • 52
user1796624
  • 3,665
  • 7
  • 38
  • 65

4 Answers4

3

Option 1. Move above:

clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);

in a separate class that synchronizes against the class object:

public class WorkerClass {
    private WorkerListener workerListener;

    public static interface WorkerListener {
        public void publishWorkProgress(String data);
    }

    public WorkerClass(WorkerListener workerListener) {
        this.workerListener = workerListener;
    }

    public void performWork() {
        synchronized (WorkerClass.class) {
             clearDb();
             publish("Cleared DB");
             dataList = fetchDataFromHttp();
             publish("Got http data");
             saveToDb(dataList);
             publish("There! saved!");
        }
    }

    private void publish(String message) {
        if(workerListener != null) {
            workerListener.publishWorkProgress(message);
        }
    }

}

While from your activity:

public class SampleActivity extends Activity {

    public void doTheThing() {
        new MyAsyncTask().execute();
    }

    private static class MyAsyncTask extends AsyncTask<Void, String, Void> implements WorkerListener {
        @Override
        protected Void doInBackground(Void... params) {
            new WorkerClass(this).performWork();
            return null;
        }

        @Override
        public void publishWorkProgress(String data) {
            publishProgress(data);
        }
    }
}

Option 2: Move above code to an IntentService:

public class WorkerIntentService extends IntentService {
   public WorkerIntentService() {
       super(null);
   }

   @Override
    protected void onHandleIntent(Intent intent) {
       clearDb();
       dataList = fetchDataFromHttp();
       saveToDb(dataList);
    }
}

Using an IntentService guarantees that tasks are executed serially.

gunar
  • 14,660
  • 7
  • 56
  • 87
  • Thanks for the comment, about method one, I have tried but the problem is that I cant use publish progress while the code is in synchronized block, and I need the publish progress to show the progress of the thread, about the ormLite exception Im still haven't try it, but Ill write about the result once Ill have it, Thanks again – user1796624 Oct 08 '13 at 12:15
  • Add a callback interface in WorkerClass and implement that in publishProgress ... performWork is doing work in a background thread, while publishProgress is doing its stuff in UI thread. I'll update the answer shortly – gunar Oct 08 '13 at 12:16
  • closed database doesn't mean concurrent write operations. so synchronization doesn't make sense – DArkO Oct 08 '13 at 12:17
  • It makes sense if the app starts at least two jobs. The sync part is to cover this need: **`all of the other treads from the DownloadAndSave class have finished, or in other words I need to run at most one instance of DownloadAndSave at a time.`** – gunar Oct 08 '13 at 12:24
  • `attempt to re-open an already-closed object:SQLiteDatabase` - doesn't that suggest you're re-using the same `SQLiteOpenHelper` object? – gunar Oct 08 '13 at 12:27
  • @gunar from what I read online, is suggest that there is more than one instance of same helper. But I doubt in this since im using a single tone to access ta database and I close it when saveDb() and clearDb() are over – user1796624 Oct 08 '13 at 13:14
2

Since API version 11 (HONEYCOMB) of the Android API, an AsyncTask can be executed on a given Executor. You can use the default SerialExecutor to execute tasks sequentially.

stan0
  • 11,549
  • 6
  • 42
  • 59
0

If you use db operation in doInBackground, you should be locked db for one thread.

public void insertToDb(){
 SQliteDatabase db;
 ...
 db.beginTransaction();
 ...//operation
 db.yieldIfContendedSafely();
 db.setTransactionSuccessful();
 db.endTransaction();
}
nurisezgin
  • 1,530
  • 12
  • 19
0

I believe that the issue you are facing is that you are starting the AsyncTask from an activity. Your activity is extending ORMLiteBaseActivity which opens the helper (and with that the database) onCreate and closes it onDestroy. When you exit the activity and the background task still hasn't finished then when trying to do write to the database you end up with a closed DB.

ORMLite handles synchronizations internally and i have never needed to do synchronized blocks with it. I use it in all my projects that require a database.

Also for the other answers, the error is a closed database and not concurrent write operations, so synchronization doesn't make sense.

DArkO
  • 15,880
  • 12
  • 60
  • 88
  • Thanks for the comment, actually I'm not extending ORMLiteBaseAchatic DatabaseHelper dbHelper, and inside the clearDb() and saveToDb() i do DatabaseHelper.getHelper().getDao() in a try(),catch(),finally{} and in finally{} I destroy using OpenHelperManager.releaseHelper(); – user1796624 Oct 08 '13 at 12:54