Sqlite 源码分析 -- SQLiteDatabase CRUD 操作 (API 14)

注意事项:

  1. 如果 SQLiteOpenHelper 使用的是单例,SQLiteDatabase 对 CRUD 操作都会加上同一个锁(因为是db文件,所以精确至数据库级). 所以同一时间只能进行一项操作,多线程读写几乎是无用功;

  2. enableWriteAheadLogging() 方法可以使得多线程并发查询可行,但默认没有开启该功能;

  3. android sqlite 不支持多 SQLiteOpenHelper 实例、多线程并发写入操作,会抛出异常“database is locked”;

  4. 插入单条数据不需要开启事务;

  5. 全局引用一个 SQLiteDatabase 时,是否存在不主动调用 close() 但被动 close() 的情况?这种情况下获取单例的 SQLiteOpenHelper 的写法就需要特别注意;

  6. SQLiteCursor 的获取与触发对数据库的真正查询是分离的,获取 SQLiteCursor 后、 查询数据库前数据库的状态发生变化(如“被关闭”),是否会出现问题?

  7. 执行 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();
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 一、概述 这篇文章主要涉及到项目当中,使用数据库相关的操作: 使用SQLiteOpenHelper来封装数据库。 ...
    泽毛阅读 2,288评论 1 9
  • Github地址,欢迎star和follow 新增android sqlite native 的代码 我们在使用a...
    丢底很远阅读 1,035评论 0 6
  • 睁眼的时候,突然发现我自己闪闪发光,太阳整个的笼罩着我,让我觉得像参加骑士的受封仪式一样,每一步都像是在跳舞,每一...
    阿尚_阅读 234评论 0 0
  • 时间的的力道十足却从不显山漏水,真正厉害的地方在于不知不觉间重塑每一个人,即便是让人变成自己讨厌的模样都不曾...
    蓝潭星海阅读 207评论 2 3
  • 我要舔干净这最后一滴酒 然后拎着酒壶上路 去樱花下面 去铁塔上面 去杀人放火 去普度众生 去爱你 然后离开 去恨你...
    神藏龙象阅读 184评论 0 1