Many people said that SQLiteDatabase is thread-safe,but ,when i ran some simple tests with setup like this:
private class MyRunnable implements Runnable{
#Override
public void run() {
for(int i = 0 ;i < 100 ; i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Cursor cursor = database.query(true,"testdb",new String[]{"num"},"id=?",
new String[]{"1"},null,null,null,null);
cursor.moveToFirst();
int original = cursor.getInt(0);
ContentValues values = new ContentValues();
values.put("num",++original);
database.update("testdb",values,"id=?",new String[]{"1"});
}
}
}
MyRunnable runnable1 = new MyRunnable();
MyRunnable runnable2 = new MyRunnable();
new Thread(runnable1).start();
new Thread(runnable2).start();
In MyRunnable ,there is a loop that runs 100 times. Each time, the num field will add by 1 and the num's initial value is 0. we can see that the above code's expected result is 200,but i only get 197.
so does that mean SQLiteDatabase is not thread-safe?
No, the way you are handling the data is just not thread-safe. You could use an atomic SQL-query like:
UPDATE table SET num = num + 1
Instead of retrieving the data in your application code, modifying it and storing it back.
SQLite being threadsafe means that you can safely use all SQLite functions in multiple threads at once, it does not mean the database gets locked automatically depending on what you are doing. If your thread gets interrupted anywhere between the query() call and the update() call, returning to the thread at that point will mean the data you retrieved earlier is no longer up to date.
Related
I created a database with a table named flagTable, this table only has two fields, which are id(auto increment) and an integer field. Next, in my program, I have a button that will trigger a thread to start. When the thread is starting, it constantly retrieve data from database, and check for the for the value, if the value is equal to one then it will trigger another new Thread, something like this:
private class statusOfStrummingInAnotherDevice extends Thread {
int value;
public void run() {
try{
while(true){
try{
if(flagCursor == null){
flagCursor = cdb1.getFlagAll();
}
}catch(Exception e){break;}
try{
Log.i("MAIN3ACTIVITY","getting status");
int size = cdb1.getSize(flagCursor);
Log.i("MAIN3ACTIVITY","SIZE is" + String.valueOf(xyz));
for(int i = 0 ; i < size ; i++){
flagCursor.moveToPosition(i);
Log.i("MAIN3ACTIVITY","getting status jkasdfasdf");
value = cdb1.getFlag();
if(value == 1){
Log.i("FLAGCURSOR=====>>>>","Succesful");
releasingNotes = new ReleasingNotes(IntendedChord);
releasingNotes.start();
//break;
}
cdb1.updateFlag(0);
Log.i("FLAGCURSOR=====>>>>",String.valueOf(value));
}
flagCursor = null;
}catch(Exception e){break;}
Log.i("MAIN3ACTIVITY","thread is sleeping");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}catch(Exception e){
}
}
}
In the meantime, the data that were retrieved from the database is using this function:
public Cursor getFlagAll(){
return getReadableDatabase().rawQuery(
"SELECT _ID, flag from flagTable", null);
}
And, the data that were updated to the database through this method:
public int updateFlag(int i) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put("flag",i);
return db.update("flagTable" , contentValues , "_ID" + "= ?",new String[]{String.valueOf(1)});
}
Now, above codes will give no error, however, the data that were retrieved from the database is always 1, it keeps trigger a new function. In my above codes, I stated if the value is equal to 1, then the current thread will trigger a new thread to start, When its finished, the program will update the current data to 0. So that, the next round of the infinite loop can stop triggering new thread until a the conditon is met. What is problem overhere? did my codes really updated the new value? or I need to referesh the database every time I updated a new value.
Use Listeners to your database.
use SQLiteTransactionListener and do your things in onCommit()
Some guide in details here :
https://developer.android.com/reference/android/database/sqlite/SQLiteTransactionListener.html and
http://www.programcreek.com/java-api-examples/index.php?api=android.database.sqlite.SQLiteTransactionListener
I have the following class which is responsible to fetch non synced receipts from the receipts table and upload them to the server, the following function as of now just iterates through the cursor result set:
public class MidnightUpload {
public static void checkLocalAndUpload(final Context ctx) {
Cursor cursor = DatabaseHandler
.getInstance(ctx)
.getReadableDatabase()
.query(Receipt.TABLE_NAME, Receipt.FIELDS,
Receipt.WEB_RECEIPT_ID + " IS ?", new String[]{"dummy"},
null, null,
null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
Log.d("_id", cursor.getString(cursor.getColumnIndexOrThrow("_id")));
Log.d("receipt_id", cursor.getString(cursor.getColumnIndexOrThrow("receipt_id")));
Log.d("web_receipt_id", cursor.getString(cursor.getColumnIndexOrThrow("web_receipt_id")));
Log.d("receipt_name", cursor.getString(cursor.getColumnIndexOrThrow("receipt_name")));
// Log.d("image", cursor.getString(cursor.getColumnIndexOrThrow("image")));
Log.d("date_added", cursor.getString(cursor.getColumnIndexOrThrow("date_added")));
Log.d("status", cursor.getString(cursor.getColumnIndexOrThrow("status")));
Log.d("currency", cursor.getString(cursor.getColumnIndexOrThrow("currency")));
Log.d("category", cursor.getString(cursor.getColumnIndexOrThrow("category")));
Log.d("sub_category", cursor.getString(cursor.getColumnIndexOrThrow("sub_category")));
Log.d("payment", cursor.getString(cursor.getColumnIndexOrThrow("payment")));
Log.d("invoice", cursor.getString(cursor.getColumnIndexOrThrow("invoice")));
Log.d("custom_field", cursor.getString(cursor.getColumnIndexOrThrow("custom_field")));
Log.d("organization", cursor.getString(cursor.getColumnIndexOrThrow("organization")));
Log.d("person", cursor.getString(cursor.getColumnIndexOrThrow("person")));
} while (cursor.moveToNext());
}
}
}
I am aware that I can start multiple Async Tasks using:
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
The above method I am planning to call from an IntentService. So here are my confusions:
1) Will the do while loop wait till control has returned from asyncTask for the next iteration?
2)Will using and spawning multiple threads within an intentService disrupt my program?
3) Am I better off using Runnable r = new Runnable() than AsyncTask - as I dont intend any UI operation?
Android Studio keeps complaining ( lightbulb red X ) that 'db' and 'dbh' may not be instantiated in the finally block.
So three things:
If the insert fails, what is the state of dbh and db in the catch block, or the finally block?
More generally, what do I need to account for in the finally and catch blocks
Is lowering the complaint level in Android Studio the proper way to handle this :) ?
Below is the code. Its an Android AsyncTask.
#Override
protected Void doInBackground(Void... voids) {
SweepDatabaseHelper dbh;
SQLiteDatabase db;
try{
Random generator = new Random();
float freqstep = (stopFreq - startFreq)/steps;
dbh = new SweepDatabaseHelper(context);
db = dbh.getWritableDatabase();
// empty the table
db.delete(dbh.TABLE_SWEEPDATA, null, null);
// start writing the data
for(int i=0;i<steps;i++){
ContentValues values = new ContentValues();
SweepData sdata=new SweepData( (long)i, startFreq+(i*freqstep), (float)generator.nextFloat()*10 );
values.put(dbh.COLUMN_ID, (long)i);
values.put(dbh.COLUMN_FREQ, startFreq+(i*freqstep));
values.put(dbh.COLUMN_VSWR, sdata.getVswr());
db.insert(dbh.TABLE_SWEEPDATA, null, values);
publishProgress(new SweepData[]{sdata});
}
dbh.close();
return null;
}catch(Exception e){
// Do nothing at the moment
return null;
}finally{
if(db != null && dbh != null && db.isOpen()){
db.close();
}
}
}
You need to either assign them an initial value at declaration time if they are defined in the method, or define them as instance variables.
The accepted answer to this question has a good explanation.
Basically, instance variables will always have a default value, but that is not the case for local variables, that is why you are seeing the error.
You have two options to choose from to fix this.
The first option is to just initialize them to null:
SweepDatabaseHelper dbh = null;
SQLiteDatabase db = null;
I would recommend the second option of defining them as instance variables in your AsyncTask, and initialize in a constructor:
SweepDatabaseHelper dbh; //make instance variable
SQLiteDatabase db; //make instance variable
//constructor
public MyAsyncTask(Context context){
dbh = new SweepDatabaseHelper(context);
db = dbh.getWritableDatabase();
}
#Override
protected Void doInBackground(Void... voids) {
//SweepDatabaseHelper dbh; //remove
//SQLiteDatabase db; //remove
try{
Random generator = new Random();
float freqstep = (stopFreq - startFreq)/steps;
//dbh = new SweepDatabaseHelper(context); //remove
//db = dbh.getWritableDatabase(); //remove
// empty the table
db.delete(dbh.TABLE_SWEEPDATA, null, null);
// start writing the data
for(int i=0;i<steps;i++){
ContentValues values = new ContentValues();
SweepData sdata=new SweepData( (long)i, startFreq+(i*freqstep), (float)generator.nextFloat()*10 );
values.put(dbh.COLUMN_ID, (long)i);
values.put(dbh.COLUMN_FREQ, startFreq+(i*freqstep));
values.put(dbh.COLUMN_VSWR, sdata.getVswr());
db.insert(dbh.TABLE_SWEEPDATA, null, values);
publishProgress(new SweepData[]{sdata});
}
dbh.close();
return null;
}catch(Exception e){
// Do nothing at the moment
return null;
}finally{
if(db != null && dbh != null && db.isOpen()){
db.close();
}
}
}
You should be able to get Android Studio to stop complaining about this by simply initializing dbh and db to "null" when you define them.
SweepDatabaseHelper dbh = null;
SQLiteDatabase db = null;
try{
...
}catch{
dbh = new SweepDatabaseHelper(context);
db = dbh.getWritableDatabase();
}finally{
...
}
The reason is given in the Java specification:
4.12.5 Initial Values of Variables
...
A local variable (§14.4, §14.14) must be explicitly given a value
before it is used, by either initialization (§14.4) or assignment
(§15.26), in a way that can be verified using the rules for definite
assignment (§16).
http://docs.oracle.com/javase/specs/jls/se7/jls7.pdf
So, for your three questions:
If the insert fails, what is the state of dbh and db in the catch
block, or the finally block?
In your posted code, I would guess it won't compile.
More generally, what do I need to account for in the finally and catch blocks
You will want to put dbh.close() in your finally block so it always runs. Otherwise you may open the database, an exception occurs, and you end up leaving it opened.
You should use the catch block to take any action you'd want to do to if the operation fails, e.g., attempt error recovery, alert the user that there's a problem or ask them to make a decision, send an error report somewhere, etc.
Is lowering the complaint level in Android Studio the proper way to handle this :) ?
In some cases where you know exactly what you're doing, you know why it's complaining, and you know it's going to work, yes. In this case? No.
I have a database, and I am upgrading the version now. The problem scenario is such that on the DB upgrade I have to read from a file and insert records into a primary database table. Before inserting a record I have to check if this record might already exist and based upon this make a decision of inserting or not inserting. There are tens of thousands of records, which I have to check and insert.
My question here is that would it be right to use a separate thread handler or an Async task on DB upgrade? Or does the system handle this?
Now I have created an AsyncTask
public class AsyncUpgrade extends AsyncTask<Void, Void,String>{
#Override
public void onPreExecute(){
super.onPreExecute();
}
#Override
public void onPostExecute(String result){
//TODO
}
#Override
protected String doInBackground(Void... params) {
System.out.println("UPGRADE 3");
Cursor nameCur = readName();
String name_friend = null;
if(nameCur != null && nameCur.moveToFirst()){
do{
name_friend = nameCur.getString(nameCur.getColumnIndexOrThrow("SelfName"));
}while (nameCur.moveToNext());
}
assets = new AssetsDbHelper(con);
Cursor mCur = assets.getMax_id();
if(mCur != null && mCur.moveToFirst()){
do{
String name = mCur.getString(mCur.getColumnIndexOrThrow("Name"));
String DOB = mCur.getString(mCur.getColumnIndexOrThrow("DOB"));
String imageData = mCur.getString(mCur.getColumnIndexOrThrow("imageUri"));
String type = mCur.getString(mCur.getColumnIndexOrThrow("Type"));
String selection = mCur.getString(mCur.getColumnIndexOrThrow("Radio"));
Cursor checkCur= checkIfRecordExists(name.trim(), DOB.trim());
if(checkCur != null && checkCur.moveToFirst()){
}else{
insertAdhoc(name, DOB,imageData, type, selection, name_friend);
}
}while(mCur.moveToNext());
}
return "Success";
}
}
and I am calling the same from the upgrade method:
if(oldVersion <3){
new AsyncUpgrade().execute();
}
And now to my luck, I have an error popping up:
No enclosing instance of type DBAdapter is accessible. Must qualify the allocation with an enclosing instance of type DBAdapter (e.g. x.new A() where x is an instance of DBAdapter).
Always use an asyctask.Whenever you are doing something which is not related to updating the UI thread.From Android best practices for saving data
Note: Because they can be long-running, be sure that you call getWritableDatabase() or getReadableDatabase() in a background thread, such as with AsyncTask or IntentService.
Been searching and using all the resources here but nothing seems to work with my project.
i need to retrieve a random data upon shaking the device.
i already have my shake detection.
my problem is i cant retrieve a random data.
tested on a device. the application crashes.
mainactivity.java
DataSource DS = new DataSource(this);
public void onShake(float force) {
// Called when Motion Detected
Toast.makeText(getBaseContext(), "Motion detected",
Toast.LENGTH_SHORT).show();
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Do something after 5s = 5000ms
TextView text = (TextView) findViewById(R.id.textView1);
text.setText(DS.getRandomQuote());
}
}, 2000);
public String getRandomQuote() {
Cursor c = database.query(MysqLiteHelper.TABLE_COMMENTS,
new String[] { MysqLiteHelper.COLUMN_COMMENT}, null, null, null, null, "ORDER BY RANDON() LIMIT 1");
if(c.moveToFirst())
return c.getString(c.getColumnIndex(MysqLiteHelper.COLUMN_COMMENT));
else
return "nothing";
}
Already Read:
How can select random data from database and display in textview?
and
How can select random data from database and display in textview?
but doesn't work for me
The function you're looking for is RANDOM() not RANDON()
query() takes its ORDER BY and LIMIT params in separate args, without the ORDER BY and LIMIT keywords.