注意事项:
如果 SQLiteOpenHelper 使用的是单例,SQLiteDatabase 对 CRUD 操作都会加上同一个锁(因为是db文件,所以精确至数据库级). 所以同一时间只能进行一项操作,多线程读写几乎是无用功;
enableWriteAheadLogging() 方法可以使得多线程并发查询可行,但默认没有开启该功能;
android sqlite 不支持多 SQLiteOpenHelper 实例、多线程并发写入操作,会抛出异常“database is locked”;
插入单条数据不需要开启事务;
全局引用一个 SQLiteDatabase 时,是否存在不主动调用 close() 但被动 close() 的情况?这种情况下获取单例的 SQLiteOpenHelper 的写法就需要特别注意;
SQLiteCursor 的获取与触发对数据库的真正查询是分离的,获取 SQLiteCursor 后、 查询数据库前数据库的状态发生变化(如“被关闭”),是否会出现问题?
执行 sql 语句的正确方式:
db.beginTransaction();
try {
...
// 注意该语句放在 try 语句块的最后,表明最终的操作成功
db.setTransactionSuccessful();
} finally {
// 注意该语句放在 finally 语句块中,确定进行 roll back 或 commit
db.endTransaction();
}
一、首先看一个有意思的方法: enableWriteAheadLogging()
/**
* 该方法可以启用多线程下并发执行查询的功能
* 该方法的关键操作只有两句:
* mConnectionPool = new DatabaseConnectionPool(this);
* setJournalMode(mPath, "WAL");
*
* This method enables parallel execution of queries from multiple threads on the same database.
* It does this by opening multiple handles to the database and using a different
* database handle for each query.
*
* 在默认情况下,如果有一个更新数据库的事务存在,则别的查询事务会被阻塞
* 该方法启用后,查询的事务会并发执行,返回旧数据
*
* <p>
* If a transaction is in progress on one connection handle and say, a table is updated in the
* transaction, then query on the same table on another connection handle will block for the
* transaction to complete. But this method enables such queries to execute by having them
* return old version of the data from the table. Most often it is the data that existed in the
* table prior to the above transaction updates on that table.
* <p>
* Maximum number of simultaneous handles used to execute queries in parallel is
* dependent upon the device memory and possibly other properties.
* <p>
* After calling this method, execution of queries in parallel is enabled as long as this
* database handle is open. To disable execution of queries in parallel, database should
* be closed and reopened.
*
* 一旦调用该方法后,多线程并发执行就会变得可用. 要关闭该功能,必需对数据库进行关闭和重打开操作.
*
* <p>
* If a query is part of a transaction, then it is executed on the same database handle the
* transaction was begun.
* <p>
* If the database has any attached databases, then execution of queries in paralel is NOT
* possible. In such cases, a message is printed to logcat and false is returned.
* <p>
* This feature is not available for :memory: databases. In such cases,
* a message is printed to logcat and false is returned.
* <p>
* A typical way to use this method is the following:
* <pre>
* SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
* CREATE_IF_NECESSARY, myDatabaseErrorHandler);
* db.enableWriteAheadLogging();
* </pre>
* <p>
* Writers should use {@link #beginTransactionNonExclusive()} or
* {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
* to start a trsnsaction.
* Non-exclusive mode allows database file to be in readable by threads executing queries.
* </p>
*
* @return true if write-ahead-logging is set. false otherwise
*/
public boolean enableWriteAheadLogging() {
// make sure the database is not READONLY. WAL doesn't make sense for readonly-databases.
if (isReadOnly()) {
return false;
}
// acquire lock - no that no other thread is enabling WAL at the same time
lock();
try {
if (mConnectionPool != null) {
// already enabled, 开启这个功能后会创建 mConnectionPool
return true;
}
if (mPath.equalsIgnoreCase(MEMORY_DB_PATH)) {
// 对于只存在于内存中的数据库不起作用
Log.i(TAG, "can't enable WAL for memory databases.");
return false;
}
// make sure this database has NO attached databases because sqlite's write-ahead-logging
// doesn't work for databases with attached databases
if (mHasAttachedDbs) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG,
"this database: " + mPath + " has attached databases. can't enable WAL.");
}
return false;
}
mConnectionPool = new DatabaseConnectionPool(this);
setJournalMode(mPath, "WAL");
return true;
} finally {
unlock();
}
}
二、添加数据操作(C:增)
1. 第一种添加数据方式:调用 SQLiteDatabase 中的 insert() 方法
/**
* 在 values==null 或者 values.size()==0 的情况下 nullColumnHack 才会起作用,
* nullColumnHack 的作用是插入数据时 nullColumnHack 所在列的 value 为 NULL
*
* Convenience method for inserting a row into the database.
*
* @param table the table to insert the row into
* @param nullColumnHack optional; may be <code>null</code>.
* SQL doesn't allow inserting a completely empty row without
* naming at least one column name. If your provided <code>values</code> is
* empty, no column names are known and an empty row can't be inserted.
* If not set to null, the <code>nullColumnHack</code> parameter
* provides the name of nullable column name to explicitly insert a NULL into
* in the case where your <code>values</code> is empty.
* @param values this map contains the initial column values for the
* row. The keys should be the column names and the values the
* column values
* @return the row ID of the newly inserted row, or -1 if an error occurred
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
// 注意这里采用 CONFLICT_NONE
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
2. 第二种添加数据方式:调用 SQLiteDatabase 中的 replace() 方法
/**
* Convenience method for replacing a row in the database.
*
* @param table the table in which to replace the row
* @param nullColumnHack optional; may be <code>null</code>.
* SQL doesn't allow inserting a completely empty row without
* naming at least one column name. If your provided <code>initialValues</code> is
* empty, no column names are known and an empty row can't be inserted.
* If not set to null, the <code>nullColumnHack</code> parameter
* provides the name of nullable column name to explicitly insert a NULL into
* in the case where your <code>initialValues</code> is empty.
* @param initialValues this map contains the initial column values for
* the row.
* @return the row ID of the newly inserted row, or -1 if an error occurred
*/
public long replace(String table, String nullColumnHack, ContentValues initialValues) {
try {
// 注意此处使用的是 CONFLICT_REPLACE
return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + initialValues, e);
return -1;
}
}
/**
* SQliteDatabase 仅仅只对 sql 语句进行拼装, sql 语句的最终执行都是通过 SQLiteStatement
* insert() 和 replace() 都会调用该方法
* General method for inserting a row into the database.
*
* @param table the table to insert the row into
* @param nullColumnHack optional; may be <code>null</code>.
* SQL doesn't allow inserting a completely empty row without
* naming at least one column name. If your provided <code>initialValues</code> is
* empty, no column names are known and an empty row can't be inserted.
* If not set to null, the <code>nullColumnHack</code> parameter
* provides the name of nullable column name to explicitly insert a NULL into
* in the case where your <code>initialValues</code> is empty.
* @param initialValues this map contains the initial column values for the
* row. The keys should be the column names and the values the
* column values
* @param conflictAlgorithm for insert conflict resolver
* @return the row ID of the newly inserted row
* OR the primary key of the existing row if the input param 'conflictAlgorithm' =
* {@link #CONFLICT_IGNORE}
* OR -1 if any error
*/
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
// 开始拼装 sql 语句
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
// 对于 insert() conflictAlgorithm 为 CONFLICT_NONE =0,取出的是空字符串
// 对于 replace() conflictAlgorithm 为 CONFLICT_REPLACE =5,取出的是 " OR REPLACE ",意味着如果不存在就插入,存在就替换
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && initialValues.size() > 0) ? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
for (String colName : initialValues.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = initialValues.get(colName);
}
sql.append(')');
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
// nullColumnHack 在 initialValues==null 或者 initialValues.size()==0 的情况下才起作用
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
// 拼装 sql 语句结束
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
// SQliteDatabase 仅仅只对 sql 语句进行拼装, sql 语句的最终执行都是通过 SQLiteStatement
return statement.executeInsert();
} catch (SQLiteDatabaseCorruptException e) {
// 数据库文件已损坏
onCorruption();
throw e;
} finally {
statement.close();
}
}
3. SQLiteStatement 调用 native 方法执行数据插入
/**
* Execute this SQL statement and return the ID of the row inserted due to this call.
* The SQL statement should be an INSERT for this to be a useful call.
*
* @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
*
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public long executeInsert() {
try {
// 缓存 STATEMENT_UPDATE 和 STATEMENT_BEGIN 类型的 sql 语句
saveSqlAsLastSqlStatement();
// 执行每个 sql 语句前都会执行该方法
// 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
acquireAndLock(WRITE);
// 调用 native 方法,进行数据插入
return native_executeInsert();
} finally {
// 插入数据完成后释放 mLock 锁
releaseAndUnlock();
}
}
4. 调用 native_executeInsert() 插入数据前先获取 mLock 锁
/**
* 执行每个 sql 语句前都会执行该方法
* 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
*
* Called before every method in this class before executing a SQL statement,
* this method does the following:
* <ul>
* <li>make sure the database is open</li>
* <li>get a database connection from the connection pool,if possible</li>
* <li>notifies {@link BlockGuard} of read/write</li>
* <li>if the SQL statement is an update, start transaction if not already in one.
* otherwise, get lock on the database</li>
* <li>acquire reference on this object</li>
* <li>and then return the current time _after_ the database lock was acquired</li>
* </ul>
* <p>
* This method removes the duplicate code from the other public
* methods in this class.
*/
private long acquireAndLock(boolean rwFlag) {
mState = 0;
// use pooled database connection handles for SELECT SQL statements
mDatabase.verifyDbIsOpen();
// 获取的 db 仍然是 mDatabase
SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0) ? mDatabase.getDbConnection(mSql) : mDatabase;
// use the database connection obtained above
mOrigDb = mDatabase;
mDatabase = db;
setNativeHandle(mDatabase.mNativeHandle);
if (rwFlag == WRITE) {
BlockGuard.getThreadPolicy().onWriteToDisk();
} else {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
/*
* Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
* we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
* beginTransaction() methods in SQLiteDatabase call lockForced() before
* calling execSQL("BEGIN transaction").
*/
if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
if (!mDatabase.isDbLockedByCurrentThread()) {
// transaction is NOT started by calling beginTransaction() methods in
// SQLiteDatabase
mDatabase.setTransactionUsingExecSqlFlag();
}
} else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_UPDATE) {
// got update SQL statement. if there is NO pending transaction, start one
if (!mDatabase.inTransaction()) {
mDatabase.beginTransactionNonExclusive();
mState = TRANS_STARTED;
}
}
// do I have database lock? if not, grab it.
if (!mDatabase.isDbLockedByCurrentThread()) {
// 当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
mDatabase.lock(mSql);
mState = LOCK_ACQUIRED;
}
acquireReference();
long startTime = SystemClock.uptimeMillis();
mDatabase.closePendingStatements();
compileAndbindAllArgs();
return startTime;
}
5. 当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
mDatabase.lock(mSql);
void lock(String sql) {
lock(sql, false);
}
private void lock(String sql, boolean forced) {
// make sure this method is NOT being called from a 'synchronized' method
if (Thread.holdsLock(this)) {
Log.w(TAG, "don't lock() while in a synchronized method");
}
verifyDbIsOpen();
if (!forced && !mLockingEnabled){
// 如果是单线程操作数据库,则是线程安全的,无需 lock
return;
}
boolean done = false;
long timeStart = SystemClock.uptimeMillis();
while (!done) { // 当前线程循环等待获取 mLock 锁,直到获取为止
try {
// wait for 30sec to acquire the lock
done = mLock.tryLock(LOCK_WAIT_PERIOD, TimeUnit.SECONDS);
if (!done) {
// lock not acquired in NSec. print a message and stacktrace saying the lock
// has not been available for 30sec.
Log.w(TAG, "database lock has not been available for " + LOCK_WAIT_PERIOD +
" sec. Current Owner of the lock is " + mLock.getOwnerDescription() +
". Continuing to wait in thread: " + Thread.currentThread().getId());
}
} catch (InterruptedException e) {
// interruption 的情况下会继续进入下一个循环等待
// ignore the interruption
}
}
if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
if (mLock.getHoldCount() == 1) {
// Use elapsed real-time since the CPU may sleep when waiting for IO
mLockAcquiredWallTime = SystemClock.elapsedRealtime();
mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
}
}
if (sql != null) {
if (ENABLE_DB_SAMPLE) {
logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX);
}
}
}
6. 插入数据完成后释放 mLock 锁
/**
* this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
*/
private void releaseAndUnlock() {
releaseReference();
if (mState == TRANS_STARTED) {
try {
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
} else if (mState == LOCK_ACQUIRED) {
// beginTransaction() 不会走这里, 隐式事务会走这里
mDatabase.unlock();
}
if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
DatabaseUtils.STATEMENT_COMMIT ||
(mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
DatabaseUtils.STATEMENT_ABORT) {
mDatabase.resetTransactionUsingExecSqlFlag();
}
clearBindings();
// release the compiled sql statement so that the caller's SQLiteStatement no longer
// has a hard reference to a database object that may get deallocated at any point.
release();
// restore the database connection handle to the original value
mDatabase = mOrigDb;
setNativeHandle(mDatabase.mNativeHandle);
}
三、删除数据操作(D:删)
1. 调用 SQLiteDatabase 中的 delete() 方法
/**
* 返回值为被删除的总行数
*
* Convenience method for deleting rows in the database.
*
* @param table the table to delete from
* @param whereClause the optional WHERE clause to apply when deleting.
* Passing null will delete all rows.
* @return the number of rows affected if a whereClause is passed in, 0
* otherwise. To remove all rows and get a count pass "1" as the
* whereClause.
*/
public int delete(String table, String whereClause, String[] whereArgs) {
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +
(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
try {
return statement.executeUpdateDelete();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
throw e;
} finally {
statement.close();
}
}
2. SQLiteStatement 调用 native 方法执行数据删除
/**
* 顾名思义,executeUpdateDelete(), SQLiteDatabase 调用 update() 和 delete() 方法时会走这里
*
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
*
* @return the number of rows affected by this SQL statement execution.
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public int executeUpdateDelete() {
try {
// 缓存 STATEMENT_UPDATE 和 STATEMENT_BEGIN 类型的 sql 语句
saveSqlAsLastSqlStatement();
// 执行每个 sql 语句前都会执行该方法
// 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
acquireAndLock(WRITE);
int numChanges = 0;
if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
// 通过计算,不会走这里
// since the statement doesn't have to be prepared,
// call the following native method which will not prepare
// the query plan
native_executeSql(mSql);
} else {
numChanges = native_execute();
}
return numChanges;
} finally {
releaseAndUnlock();
}
}
四、更新数据操作(U:改)
1. 调用 SQLiteDatabase 中的 update() 方法
/**
* 返回被改动的总行数
*
* Convenience method for updating rows in the database.
*
* @param table the table to update in
* @param values a map from column names to new column values. null is a
* valid value that will be translated to NULL.
* @param whereClause the optional WHERE clause to apply when updating.
* Passing null will update all rows.
* @return the number of rows affected
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
// 注意此处出入的是 CONFLICT_NONE
return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
/**
* Convenience method for updating rows in the database.
*
* @param table the table to update in
* @param values a map from column names to new column values. null is a
* valid value that will be translated to NULL.
* @param whereClause the optional WHERE clause to apply when updating.
* Passing null will update all rows.
* @param conflictAlgorithm for update conflict resolver
* @return the number of rows affected
*/
public int updateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm) {
if (values == null || values.size() == 0) {
throw new IllegalArgumentException("Empty values");
}
// 拼接 sql 语句
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
// conflictAlgorithm 为 CONFLICT_NONE =0,取出的是空字符串
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
// 注意此处,调用 "SET"
sql.append(" SET ");
// move all bind args to one array
int setValuesSize = values.size();
int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
Object[] bindArgs = new Object[bindArgsSize];
int i = 0;
for (String colName : values.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = values.get(colName);
sql.append("=?");
}
if (whereArgs != null) {
for (i = setValuesSize; i < bindArgsSize; i++) {
bindArgs[i] = whereArgs[i - setValuesSize];
}
}
if (!TextUtils.isEmpty(whereClause)) {
sql.append(" WHERE ");
sql.append(whereClause);
}
// 拼接 sql 语句结束
// 通过 SQLiteStatement 执行数据更新
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeUpdateDelete();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
throw e;
} finally {
statement.close();
}
}
五、查询数据库(R:查)
1. 多个 query() 方法最终都会调用该 query() 方法
/**
* Query the given URL, returning a {@link Cursor} over the result set.
*
* @param distinct 常规使用 query() 方法时为 false,用于对某个字段去重
* 如:SELECT DISTINCT name FROM COMPANY; 将对名字进行去重后展示
*
* true if you want each row to be unique, false otherwise.
* @param table The table name to compile the query against.
* @param columns A list of which columns to return. Passing null will
* return all columns, which is discouraged to prevent reading
* data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return, formatted as an
* SQL WHERE clause (excluding the WHERE itself). Passing null
* will return all rows for the given table.
* @param selectionArgs You may include ?s in selection, which will be
* replaced by the values from selectionArgs, in order that they
* appear in the selection. The values will be bound as Strings.
* @param groupBy 指定某一列,对相同字段进行合并,通常用于统计该相同字段另一列的总和
* 如: SELECT NAME, SUM(SALARY) FROM COMPANY GROUP BY NAME
* 把具有相同名字的 SALARY 加和后展示
*
* A filter declaring how to group rows, formatted as an SQL
* GROUP BY clause (excluding the GROUP BY itself). Passing null
* will cause the rows to not be grouped.
* @param having 只有使用 groupBy 的情况下才能使用 having,否则会抛出异常
* 使用范例:
* SELECT column1, column2
* FROM table1, table2
* WHERE [ conditions ]
* GROUP BY column1, column2
* HAVING [ conditions ] (FC: having 后面跟的是条件判断语句)
* ORDER BY column1, column2
* 如:SELECT * FROM COMPANY GROUP BY name HAVING count(name) < 2
*
* A filter declare which row groups to include in the cursor,
* if row grouping is being used, formatted as an SQL HAVING
* clause (excluding the HAVING itself). Passing null will cause
* all row groups to be included, and is required when row
* grouping is not being used.
* @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
* (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return A {@link Cursor} object, which is positioned before the first entry. Note that
* {@link Cursor}s are not synchronized, see the documentation for more details.
* @see Cursor
*/
public Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit) {
return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
groupBy, having, orderBy, limit);
}
2. 拼装 sql 语句,调用 rawQueryWithFactory()
public Cursor queryWithFactory(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit) {
// 检测 database 是否是 open 状态,否则抛出异常
verifyDbIsOpen();
// 校验并拼装 sql 语句
String sql = SQLiteQueryBuilder.buildQueryString(distinct, table, columns, selection, groupBy, having, orderBy, limit);
return rawQueryWithFactory(cursorFactory, sql, selectionArgs, findEditTable(table));
}
3. 返回 Cursor 对象,但并没有进行真正的查询
/**
* Runs the provided SQL and returns a cursor over the result set.
*
* @param cursorFactory the cursor factory to use, or null for the default factory
* @param sql the SQL query. The SQL string must not be ; terminated
* @param selectionArgs You may include ?s in where clause in the query,
* which will be replaced by the values from selectionArgs. The
* values will be bound as Strings.
* @param editTable the name of the first table, which is editable
* @return A {@link Cursor} object, which is positioned before the first entry. Note that
* {@link Cursor}s are not synchronized, see the documentation for more details.
*/
public Cursor rawQueryWithFactory(CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable) {
verifyDbIsOpen();
BlockGuard.getThreadPolicy().onReadFromDisk();
// 返回的就是是当前 db 对象
SQLiteDatabase db = getDbConnection(sql);
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable);
Cursor cursor = null;
try {
// 当前线程循环等待获取 mLock 锁,直到获取为止,然后创建了 SQLiteQuery、SQLiteCursor
// 除了获取锁时的可能耗时,不存在别的耗时操作
cursor = driver.query(cursorFactory != null ? cursorFactory : mFactory, selectionArgs);
} finally {
// 根据该方法内部逻辑,什么都不会做,直接返回
releaseDbConnection(db);
}
return cursor;
}
4. SQLiteDirectCursorDriver 生成 cursor
public Cursor query(CursorFactory factory, String[] selectionArgs) {
// Compile the query
SQLiteQuery query = null;
try {
// 当前线程循环等待获取 mLock 锁,直到获取为止
mDatabase.lock(mSql);
mDatabase.closePendingStatements();
query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
// Create the cursor
if (factory == null) {
// 仅仅是做了些赋值,没有耗时操作
mCursor = new SQLiteCursor(this, mEditTable, query);
} else {
mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
mQuery = query;
query = null;
return mCursor;
} finally {
// Make sure this object is cleaned up if something happens
if (query != null){
query.close();
}
// 最后释放 mLock 锁
mDatabase.unlock();
}
}
六、SQLiteCursor 触发 SQLiteQuery 对数据库进行查询操作
1. SQLiteCursor 以下方法执行时,会触发 SQLiteQuery 对数据库的查询:
public int getCount() {
if (mCount == NO_COUNT) {
// 触发对数据库的查询
fillWindow(0);
}
return mCount;
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null || newPosition < mWindow.getStartPosition() || newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
// 触发对数据库的查询
fillWindow(newPosition);
}
return true;
}
2. 触发 SQLiteQuery 对数据库的查询
/**
* 触发 SQLiteQuery 对数据库的查询操作
* @param startPos
*/
private void fillWindow(int startPos) {
// 如果 mWindow==null,则 new CursorWindow(name, true),否则 mWindow.clear()
clearOrCreateLocalWindow(getDatabase().getPath());
mWindow.setStartPosition(startPos);
// 触发 SQLiteQuery 对数据库的查询操作
int count = getQuery().fillWindow(mWindow);
if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + count);
}
mCount = count;
} else if (mCount <= 0) {
throw new IllegalStateException("Row count should never be zero or negative "
+ "when the start position is non-zero");
}
}
3. SQLiteQuery 调用 native 方法查询数据库
/**
* 真正查询数据库的地方,当前线程会循环等待获取 mDatabase 的 mLock 锁,直到获取为止
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
* @return number of total rows in the query
*/
/* package */ int fillWindow(CursorWindow window) {
// 当前线程循环等待获取 mDatabase 的 mLock 锁,直到获取为止
mDatabase.lock(mSql);
long timeStart = SystemClock.uptimeMillis();
try {
acquireReference();
try {
window.acquireReference();
int startPos = window.getStartPosition();
// 调用 native 方法填充 CursorWindow
int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr, startPos, mOffsetIndex);
mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
} catch (SQLiteDatabaseCorruptException e) {
mDatabase.onCorruption();
throw e;
} catch (SQLiteException e) {
Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql);
throw e;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
mDatabase.unlock();
}
}