Singleton database instance to prevent locking exceptions.
Previously, all of the various subclasses of FDroidProvider would create their own database open helper in their respective `onCreate()` methods. This seemed to be the cause of the multiple database locked exceptions. Various online articles/SO posts/etc helped come to this conclusion: * http://stackoverflow.com/a/3689883 * http://stackoverflow.com/a/8888606 * https://web.archive.org/web/20150709074733/http://www.dmytrodanylyk.com/pages/blog/concurrent-database.html This should fix #265. In the process, also did away with the two `read()` and `write()` methods that returned a "readable" and "writeable" database respectively. It turns out that it doesn't quite do what I originally thought. There is not much benefit to specifying to the database helper that you want a readable/writeable database. In fact, it is often the case that a call to `read()` would most likely have returned the same instance that is returned by `write()`. The semantics of them were therefore broken, and they've been replaced with `db()`.
This commit is contained in:
		
							parent
							
								
									426e3da139
								
							
						
					
					
						commit
						4957bcb812
					
				| @ -486,7 +486,7 @@ public class ApkProvider extends FDroidProvider { | ||||
|         queryBuilder.addSelection(query.getSelection()); | ||||
|         queryBuilder.addOrderBy(sortOrder); | ||||
| 
 | ||||
|         Cursor cursor = read().rawQuery(queryBuilder.toString(), query.getArgs()); | ||||
|         Cursor cursor = db().rawQuery(queryBuilder.toString(), query.getArgs()); | ||||
|         cursor.setNotificationUri(getContext().getContentResolver(), uri); | ||||
|         return cursor; | ||||
|     } | ||||
| @ -507,7 +507,7 @@ public class ApkProvider extends FDroidProvider { | ||||
|     public Uri insert(Uri uri, ContentValues values) { | ||||
|         removeRepoFields(values); | ||||
|         validateFields(DataColumns.ALL, values); | ||||
|         write().insertOrThrow(getTableName(), null, values); | ||||
|         db().insertOrThrow(getTableName(), null, values); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
| @ -553,7 +553,7 @@ public class ApkProvider extends FDroidProvider { | ||||
|                 throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri); | ||||
|         } | ||||
| 
 | ||||
|         int rowsAffected = write().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         int rowsAffected = db().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         getContext().getContentResolver().notifyChange(uri, null); | ||||
|         return rowsAffected; | ||||
| 
 | ||||
| @ -574,7 +574,7 @@ public class ApkProvider extends FDroidProvider { | ||||
|         QuerySelection query = new QuerySelection(where, whereArgs); | ||||
|         query = query.add(querySingle(uri)); | ||||
| 
 | ||||
|         int numRows = write().update(getTableName(), values, query.getSelection(), query.getArgs()); | ||||
|         int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs()); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
|  | ||||
| @ -822,7 +822,7 @@ public class AppProvider extends FDroidProvider { | ||||
|         query.addFields(projection); // TODO: Make the order of addFields/addSelection not dependent on each other... | ||||
|         query.addOrderBy(sortOrder); | ||||
| 
 | ||||
|         Cursor cursor = read().rawQuery(query.toString(), selection.getArgs()); | ||||
|         Cursor cursor = db().rawQuery(query.toString(), selection.getArgs()); | ||||
|         cursor.setNotificationUri(getContext().getContentResolver(), uri); | ||||
|         return cursor; | ||||
|     } | ||||
| @ -842,14 +842,14 @@ public class AppProvider extends FDroidProvider { | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         int count = write().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         int count = db().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         getContext().getContentResolver().notifyChange(uri, null); | ||||
|         return count; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Uri insert(Uri uri, ContentValues values) { | ||||
|         write().insertOrThrow(getTableName(), null, values); | ||||
|         db().insertOrThrow(getTableName(), null, values); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
| @ -873,7 +873,7 @@ public class AppProvider extends FDroidProvider { | ||||
|                 throw new UnsupportedOperationException("Update not supported for " + uri + "."); | ||||
| 
 | ||||
|         } | ||||
|         int count = write().update(getTableName(), values, query.getSelection(), query.getArgs()); | ||||
|         int count = db().update(getTableName(), values, query.getSelection(), query.getArgs()); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
| @ -884,7 +884,7 @@ public class AppProvider extends FDroidProvider { | ||||
|         updateCompatibleFlags(); | ||||
|         updateSuggestedFromUpstream(); | ||||
|         updateSuggestedFromLatest(); | ||||
|         updateIconUrls(getContext(), write(), getTableName(), getApkTableName()); | ||||
|         updateIconUrls(getContext(), db(), getTableName(), getApkTableName()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -911,7 +911,7 @@ public class AppProvider extends FDroidProvider { | ||||
|                 " FROM " + apk + | ||||
|                 " WHERE " + apk + ".id = " + app + ".id );"; | ||||
| 
 | ||||
|         write().execSQL(updateSql); | ||||
|         db().execSQL(updateSql); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -952,7 +952,7 @@ public class AppProvider extends FDroidProvider { | ||||
|                     " ( " + app + ".compatible = 0 OR " + apk + ".compatible = 1 ) ) " + | ||||
|                 " WHERE upstreamVercode > 0 "; | ||||
| 
 | ||||
|         write().execSQL(updateSql); | ||||
|         db().execSQL(updateSql); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -990,7 +990,7 @@ public class AppProvider extends FDroidProvider { | ||||
|                     " ( " + app + ".compatible = 0 OR " + apk + ".compatible = 1 ) ) " + | ||||
|                 " WHERE upstreamVercode = 0 OR upstreamVercode IS NULL OR suggestedVercode IS NULL "; | ||||
| 
 | ||||
|         write().execSQL(updateSql); | ||||
|         db().execSQL(updateSql); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -5,12 +5,15 @@ import android.content.ContentProvider; | ||||
| import android.content.ContentProviderOperation; | ||||
| import android.content.ContentProviderResult; | ||||
| import android.content.ContentValues; | ||||
| import android.content.Context; | ||||
| import android.content.OperationApplicationException; | ||||
| import android.content.UriMatcher; | ||||
| import android.database.sqlite.SQLiteDatabase; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import org.fdroid.fdroid.Utils; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashSet; | ||||
| import java.util.Map; | ||||
| @ -18,12 +21,14 @@ import java.util.Set; | ||||
| 
 | ||||
| public abstract class FDroidProvider extends ContentProvider { | ||||
| 
 | ||||
|     private static final String TAG = "FDroidProvider"; | ||||
| 
 | ||||
|     public static final String AUTHORITY = "org.fdroid.fdroid.data"; | ||||
| 
 | ||||
|     protected static final int CODE_LIST   = 1; | ||||
|     protected static final int CODE_SINGLE = 2; | ||||
| 
 | ||||
|     private DBHelper dbHelper; | ||||
|     private static DBHelper dbHelper; | ||||
| 
 | ||||
|     private boolean isApplyingBatch; | ||||
| 
 | ||||
| @ -53,33 +58,33 @@ public abstract class FDroidProvider extends ContentProvider { | ||||
|         throws OperationApplicationException { | ||||
|         ContentProviderResult[] result = null; | ||||
|         isApplyingBatch = true; | ||||
|         write().beginTransaction(); | ||||
|         final SQLiteDatabase db = db(); | ||||
|         db.beginTransaction(); | ||||
|         try { | ||||
|             result = super.applyBatch(operations); | ||||
|             write().setTransactionSuccessful(); | ||||
|             db.setTransactionSuccessful(); | ||||
|         } finally { | ||||
|             write().endTransaction(); | ||||
|             db.endTransaction(); | ||||
|             isApplyingBatch = false; | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         dbHelper = new DBHelper(getContext()); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     protected final DBHelper db() { | ||||
|     private static synchronized DBHelper getOrCreateDb(Context context) { | ||||
|         if (dbHelper == null) { | ||||
|             Utils.debugLog(TAG, "First time accessing database, creating new helper"); | ||||
|             dbHelper = new DBHelper(context); | ||||
|         } | ||||
|         return dbHelper; | ||||
|     } | ||||
| 
 | ||||
|     protected final SQLiteDatabase read() { | ||||
|         return db().getReadableDatabase(); | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     protected final SQLiteDatabase write() { | ||||
|         return db().getWritableDatabase(); | ||||
|     protected final synchronized SQLiteDatabase db() { | ||||
|         return getOrCreateDb(getContext()).getWritableDatabase(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | ||||
| @ -180,7 +180,7 @@ public class InstalledAppProvider extends FDroidProvider { | ||||
|                 throw new UnsupportedOperationException(message); | ||||
|         } | ||||
| 
 | ||||
|         Cursor cursor = read().query(getTableName(), projection, selection.getSelection(), selection.getArgs(), null, null, sortOrder); | ||||
|         Cursor cursor = db().query(getTableName(), projection, selection.getSelection(), selection.getArgs(), null, null, sortOrder); | ||||
|         cursor.setNotificationUri(getContext().getContentResolver(), uri); | ||||
|         return cursor; | ||||
|     } | ||||
| @ -195,7 +195,7 @@ public class InstalledAppProvider extends FDroidProvider { | ||||
|         QuerySelection query = new QuerySelection(where, whereArgs); | ||||
|         query = query.add(queryApp(uri.getLastPathSegment())); | ||||
| 
 | ||||
|         int count = write().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         int count = db().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
| @ -210,7 +210,7 @@ public class InstalledAppProvider extends FDroidProvider { | ||||
|         } | ||||
| 
 | ||||
|         verifyVersionNameNotNull(values); | ||||
|         write().replaceOrThrow(getTableName(), null, values); | ||||
|         db().replaceOrThrow(getTableName(), null, values); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
|  | ||||
| @ -305,8 +305,7 @@ public class RepoProvider extends FDroidProvider { | ||||
|                 throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri); | ||||
|         } | ||||
| 
 | ||||
|         Cursor cursor = read().query(getTableName(), projection, selection, | ||||
|                 selectionArgs, null, null, sortOrder); | ||||
|         Cursor cursor = db().query(getTableName(), projection, selection, selectionArgs, null, null, sortOrder); | ||||
|         cursor.setNotificationUri(getContext().getContentResolver(), uri); | ||||
|         return cursor; | ||||
|     } | ||||
| @ -342,7 +341,7 @@ public class RepoProvider extends FDroidProvider { | ||||
|             values.put(DataColumns.NAME, Repo.addressToName(address)); | ||||
|         } | ||||
| 
 | ||||
|         long id = write().insertOrThrow(getTableName(), null, values); | ||||
|         long id = db().insertOrThrow(getTableName(), null, values); | ||||
|         Utils.debugLog(TAG, "Inserted repo. Notifying provider change: '" + uri + "'."); | ||||
|         getContext().getContentResolver().notifyChange(uri, null); | ||||
|         return getContentUri(id); | ||||
| @ -366,7 +365,7 @@ public class RepoProvider extends FDroidProvider { | ||||
|                 throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri); | ||||
|         } | ||||
| 
 | ||||
|         int rowsAffected = write().delete(getTableName(), where, whereArgs); | ||||
|         int rowsAffected = db().delete(getTableName(), where, whereArgs); | ||||
|         Utils.debugLog(TAG, "Deleted repos. Notifying provider change: '" + uri + "'."); | ||||
|         getContext().getContentResolver().notifyChange(uri, null); | ||||
|         return rowsAffected; | ||||
| @ -374,7 +373,7 @@ public class RepoProvider extends FDroidProvider { | ||||
| 
 | ||||
|     @Override | ||||
|     public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { | ||||
|         int numRows = write().update(getTableName(), values, where, whereArgs); | ||||
|         int numRows = db().update(getTableName(), values, where, whereArgs); | ||||
|         Utils.debugLog(TAG, "Updated repo. Notifying provider change: '" + uri + "'."); | ||||
|         getContext().getContentResolver().notifyChange(uri, null); | ||||
|         return numRows; | ||||
|  | ||||
| @ -3,6 +3,7 @@ package org.fdroid.fdroid.data; | ||||
| import android.content.ContentValues; | ||||
| import android.content.Context; | ||||
| import android.content.UriMatcher; | ||||
| import android.database.sqlite.SQLiteDatabase; | ||||
| import android.net.Uri; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| @ -112,7 +113,7 @@ public class TempApkProvider extends ApkProvider { | ||||
|                 throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri); | ||||
|         } | ||||
| 
 | ||||
|         int rowsAffected = write().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         int rowsAffected = db().delete(getTableName(), query.getSelection(), query.getArgs()); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
| @ -121,11 +122,12 @@ public class TempApkProvider extends ApkProvider { | ||||
|     } | ||||
| 
 | ||||
|     private void initTable() { | ||||
|         write().execSQL("DROP TABLE IF EXISTS " + getTableName()); | ||||
|         write().execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK); | ||||
|         write().execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + getTableName() + " (vercode);"); | ||||
|         write().execSQL("CREATE INDEX IF NOT EXISTS apk_id on " + getTableName() + " (id);"); | ||||
|         write().execSQL("CREATE INDEX IF NOT EXISTS apk_compatible ON " + getTableName() + " (compatible);"); | ||||
|         final SQLiteDatabase db = db(); | ||||
|         db.execSQL("DROP TABLE IF EXISTS " + getTableName()); | ||||
|         db.execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK); | ||||
|         db.execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + getTableName() + " (vercode);"); | ||||
|         db.execSQL("CREATE INDEX IF NOT EXISTS apk_id on " + getTableName() + " (id);"); | ||||
|         db.execSQL("CREATE INDEX IF NOT EXISTS apk_compatible ON " + getTableName() + " (compatible);"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -104,7 +104,7 @@ public class TempAppProvider extends AppProvider { | ||||
|                 throw new UnsupportedOperationException("Update not supported for " + uri + "."); | ||||
|         } | ||||
| 
 | ||||
|         int count = write().update(getTableName(), values, query.getSelection(), query.getArgs()); | ||||
|         int count = db().update(getTableName(), values, query.getSelection(), query.getArgs()); | ||||
|         if (!isApplyingBatch()) { | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
| @ -112,15 +112,16 @@ public class TempAppProvider extends AppProvider { | ||||
|     } | ||||
| 
 | ||||
|     private void initTable() { | ||||
|         write().execSQL("DROP TABLE IF EXISTS " + getTableName()); | ||||
|         write().execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP); | ||||
|         write().execSQL("CREATE INDEX IF NOT EXISTS app_id ON " + getTableName() + " (id);"); | ||||
|         write().execSQL("CREATE INDEX IF NOT EXISTS app_upstreamVercode ON " + getTableName() + " (upstreamVercode);"); | ||||
|         write().execSQL("CREATE INDEX IF NOT EXISTS app_compatible ON " + getTableName() + " (compatible);"); | ||||
|         final SQLiteDatabase db = db(); | ||||
|         db.execSQL("DROP TABLE IF EXISTS " + getTableName()); | ||||
|         db.execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP); | ||||
|         db.execSQL("CREATE INDEX IF NOT EXISTS app_id ON " + getTableName() + " (id);"); | ||||
|         db.execSQL("CREATE INDEX IF NOT EXISTS app_upstreamVercode ON " + getTableName() + " (upstreamVercode);"); | ||||
|         db.execSQL("CREATE INDEX IF NOT EXISTS app_compatible ON " + getTableName() + " (compatible);"); | ||||
|     } | ||||
| 
 | ||||
|     private void commitTable() { | ||||
|         final SQLiteDatabase db = write(); | ||||
|         final SQLiteDatabase db = db(); | ||||
|         try { | ||||
|             db.beginTransaction(); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Peter Serwylo
						Peter Serwylo