在android中和SQLite数据库相关的几大类有SQLiteOpenHelper、SQLiteDatabase、SQLiteConnection、SQLiteSession,本文主要从1.数据库的创建过程;2.对数据库的CRUP操作;两个方面来分析android数据库运行机制。先概述下这几个类,让大家对它们有个了解;
SQLiteOpenHelper
封装管理数据库的创造和版本管理类,主要封装了数据库的创建和获取的方法,一般继承该类实现onCreate()、onUpdate()方法
,在onCreate创建数据库,在onUpdate进行数据库升级操作。还有onConfigure()、onDowngrade()、onOpen()等方法。
线程数据库会话。线程操作数据必须通过SQLiteSession来获取一个SQLiteConnection连接,每个线程都有自己的SQLiteSession对象,属于线程私有数据,需要通过SQLiteDatabase.getThreadSession()静态函数来获取。线程第一次调用getThreadSession()便会创建一个SQLiteSession对象。例如:进程PID=911中tid=911的线程第一次调用SQLiteDatabase.getThreadSession()时,便会创建一个SQLiteSession对象。tid=912的线程第一次调用SQLiteDatabase.getThreadSession()时,同样也会创建一个SQLiteSession对象。
SQLiteConnection(native)
数据库操作最终通过native SQLiteConnection对象调用sqlite库接口来完成。每个Java层SQLiteConnection对象,都对应有一个native层SQLiteConnection对象,native层SQLiteConnection对象的地址保存在java SQLiteConnection.mConnectionPtr中。
对数据库相关的几个类有了一定的认识之后,我们下面从1.数据库的创建过程;2.对数据库的CRUP操作;两个方面来分析android数据库运行机制。
数据库的创建
两个方法:getReadableDatabase()、getWritableDatabase()。需要注意的一点是这两个方法都加锁,是线程安全的。这两个方法最终调用getDatabaseLocked(boolean writable):
SQLiteOpenHelper
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) { // 判断数据库是否已经关闭
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) { //判断数据库是否符合要求,如果数据库可读可写则返回,即!mDatabase.isReadOnly()一直为true
// The database is already open for business.
return mDatabase;
}
}
// 正在初始化中
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) { // 数据库不为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 {
// 通过mContext.openOrCreateDatabase创建数据库,其实还是调用SQLiteDatabase.openDatabase(..)创建数据库
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 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);
}
}
// 调用onConfigure
onConfigure(db);
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 {
// 当第一次创建数据库时DataBase的版本为0,会调用onCreate()方法
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()方法
onOpen(db);
if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;
return db;
} finally {
mIsInitializing = false;
// 数据库创建失败时,进行close操作
if (db != null && db != mDatabase) {
db.close();
}
}
}
这里调用了SQLiteDatabase.openDatabase(..)创建数据库
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
db.open();
return db;
}
可以看到new一个SQLiteDatabase对象,并调用open(),再返回该数据库对象,先看open()函数:
private void open() {
try {
try {
openInner();
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
openInner();
}
} catch (SQLiteException ex) {
// ....
}
}
private void openInner() {
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
}
}
// 可以看到调用SQLiteConnectionPool.open(mConfigurationLocked):
public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
if (configuration == null) {
throw new IllegalArgumentException("configuration must not be null.");
}
// Create the pool.
SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
pool.open(); // might throw
return pool;
}
// 可以看到其中是创建一个SQLiteConnectionPool,并且调用open操作:
// Might throw
private void open() {
// Open the primary connection.
// This might throw if the database is corrupt.
mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
true /*primaryConnection*/); // might throw
// ...
}
// 可以看到创建了主连接mAvailablePrimaryConnection:
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
boolean primaryConnection) {
final int connectionId = mNextConnectionId++;
return SQLiteConnection.open(this, configuration,
connectionId, primaryConnection); // might throw
}
// 调用了SQLiteConnection.open()创建主连接:
static SQLiteConnection open(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
SQLiteConnection connection = new SQLiteConnection(pool, configuration,
connectionId, primaryConnection);
try {
connection.open();
return connection;
} catch (SQLiteException ex) {
connection.dispose(false);
throw ex;
}
}
private void open() {
mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
mConfiguration.label,
SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
setPageSize();
setForeignKeyModeFromConfiguration();
setWalModeFromConfiguration();
setJournalSizeLimit();
setAutoCheckpointInterval();
setLocaleFromConfiguration();
// Register custom functions.
final int functionCount = mConfiguration.customFunctions.size();
for (int i = 0; i < functionCount; i++) {
SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
nativeRegisterCustomFunction(mConnectionPtr, function);
}
}
// 可以看到最终调用了nativeOpen打开一个主数据库连接,并且设置各自sqlite的属性。
创建流程:
总结:
可以看出,创建一个数据库对象,会创建一个数据库连接池,并且会创建出一个主连接。数据库连接池用于管理数据库连接对象。而数据库连接SQLiteConnection则在其中包装了native的sqlite3对象,数据库sql语句最终会通过sqlite3对象执行。
数据库的CRUP操作
那么我们就以Insert为例,理一下整个过程。
SqliteDatabase的insert方法
public long insert(String table, StringnullColumnHack, ContentValues values) {
try {
return insertWithOnConflict(table, nullColumnHack, values,CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
Ok... 继续往下面分析insertWithOnConflict方法
public long insertWithOnConflict(Stringtable, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
...
sql.append(')');
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(),bindArgs);
try {
returnstatement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
这个方法首先构造insert的sql语句,然后调用statement的executeInsert()方法,那继续跟下去
frameworks\base\core\java\android\database\sqlite\SQLiteStatement.java
public long executeInsert() {
...
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
...
}
这个方法比较重要,分成两步来分析:
首先分析getSession()方法,
然后再去看看executeForLastInsertedRowId方法。
getSession分析
frameworks\base\core\java\android\database\sqlite\SQLiteProgram.java
protected final SQLiteSession getSession(){
return mDatabase.getThreadSession();
}
调用的是SqliteDatabase.getThreadSession()方法,继续分析
SQLiteSession getThreadSession() {
return mThreadSession.get(); // initialValue() throws if database closed
}
调用的是mThreadSession.get(),看看mThreadSession是什么东西
private final ThreadLocal<SQLiteSession>mThreadSession = new ThreadLocal<SQLiteSession>() {
protected SQLiteSession initialValue() {
return createSession();
}
};
SQLiteSession createSession() {
final SQLiteConnectionPool pool;
synchronized (mLock) {
pool = mConnectionPoolLocked;
}
return new SQLiteSession(pool);
}
public SQLiteSession(SQLiteConnectionPoolconnectionPool) {
if (connectionPool == null) {
throw new IllegalArgumentException("connectionPool must not benull");
}
mConnectionPool = connectionPool;
}
嗯,非常明白,这是一个
ThreadLocal
类型的变量,
ThreadLocal
,
Java
提供的一个用于多线程之间隔离数据的东西,避免多线程访问同一个变量导致冲突。
也就是ThreadLocal是为每个线程保存一份变量,这样就不会引起冲突了。这个ThreadLocal如果有不理解的,可以百度下ThreadLocal的使用。
Ok..理解完ThreadLocal之后,继续看createSession方法
它是使用mConnectionPoolLocked这么个SQLiteConnectionPool变量来实例化一个SQLiteSession对象。而这个mConnectionPoolLocked对象是SqliteDatabase打开的时候实例化的,也就是说一个database只有一个这样连接。
那么,这里我们可以理清楚一个关系
a.
一个线程会对应一个SqliteSession对象,这个是用ThreadLocal对象来维持的。
b.
一个数据库对应唯一一个sqliteDabase对象,以及唯一一个SQLiteConnectionPool对象,然后各个线程之间共享这个SQLiteConnectionPool对象。
Ok..理清楚这几个关系之后,返回去分析executeForLastInsertedRowId方法
executeForLastInsertedRowId
frameworks\base\core\java\android\database\sqlite\SQLiteSession.java
public long executeForLastInsertedRowId(Stringsql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
...
acquireConnection(sql, connectionFlags, cancellationSignal); // mightthrow
try {
return mConnection.executeForLastInsertedRowId(sql, bindArgs,
cancellationSignal); //might throw
} finally {
releaseConnection(); // might throw
}
}
这个方法主要分析两个操作
acquireConnection
releaseConnection
因为中间的 mConnection.executeForLastInsertedRowId 语句主要是通过 Jni 往底层调用,插入数据库数据,不是今天我们讨论的话题。
Ok.. 那这两个操作,一个个来分析
acquireConnection
frameworks\base\core\java\android\database\sqlite\SQLiteSession.java
private void acquireConnection(String sql,int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // mightthrow
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
Ok..
首先会去判断当前线程的
Session
里面的
mConnection
是否为
null,
如果不为
null
,就只是简单的把连接个数
mConnectionUseCount
加
1
如果为null,也就是当前线程的Session没有连接数据库,那么就要去申请一个连接。
所以这里的逻辑特别重要,就是一个对于一个线程而已,它只会去获取一次数据库连接。即使你调用再多的beginTranscation以及query,第一次调用的时候会去获取连接,以后就是让mConnectionUseCount 加1;
当然,你使用beginTranscation需要手动调用endTranscation,不然不会去释放连接。
调用其他的,比如query,insert之类的,不需要手动释放。系统会帮你去调用释放连接。
Ok.. 接下来分析mConnectionPool.acquireConnection的流程.
调用mConnectionPool.acquireConnection,顾名思义,它是向连接池申请一个连接;根据之前的分析,这个连接池是唯一的,是多个线程之间共享的。
frameworks\base\core\java\android\database\sqlite\SQLiteConnectionPool.java
public SQLiteConnectionacquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
return waitForConnection(sql, connectionFlags, cancellationSignal);
}
private SQLiteConnectionwaitForConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
final boolean wantPrimaryConnection =
(connectionFlags &CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
final ConnectionWaiter waiter;
final int nonce;
synchronized (mLock) {
throwIfClosedLocked();
// Abort if canceled.
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
// Try to acquire a connection.
SQLiteConnection connection = null;
if (!wantPrimaryConnection) {
//尝试获取非主连接
connection =tryAcquireNonPrimaryConnectionLocked(
sql, connectionFlags);// might throw
}
if (connection == null) {
//尝试去获取主连接
connection =tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
}
if (connection != null) {
//获取到连接后,返回连接
return connection;
}
//没有获取到连接,新建一个waiter对象,加入等待队列
// No connections available.
Enqueue a waiter in priority order.
final int priority = getPriority(connectionFlags);
final long startTime = SystemClock.uptimeMillis();
waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,
priority,wantPrimaryConnection, sql, connectionFlags);
ConnectionWaiter predecessor = null;
ConnectionWaiter successor = mConnectionWaiterQueue;
while (successor != null) {
if (priority >successor.mPriority) {
waiter.mNext = successor;
break;
}
predecessor = successor;
successor = successor.mNext;
}
if (predecessor != null) {
predecessor.mNext = waiter;
} else {
mConnectionWaiterQueue =waiter;
}
nonce = waiter.mNonce;
}
....
//把当前线程睡眠30s,然后尝试去获取连接,如果没有获取到连接,则打印当前线程的等待时间。
try {
// Park the thread until a connection is assigned or the pool is closed.
// Rethrow an exception from the wait, if we got one.
long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
//30s
long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
for (;;) {
// Detect and recover fromconnection leaks.
if(mConnectionLeaked.compareAndSet(true, false)) {
synchronized (mLock) {
wakeConnectionWaitersLocked();
}
}
// Wait to be unparked (mayalready have happened), a timeout, or interruption.
LockSupport.parkNanos(this,busyTimeoutMillis * 1000000L);
//当前线程停止30s
// Clear the interrupted flag,just in case.
Thread.interrupted();
// Check whether we are donewaiting yet.
synchronized (mLock) {
throwIfClosedLocked();
final SQLiteConnectionconnection = waiter.mAssignedConnection;
final RuntimeException ex =waiter.mException;
if (connection != null ||ex != null) {
recycleConnectionWaiterLocked(waiter);
if (connection != null){
return connection;
}
throw ex; // rethrow!
}
final long now =SystemClock.uptimeMillis();
if (now <nextBusyTimeoutTime) {
busyTimeoutMillis = now- nextBusyTimeoutTime;
} else {
logConnectionPoolBusyLocked(now- waiter.mStartTime, connectionFlags);
//打印出当前线程等待的时间
busyTimeoutMillis =CONNECTION_POOL_BUSY_MILLIS;
nextBusyTimeoutTime =now + busyTimeoutMillis;
}
}
}
} finally {
...
}
}
这个方法比较多,我在里面做了一点注释,大概包括下面几个步骤。同时要明确一个概念,主连接和非主连接。
其实他们没有本质的区别,主连接是一定有的,在初始化的时候就实例化完毕;而非主连接是一个连接集合,也就是说非主连接可以有很多个。不过一般有个最大大小,我们可以配置的。
Ok...明白这个概念之后,讲讲步骤
如果没有指定要获取主连接的话,首先尝试获取非主连接
private SQLiteConnectiontryAcquireNonPrimaryConnectionLocked(
String sql, int connectionFlags) {
// Try to acquire the next connection in the queue.
SQLiteConnection connection;
final int availableCount = mAvailableNonPrimaryConnections.size();
if (availableCount > 1 && sql != null) {
// If we have a choice, then prefer a connection that has the
// prepared statement in its cache.
for (int i = 0; i <availableCount; i++) {
connection =mAvailableNonPrimaryConnections.get(i);
if(connection.isPreparedStatementInCache(sql)) {
mAvailableNonPrimaryConnections.remove(i);
finishAcquireConnectionLocked(connection,connectionFlags); // might throw
return connection;
}
}
}
if (availableCount > 0) {
// Otherwise, just grab the next one.
connection =mAvailableNonPrimaryConnections.remove(availableCount - 1);
finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow
return connection;
}
// Expand the pool if needed.
int openConnections = mAcquiredConnections.size();
if (mAvailablePrimaryConnection != null) {
openConnections += 1;
}
if (openConnections >= mMaxConnectionPoolSize) {
return null;
}
connection = openConnectionLocked(mConfiguration,
false /*primaryConnection*/);// might throw
finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow
return connection;
}
获取非主连接的时候,首先会判断已有的连接中有没有相同的
sql
,如果有的话,就直接返回这个连接。
然后如果第一步没有成功的话,比如传入的 sql 是 null ,那么就会尝试获取队列里面的最后一个连接,然后返回。
如果第二步也没有成功,那么它会尝试去扩充非主连接集合
但是,它会去判断是否超过了最大的连接数,是已经到达最大的连接数,那么就返回null
if (openConnections >=mMaxConnectionPoolSize) {
return null;
}
如果还没有到达最大连接数,那么就把连接放入非主连接集合,然后返回这个扩容的连接。
如果没有获取到非主连接,或者指定要获取主连接,那么就要去尝试获取主连接。
private SQLiteConnectiontryAcquirePrimaryConnectionLocked(int connectionFlags) {
// If the primary connection is available, acquire it now.
SQLiteConnection connection = mAvailablePrimaryConnection;
if (connection != null) {
mAvailablePrimaryConnection = null;
finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow
return connection;
}
// Make sure that the primary connection actually exists and has justbeen acquired.
for (SQLiteConnection acquiredConnection :mAcquiredConnections.keySet()) {
if (acquiredConnection.isPrimaryConnection()) {
return null;
}
}
// Uhoh.
No primaryconnection!
Either this is the firsttime we asked
// for it, or maybe it leaked?
connection = openConnectionLocked(mConfiguration,
true /*primaryConnection*/); //might throw
finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow
return connection;
}
由于主连接只有一个,所以它用一个变量来表示
--- mAvailablePrimaryConnection
如果主连接为null就返回主连接,并把主连接设置成null,也就是主连接被占用。
if (connection != null) {
mAvailablePrimaryConnection = null;
finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow
return connection;
}
如果主连接为
null
,那么它会去查看是否已经有人获取了主连接,如果是,那么返回
null
;这样做就是确保主连接是存在的。
如果经过上面一步确认,还没有返回,那么说明主连接没有创建;这个一般是不可能的,因为主连接时数据库初始化的时候创建的。这个一般是第一次访问,或者出现了程序异常。那么就新建一个主连接,然后返回。
如果获取到连接,那么返回这个连接
if (connection != null) {
return connection;
}
如果没有获取到连接,那么新建一个
waiter
对象,并进入等待队列。
e.
线程进入死循环,不断休眠(30s),然后重新获取连接。直到获取到连接后返回。否则,记录下当前线程等待的时间。不过这里要注意,这里打印的等待时间并不包括系统睡眠的时间。比如,下午1点进入等待,2点-4点手机睡眠,那么到5点的时候打印,只能算两个小时。
打印的日志为(最后的总结会对此bug做解析):
04-2314:25:48.522 W/SQLiteConnectionPool( 3681): The connection pool for database'/data/user/0/com.android.providers.contacts/databases/contacts2.db' has beenunable to grant a connection to thread 371 (ContactsProviderWorker) with flags0x1 for 30.000002 seconds.
04-23 14:25:48.522 W/SQLiteConnectionPool( 3681):Connections: 0 active, 1 idle, 0 available.
Ok...至此,整个申请数据库连接过程分析基本完毕,下面分析释放的过程
释放连接
frameworks\base\core\java\android\database\sqlite\SQLiteSession.java
finally {
releaseConnection();// might throw
}
private void releaseConnection() {
assert mConnection != null;
assert mConnectionUseCount > 0;
if (--mConnectionUseCount == 0) {
try {
mConnectionPool.releaseConnection(mConnection); // might throw
} finally {
mConnection = null;
}
}
}
首先会去判断当前使用数是否为大于
0
,还记得前面我们说过如果线程第一次申请连接,那么就去申请,然后使用数
+1
;但是如果线程已经拥有了连接,那么我们只是简单的把连接数
+1
。
所以,这里要判断这个连接数是否 >0
然后把连接数减去 1 ,看是否等于 0 ;这是什么意思呢?就是说当前线程已经没有使用数据库的操作了。
如果减去 1 之后 >0 ,那么说明当前线程还要使用这个连接操作数据库,还不能释放。
这里也可以看出,申请连接和释放连接一定要是一一对应的。申请了,一定要释放。当然,对于应用程序来说,只有 beginTranscation 需要手动去释放,也就是调用 endTranscation ,而且必须要调用 ( 一般写在 finally 里面 ) 。
Ok... 一切都 ok 的话,正式去释放连接
mConnectionPool.releaseConnection(mConnection);// might throw
正式释放连接
frameworks\base\core\java\android\database\sqlite\SQLiteConnectionPool.java
public voidreleaseConnection(SQLiteConnection connection) {
synchronized (mLock) {
....
if (!mIsOpen) {
closeConnectionAndLogExceptionsLocked(connection);
} else if (connection.isPrimaryConnection()) {
if(recycleConnectionLocked(connection, status)) {
assert mAvailablePrimaryConnection== null;
mAvailablePrimaryConnection= connection;
}
wakeConnectionWaitersLocked();
} else if (mAvailableNonPrimaryConnections.size() >=mMaxConnectionPoolSize - 1) {
closeConnectionAndLogExceptionsLocked(connection);
} else {
if(recycleConnectionLocked(connection, status)) {
mAvailableNonPrimaryConnections.add(connection);
}
wakeConnectionWaitersLocked();
}
}
}
这里会去根据当前连接是否是主连接而选择释放方法,然后通知那些正在等待的线程(waiter)去获取连接。
总结
数据库使用报错1
在android开发过程中,突然碰到了这个错误,数据库连接分配不到,日志如下:
W/SQLiteConnectionPool( 3681): Theconnection pool for database '/data/user/0/com.android.providers.contacts/databases/contacts2.db'has been unable to grant a connection to thread 371 (ContactsProviderWorker)with flags 0x1 for 30.000002 seconds.W/SQLiteConnectionPool( 3681): Connections:0 active, 1 idle, 0 available. 1.这个bug不是由于在beginTranscation里面执行一个execuSql(sql)所导致的,因为在同一个线程里面,即使申请两次数据库连接,那也只是让使用数+1而已。它只会去申请一个连接。除非beginTranscation和execuSql(sql) 不在一个线程,另外开线程去execuSql(sql).
2.那么怎么会导致这个bug呢?
a. 时间过长的操作,某个操作持续很长时间,那么其他线程等待的时候就会打印这个信息。不过,一般不会等待太久。因为一般不会有这么长时间的数据库操作。
b. 忘记调用endTranscation,如前面所说,只有beginTranscation需要程序员手动去调用endTransction来释放连接,其他操作不需要。
那么,有个时候会疏忽忘记调用endTransction了。
或者endTranscation没有放在finally里面,导致出现异常而没有调用endTransction
c.
死锁,这个比较复杂,需要具体问题具体分析。
如果出现死锁,线程互相等待,这样也没有去释放连接;那么后面的线程自然也拿不到数据库连接了。
(注:我这边出现这个问题的原因是在子线程对数据库执行了耗时操作占用了数据库连接,而此时在主线程又有一个方法在获取数据库连接,此时就需要等子线程对数据库操作完成释放数据库连接,由于是在主线程执行的操作,导致ANR)