Merge "Refactor SQLiteOpenHelper."
This commit is contained in:
@@ -92,13 +92,25 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
new ArrayList<SQLiteConnection>();
|
||||
private SQLiteConnection mAvailablePrimaryConnection;
|
||||
|
||||
// Describes what should happen to an acquired connection when it is returned to the pool.
|
||||
enum AcquiredConnectionStatus {
|
||||
// The connection should be returned to the pool as usual.
|
||||
NORMAL,
|
||||
|
||||
// The connection must be reconfigured before being returned.
|
||||
RECONFIGURE,
|
||||
|
||||
// The connection must be closed and discarded.
|
||||
DISCARD,
|
||||
}
|
||||
|
||||
// Weak references to all acquired connections. The associated value
|
||||
// is a boolean that indicates whether the connection must be reconfigured
|
||||
// before being returned to the available connection list.
|
||||
// indicates whether the connection must be reconfigured before being
|
||||
// returned to the available connection list or discarded.
|
||||
// For example, the prepared statement cache size may have changed and
|
||||
// need to be updated.
|
||||
private final WeakHashMap<SQLiteConnection, Boolean> mAcquiredConnections =
|
||||
new WeakHashMap<SQLiteConnection, Boolean>();
|
||||
// need to be updated in preparation for the next client.
|
||||
private final WeakHashMap<SQLiteConnection, AcquiredConnectionStatus> mAcquiredConnections =
|
||||
new WeakHashMap<SQLiteConnection, AcquiredConnectionStatus>();
|
||||
|
||||
/**
|
||||
* Connection flag: Read-only.
|
||||
@@ -168,7 +180,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
private void open() {
|
||||
// Open the primary connection.
|
||||
// This might throw if the database is corrupt.
|
||||
mAvailablePrimaryConnection = openConnectionLocked(
|
||||
mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
|
||||
true /*primaryConnection*/); // might throw
|
||||
|
||||
// Mark the pool as being open for business.
|
||||
@@ -209,16 +221,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
|
||||
mIsOpen = false;
|
||||
|
||||
final int count = mAvailableNonPrimaryConnections.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
|
||||
}
|
||||
mAvailableNonPrimaryConnections.clear();
|
||||
|
||||
if (mAvailablePrimaryConnection != null) {
|
||||
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
|
||||
mAvailablePrimaryConnection = null;
|
||||
}
|
||||
closeAvailableConnectionsAndLogExceptionsLocked();
|
||||
|
||||
final int pendingCount = mAcquiredConnections.size();
|
||||
if (pendingCount != 0) {
|
||||
@@ -254,21 +257,27 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
synchronized (mLock) {
|
||||
throwIfClosedLocked();
|
||||
|
||||
final boolean poolSizeChanged = mConfiguration.maxConnectionPoolSize
|
||||
!= configuration.maxConnectionPoolSize;
|
||||
mConfiguration.updateParametersFrom(configuration);
|
||||
if (mConfiguration.openFlags != configuration.openFlags) {
|
||||
// Try to reopen the primary connection using the new open flags then
|
||||
// close and discard all existing connections.
|
||||
// This might throw if the database is corrupt or cannot be opened in
|
||||
// the new mode in which case existing connections will remain untouched.
|
||||
SQLiteConnection newPrimaryConnection = openConnectionLocked(configuration,
|
||||
true /*primaryConnection*/); // might throw
|
||||
|
||||
if (poolSizeChanged) {
|
||||
int availableCount = mAvailableNonPrimaryConnections.size();
|
||||
while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
|
||||
SQLiteConnection connection =
|
||||
mAvailableNonPrimaryConnections.remove(availableCount);
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
}
|
||||
closeAvailableConnectionsAndLogExceptionsLocked();
|
||||
discardAcquiredConnectionsLocked();
|
||||
|
||||
mAvailablePrimaryConnection = newPrimaryConnection;
|
||||
mConfiguration.updateParametersFrom(configuration);
|
||||
} else {
|
||||
// Reconfigure the database connections in place.
|
||||
mConfiguration.updateParametersFrom(configuration);
|
||||
|
||||
closeExcessConnectionsAndLogExceptionsLocked();
|
||||
reconfigureAllConnectionsLocked();
|
||||
}
|
||||
|
||||
reconfigureAllConnectionsLocked();
|
||||
|
||||
wakeConnectionWaitersLocked();
|
||||
}
|
||||
}
|
||||
@@ -310,8 +319,8 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
*/
|
||||
public void releaseConnection(SQLiteConnection connection) {
|
||||
synchronized (mLock) {
|
||||
Boolean mustReconfigure = mAcquiredConnections.remove(connection);
|
||||
if (mustReconfigure == null) {
|
||||
AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Cannot perform this operation "
|
||||
+ "because the specified connection was not acquired "
|
||||
+ "from this pool or has already been released.");
|
||||
@@ -320,18 +329,8 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
if (!mIsOpen) {
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
} else if (connection.isPrimaryConnection()) {
|
||||
assert mAvailablePrimaryConnection == null;
|
||||
try {
|
||||
if (mustReconfigure == Boolean.TRUE) {
|
||||
connection.reconfigure(mConfiguration); // might throw
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Failed to reconfigure released primary connection, closing it: "
|
||||
+ connection, ex);
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
connection = null;
|
||||
}
|
||||
if (connection != null) {
|
||||
if (recycleConnectionLocked(connection, status)) {
|
||||
assert mAvailablePrimaryConnection == null;
|
||||
mAvailablePrimaryConnection = connection;
|
||||
}
|
||||
wakeConnectionWaitersLocked();
|
||||
@@ -339,17 +338,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
mConfiguration.maxConnectionPoolSize - 1) {
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
} else {
|
||||
try {
|
||||
if (mustReconfigure == Boolean.TRUE) {
|
||||
connection.reconfigure(mConfiguration); // might throw
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Failed to reconfigure released non-primary connection, "
|
||||
+ "closing it: " + connection, ex);
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
connection = null;
|
||||
}
|
||||
if (connection != null) {
|
||||
if (recycleConnectionLocked(connection, status)) {
|
||||
mAvailableNonPrimaryConnections.add(connection);
|
||||
}
|
||||
wakeConnectionWaitersLocked();
|
||||
@@ -357,6 +346,25 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private boolean recycleConnectionLocked(SQLiteConnection connection,
|
||||
AcquiredConnectionStatus status) {
|
||||
if (status == AcquiredConnectionStatus.RECONFIGURE) {
|
||||
try {
|
||||
connection.reconfigure(mConfiguration); // might throw
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Failed to reconfigure released connection, closing it: "
|
||||
+ connection, ex);
|
||||
status = AcquiredConnectionStatus.DISCARD;
|
||||
}
|
||||
}
|
||||
if (status == AcquiredConnectionStatus.DISCARD) {
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the session should yield the connection due to
|
||||
* contention over available database connections.
|
||||
@@ -407,9 +415,10 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
}
|
||||
|
||||
// Might throw.
|
||||
private SQLiteConnection openConnectionLocked(boolean primaryConnection) {
|
||||
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
|
||||
boolean primaryConnection) {
|
||||
final int connectionId = mNextConnectionId++;
|
||||
return SQLiteConnection.open(this, mConfiguration,
|
||||
return SQLiteConnection.open(this, configuration,
|
||||
connectionId, primaryConnection); // might throw
|
||||
}
|
||||
|
||||
@@ -442,6 +451,30 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
mConnectionLeaked.set(true);
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private void closeAvailableConnectionsAndLogExceptionsLocked() {
|
||||
final int count = mAvailableNonPrimaryConnections.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
|
||||
}
|
||||
mAvailableNonPrimaryConnections.clear();
|
||||
|
||||
if (mAvailablePrimaryConnection != null) {
|
||||
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
|
||||
mAvailablePrimaryConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private void closeExcessConnectionsAndLogExceptionsLocked() {
|
||||
int availableCount = mAvailableNonPrimaryConnections.size();
|
||||
while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
|
||||
SQLiteConnection connection =
|
||||
mAvailableNonPrimaryConnections.remove(availableCount);
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
|
||||
try {
|
||||
@@ -452,9 +485,13 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private void discardAcquiredConnectionsLocked() {
|
||||
markAcquiredConnectionsLocked(AcquiredConnectionStatus.DISCARD);
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private void reconfigureAllConnectionsLocked() {
|
||||
boolean wake = false;
|
||||
if (mAvailablePrimaryConnection != null) {
|
||||
try {
|
||||
mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw
|
||||
@@ -463,7 +500,6 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
+ mAvailablePrimaryConnection, ex);
|
||||
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
|
||||
mAvailablePrimaryConnection = null;
|
||||
wake = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,27 +514,30 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
closeConnectionAndLogExceptionsLocked(connection);
|
||||
mAvailableNonPrimaryConnections.remove(i--);
|
||||
count -= 1;
|
||||
wake = true;
|
||||
}
|
||||
}
|
||||
|
||||
markAcquiredConnectionsLocked(AcquiredConnectionStatus.RECONFIGURE);
|
||||
}
|
||||
|
||||
// Can't throw.
|
||||
private void markAcquiredConnectionsLocked(AcquiredConnectionStatus status) {
|
||||
if (!mAcquiredConnections.isEmpty()) {
|
||||
ArrayList<SQLiteConnection> keysToUpdate = new ArrayList<SQLiteConnection>(
|
||||
mAcquiredConnections.size());
|
||||
for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) {
|
||||
if (entry.getValue() != Boolean.TRUE) {
|
||||
for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry
|
||||
: mAcquiredConnections.entrySet()) {
|
||||
AcquiredConnectionStatus oldStatus = entry.getValue();
|
||||
if (status != oldStatus
|
||||
&& oldStatus != AcquiredConnectionStatus.DISCARD) {
|
||||
keysToUpdate.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
final int updateCount = keysToUpdate.size();
|
||||
for (int i = 0; i < updateCount; i++) {
|
||||
mAcquiredConnections.put(keysToUpdate.get(i), Boolean.TRUE);
|
||||
mAcquiredConnections.put(keysToUpdate.get(i), status);
|
||||
}
|
||||
}
|
||||
|
||||
if (wake) {
|
||||
wakeConnectionWaitersLocked();
|
||||
}
|
||||
}
|
||||
|
||||
// Might throw.
|
||||
@@ -658,8 +697,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
int activeConnections = 0;
|
||||
int idleConnections = 0;
|
||||
if (!mAcquiredConnections.isEmpty()) {
|
||||
for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) {
|
||||
final SQLiteConnection connection = entry.getKey();
|
||||
for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
|
||||
String description = connection.describeCurrentOperationUnsafe();
|
||||
if (description != null) {
|
||||
requests.add(description);
|
||||
@@ -769,7 +807,8 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
|
||||
// Uhoh. No primary connection! Either this is the first time we asked
|
||||
// for it, or maybe it leaked?
|
||||
connection = openConnectionLocked(true /*primaryConnection*/); // might throw
|
||||
connection = openConnectionLocked(mConfiguration,
|
||||
true /*primaryConnection*/); // might throw
|
||||
finishAcquireConnectionLocked(connection, connectionFlags); // might throw
|
||||
return connection;
|
||||
}
|
||||
@@ -807,7 +846,8 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
if (openConnections >= mConfiguration.maxConnectionPoolSize) {
|
||||
return null;
|
||||
}
|
||||
connection = openConnectionLocked(false /*primaryConnection*/); // might throw
|
||||
connection = openConnectionLocked(mConfiguration,
|
||||
false /*primaryConnection*/); // might throw
|
||||
finishAcquireConnectionLocked(connection, connectionFlags); // might throw
|
||||
return connection;
|
||||
}
|
||||
@@ -818,7 +858,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0;
|
||||
connection.setOnlyAllowReadOnlyOperations(readOnly);
|
||||
|
||||
mAcquiredConnections.put(connection, Boolean.FALSE);
|
||||
mAcquiredConnections.put(connection, AcquiredConnectionStatus.NORMAL);
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Failed to prepare acquired connection for session, closing it: "
|
||||
+ connection +", connectionFlags=" + connectionFlags);
|
||||
@@ -858,7 +898,7 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
private void throwIfClosedLocked() {
|
||||
if (!mIsOpen) {
|
||||
throw new IllegalStateException("Cannot perform this operation "
|
||||
+ "because the connection pool have been closed.");
|
||||
+ "because the connection pool has been closed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,11 +962,11 @@ public final class SQLiteConnectionPool implements Closeable {
|
||||
|
||||
printer.println(" Acquired connections:");
|
||||
if (!mAcquiredConnections.isEmpty()) {
|
||||
for (Map.Entry<SQLiteConnection, Boolean> entry :
|
||||
for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry :
|
||||
mAcquiredConnections.entrySet()) {
|
||||
final SQLiteConnection connection = entry.getKey();
|
||||
connection.dumpUnsafe(indentedPrinter, verbose);
|
||||
indentedPrinter.println(" Pending reconfiguration: " + entry.getValue());
|
||||
indentedPrinter.println(" Status: " + entry.getValue());
|
||||
}
|
||||
} else {
|
||||
indentedPrinter.println("<none>");
|
||||
|
||||
@@ -677,6 +677,38 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopens the database in read-write mode.
|
||||
* If the database is already read-write, does nothing.
|
||||
*
|
||||
* @throws SQLiteException if the database could not be reopened as requested, in which
|
||||
* case it remains open in read only mode.
|
||||
* @throws IllegalStateException if the database is not open.
|
||||
*
|
||||
* @see #isReadOnly()
|
||||
* @hide
|
||||
*/
|
||||
public void reopenReadWrite() {
|
||||
synchronized (mLock) {
|
||||
throwIfNotOpenLocked();
|
||||
|
||||
if (!isReadOnlyLocked()) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
// Reopen the database in read-write mode.
|
||||
final int oldOpenFlags = mConfigurationLocked.openFlags;
|
||||
mConfigurationLocked.openFlags = (mConfigurationLocked.openFlags & ~OPEN_READ_MASK)
|
||||
| OPEN_READWRITE;
|
||||
try {
|
||||
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
|
||||
} catch (RuntimeException ex) {
|
||||
mConfigurationLocked.openFlags = oldOpenFlags;
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void open() {
|
||||
try {
|
||||
try {
|
||||
@@ -1902,28 +1934,6 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent other threads from using the database's primary connection.
|
||||
*
|
||||
* This method is only used by {@link SQLiteOpenHelper} when transitioning from
|
||||
* a readable to a writable database. It should not be used in any other way.
|
||||
*
|
||||
* @see #unlockPrimaryConnection()
|
||||
*/
|
||||
void lockPrimaryConnection() {
|
||||
getThreadSession().beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED,
|
||||
null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow other threads to use the database's primary connection.
|
||||
*
|
||||
* @see #lockPrimaryConnection()
|
||||
*/
|
||||
void unlockPrimaryConnection() {
|
||||
getThreadSession().endTransaction(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SQLiteDatabase: " + getPath();
|
||||
|
||||
@@ -50,17 +50,17 @@ public final class SQLiteDatabaseConfiguration {
|
||||
*/
|
||||
public final String path;
|
||||
|
||||
/**
|
||||
* The flags used to open the database.
|
||||
*/
|
||||
public final int openFlags;
|
||||
|
||||
/**
|
||||
* The label to use to describe the database when it appears in logs.
|
||||
* This is derived from the path but is stripped to remove PII.
|
||||
*/
|
||||
public final String label;
|
||||
|
||||
/**
|
||||
* The flags used to open the database.
|
||||
*/
|
||||
public int openFlags;
|
||||
|
||||
/**
|
||||
* The maximum number of connections to retain in the connection pool.
|
||||
* Must be at least 1.
|
||||
@@ -103,8 +103,8 @@ public final class SQLiteDatabaseConfiguration {
|
||||
}
|
||||
|
||||
this.path = path;
|
||||
this.openFlags = openFlags;
|
||||
label = stripPathForLogs(path);
|
||||
this.openFlags = openFlags;
|
||||
|
||||
// Set default values for optional parameters.
|
||||
maxConnectionPoolSize = 1;
|
||||
@@ -123,7 +123,6 @@ public final class SQLiteDatabaseConfiguration {
|
||||
}
|
||||
|
||||
this.path = other.path;
|
||||
this.openFlags = other.openFlags;
|
||||
this.label = other.label;
|
||||
updateParametersFrom(other);
|
||||
}
|
||||
@@ -138,11 +137,12 @@ public final class SQLiteDatabaseConfiguration {
|
||||
if (other == null) {
|
||||
throw new IllegalArgumentException("other must not be null.");
|
||||
}
|
||||
if (!path.equals(other.path) || openFlags != other.openFlags) {
|
||||
if (!path.equals(other.path)) {
|
||||
throw new IllegalArgumentException("other configuration must refer to "
|
||||
+ "the same database.");
|
||||
}
|
||||
|
||||
openFlags = other.openFlags;
|
||||
maxConnectionPoolSize = other.maxConnectionPoolSize;
|
||||
maxSqlCacheSize = other.maxSqlCacheSize;
|
||||
locale = other.locale;
|
||||
|
||||
@@ -43,13 +43,21 @@ import android.util.Log;
|
||||
public abstract class SQLiteOpenHelper {
|
||||
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
// When true, getReadableDatabase returns a read-only database if it is just being opened.
|
||||
// The database handle is reopened in read/write mode when getWritableDatabase is called.
|
||||
// We leave this behavior disabled in production because it is inefficient and breaks
|
||||
// many applications. For debugging purposes it can be useful to turn on strict
|
||||
// read-only semantics to catch applications that call getReadableDatabase when they really
|
||||
// wanted getWritableDatabase.
|
||||
private static final boolean DEBUG_STRICT_READONLY = false;
|
||||
|
||||
private final Context mContext;
|
||||
private final String mName;
|
||||
private final CursorFactory mFactory;
|
||||
private final int mNewVersion;
|
||||
|
||||
private SQLiteDatabase mDatabase = null;
|
||||
private boolean mIsInitializing = false;
|
||||
private SQLiteDatabase mDatabase;
|
||||
private boolean mIsInitializing;
|
||||
private final DatabaseErrorHandler mErrorHandler;
|
||||
|
||||
/**
|
||||
@@ -127,76 +135,9 @@ public abstract class SQLiteOpenHelper {
|
||||
* @throws SQLiteException if the database cannot be opened for writing
|
||||
* @return a read/write database object valid until {@link #close} is called
|
||||
*/
|
||||
public synchronized SQLiteDatabase getWritableDatabase() {
|
||||
if (mDatabase != null) {
|
||||
if (!mDatabase.isOpen()) {
|
||||
// darn! the user closed the database by calling mDatabase.close()
|
||||
mDatabase = null;
|
||||
} else if (!mDatabase.isReadOnly()) {
|
||||
return mDatabase; // The database is already open for business
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsInitializing) {
|
||||
throw new IllegalStateException("getWritableDatabase called recursively");
|
||||
}
|
||||
|
||||
// If we have a read-only database open, someone could be using it
|
||||
// (though they shouldn't), which would cause a lock to be held on
|
||||
// the file, and our attempts to open the database read-write would
|
||||
// fail waiting for the file lock. To prevent that, we acquire a lock
|
||||
// on the read-only database, which shuts out other users.
|
||||
|
||||
boolean success = false;
|
||||
SQLiteDatabase db = null;
|
||||
if (mDatabase != null) {
|
||||
mDatabase.lockPrimaryConnection();
|
||||
}
|
||||
try {
|
||||
mIsInitializing = true;
|
||||
if (mName == null) {
|
||||
db = SQLiteDatabase.create(null);
|
||||
} else {
|
||||
db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
|
||||
}
|
||||
|
||||
int version = db.getVersion();
|
||||
if (version != mNewVersion) {
|
||||
db.beginTransaction();
|
||||
try {
|
||||
if (version == 0) {
|
||||
onCreate(db);
|
||||
} else {
|
||||
if (version > mNewVersion) {
|
||||
onDowngrade(db, version, mNewVersion);
|
||||
} else {
|
||||
onUpgrade(db, version, mNewVersion);
|
||||
}
|
||||
}
|
||||
db.setVersion(mNewVersion);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(db);
|
||||
success = true;
|
||||
return db;
|
||||
} finally {
|
||||
mIsInitializing = false;
|
||||
if (success) {
|
||||
if (mDatabase != null) {
|
||||
try { mDatabase.close(); } catch (Exception e) { }
|
||||
mDatabase.unlockPrimaryConnection();
|
||||
}
|
||||
mDatabase = db;
|
||||
} else {
|
||||
if (mDatabase != null) {
|
||||
mDatabase.unlockPrimaryConnection();
|
||||
}
|
||||
if (db != null) db.close();
|
||||
}
|
||||
public SQLiteDatabase getWritableDatabase() {
|
||||
synchronized (this) {
|
||||
return getDatabaseLocked(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,45 +159,95 @@ public abstract class SQLiteOpenHelper {
|
||||
* @return a database object valid until {@link #getWritableDatabase}
|
||||
* or {@link #close} is called.
|
||||
*/
|
||||
public synchronized SQLiteDatabase getReadableDatabase() {
|
||||
public SQLiteDatabase getReadableDatabase() {
|
||||
synchronized (this) {
|
||||
return getDatabaseLocked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private SQLiteDatabase getDatabaseLocked(boolean writable) {
|
||||
if (mDatabase != null) {
|
||||
if (!mDatabase.isOpen()) {
|
||||
// darn! the user closed the database by calling mDatabase.close()
|
||||
// Darn! The user closed the database by calling mDatabase.close().
|
||||
mDatabase = null;
|
||||
} else {
|
||||
return mDatabase; // The database is already open for business
|
||||
} else if (!writable || !mDatabase.isReadOnly()) {
|
||||
// The database is already open for business.
|
||||
return mDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsInitializing) {
|
||||
throw new IllegalStateException("getReadableDatabase called recursively");
|
||||
throw new IllegalStateException("getDatabase called recursively");
|
||||
}
|
||||
|
||||
try {
|
||||
return getWritableDatabase();
|
||||
} catch (SQLiteException e) {
|
||||
if (mName == null) throw e; // Can't open a temp database read-only!
|
||||
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
|
||||
}
|
||||
|
||||
SQLiteDatabase db = null;
|
||||
SQLiteDatabase db = mDatabase;
|
||||
try {
|
||||
mIsInitializing = true;
|
||||
String path = mContext.getDatabasePath(mName).getPath();
|
||||
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY,
|
||||
mErrorHandler);
|
||||
if (db.getVersion() != mNewVersion) {
|
||||
throw new SQLiteException("Can't upgrade read-only database from version " +
|
||||
db.getVersion() + " to " + mNewVersion + ": " + path);
|
||||
|
||||
if (db != null) {
|
||||
if (writable && db.isReadOnly()) {
|
||||
db.reopenReadWrite();
|
||||
}
|
||||
} else if (mName == null) {
|
||||
db = SQLiteDatabase.create(null);
|
||||
} else {
|
||||
try {
|
||||
if (DEBUG_STRICT_READONLY && !writable) {
|
||||
final String path = mContext.getDatabasePath(mName).getPath();
|
||||
db = SQLiteDatabase.openDatabase(path, mFactory,
|
||||
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
|
||||
} else {
|
||||
db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
|
||||
}
|
||||
} catch (SQLiteException ex) {
|
||||
if (writable) {
|
||||
throw ex;
|
||||
}
|
||||
Log.e(TAG, "Couldn't open " + mName
|
||||
+ " for writing (will try read-only):", ex);
|
||||
final String path = mContext.getDatabasePath(mName).getPath();
|
||||
db = SQLiteDatabase.openDatabase(path, mFactory,
|
||||
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
|
||||
}
|
||||
}
|
||||
|
||||
final int version = db.getVersion();
|
||||
if (version != mNewVersion) {
|
||||
if (db.isReadOnly()) {
|
||||
throw new SQLiteException("Can't upgrade read-only database from version " +
|
||||
db.getVersion() + " to " + mNewVersion + ": " + mName);
|
||||
}
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
if (version == 0) {
|
||||
onCreate(db);
|
||||
} else {
|
||||
if (version > mNewVersion) {
|
||||
onDowngrade(db, version, mNewVersion);
|
||||
} else {
|
||||
onUpgrade(db, version, mNewVersion);
|
||||
}
|
||||
}
|
||||
db.setVersion(mNewVersion);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
onOpen(db);
|
||||
Log.w(TAG, "Opened " + mName + " in read-only mode");
|
||||
|
||||
if (db.isReadOnly()) {
|
||||
Log.w(TAG, "Opened " + mName + " in read-only mode");
|
||||
}
|
||||
|
||||
mDatabase = db;
|
||||
return mDatabase;
|
||||
return db;
|
||||
} finally {
|
||||
mIsInitializing = false;
|
||||
if (db != null && db != mDatabase) db.close();
|
||||
if (db != null && db != mDatabase) {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user