SQLiteOpenHelper/SQLiteDatabase/Cursor源碼解析

轉載請註明出處:http://blog.csdn.net/y_zhiwen/article/details/51583188

Github地址,歡迎star和follow

新增android sqlite native 的代碼

我們在使用android提供的SQLite存儲數據的時候,就會用到SQLiteOpenHelper和SQLiteDataBase,但查詢數據的時候會得到一個Cursor對象,這裏我們將深入android提供的關於SQLite的封裝以原理。

SQLiteOpenHelper

——封裝管理數據庫的創造和版本管理類

主要封裝了數據庫的創建和獲取的方法,一般繼承該類實現onCreate()、onUpdate()方法,在onCreate創建數據庫,在onUpdate進行數據庫升級操作。其中還有onConfigure()、onDowngrade()、onOpen()方法,將會在下面獲取數據庫對象分析進行解析

  • 數據庫的獲取:
    兩個方法:getReadableDatabase()、getWritableDatabase()。需要注意的一點是這兩個方法都加鎖,是線程安全的。這兩個方法最終調用getDatabaseLocked(boolean writable):
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();
        }
    }
}

onCreate()、onUpdate()、onConfigure()、onDowngrade()、onOpen()方法的調用規則:

  • onConfigure():
    當第一次調用getReadableDatabase()或者getWritableDatabase()會調用onConfigure(),如果第一是獲取到只讀形式的數據庫,當轉換成可寫形式數據庫時會再次調用onConfigure()。

  • onCreate()
    mDatabase第一次創建時會調用onCreate()

  • onUpdate() / onDowngrade()
    在版本改變時會調用相應的onUpdate()或onDowngrade()方法,

  • onConfigure()
    至於onOpen()的調用規則同onConfigure()。

那麼onConfigure()和onOpen()方法可以幹嘛呢,從api文檔可以看到:

  • 可以在onConfigure**開啓SQLite的WAL模式,以及設置外鍵的支持**;

  • 而onOpen()方法是說明數據庫已經打開,可以進行一些自己的操作,但是需要通過SQLiteDatabase#isReadOnly方法檢查數據庫是否真正打開了

  • 開啓WAL方法:setWriteAheadLoggingEnabled(boolean enabled)
    WAL支持讀寫併發,是通過將修改的數據單獨寫到一個wal文件中,默認在到達一個checkpoint時會將數據合併入主數據庫中
    至於關於WAL的詳細介紹和分析可以參見SQLite3性能深入分析](http://blog.xcodev.com/posts/sqlite3-performance-indeep/)

SQLiteDatabase

open

獲取SQLiteDatabase對象,從上面可以看到getReadableDatabase()、getWritableDatabase()是通過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()函數:

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的屬性。

創建流程:
sqlite_open

可以看出,創建一個數據庫對象,會創建一個數據庫連接池,並且會創建出一個主連接

數據庫連接池用於管理數據庫連接對象

而數據庫連接SQLiteConnection則在其中包裝了native的sqlite3對象,數據庫sql語句最終會通過sqlite3對象執行可以看出,創建一個數據庫對象,會創建一個數據庫連接池,並且會創建出一個主連接

數據庫連接池用於管理數據庫連接對象

而數據庫連接SQLiteConnection則在其中包裝了native的sqlite3對象,數據庫sql語句最終會通過sqlite3對象執行

insert

那麼接下來就可以對數據庫進行一些CRUD操作

先分析一下insert()和insertOrThrow()插入函數:

// 最終會調用insertWithOnConflict
public long insertWithOnConflict(String table, String nullColumnHack,
        ContentValues initialValues, int conflictAlgorithm) {
    acquireReference();
    try {
        StringBuilder sql = new StringBuilder();
        // 構造insert SQL語句

        // 創建SQLiteStatement對象,並調用executeInsert()
        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
        try {
            return statement.executeInsert();
        } finally {
            statement.close();
        }
    } finally {
        releaseReference();
    }
}

// SQLiteStatement::executeInsert():
public long executeInsert() {
    acquireReference();
    try {
        return getSession().executeForLastInsertedRowId(
                getSql(), getBindArgs(), getConnectionFlags(), null);
    } catch (SQLiteDatabaseCorruptException ex) {
        onCorruption();
        throw ex;
    } finally {
        releaseReference();
    }
}

// getSession()調用的是mDatabase.getThreadSession(),獲取到SQLiteSession對象:

// SQLiteSession::executeForLastInsertedRowId():
public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
        CancellationSignal cancellationSignal) {
   // 驗證判斷

   // 獲取一個數據庫連接
    acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
    try {
        // 執行sql語句
        return mConnection.executeForLastInsertedRowId(sql, bindArgs,
                cancellationSignal); // might throw
    } finally {
        releaseConnection(); // might throw
    }
}

// SQLiteConnection::executeForLastInsertedRowId():
public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
        CancellationSignal cancellationSignal) {
    if (sql == null) {
        throw new IllegalArgumentException("sql must not be null.");
    }

    final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
            sql, bindArgs);
    try {
        final PreparedStatement statement = acquirePreparedStatement(sql);
        try {
            throwIfStatementForbidden(statement);
            // 綁定數據參數
            bindArguments(statement, bindArgs);
            applyBlockGuardPolicy(statement);
            attachCancellationSignal(cancellationSignal);
            try {
                // 調用native執行sql語句
                return nativeExecuteForLastInsertedRowId(
                        mConnectionPtr, statement.mStatementPtr);
            } finally {
                detachCancellationSignal(cancellationSignal);
            }
        } finally {
            releasePreparedStatement(statement);
        }
    } catch (RuntimeException ex) {
        mRecentOperations.failOperation(cookie, ex);
        throw ex;
    } finally {
        mRecentOperations.endOperation(cookie);
    }
}

流程圖:
insert

這裏有幾個需要注意一下:

  • SQLiteSession:
private final ThreadLocal<SQLiteSession> mThreadSession = new ThreadLocal<SQLiteSession>() {
    @Override
    protected SQLiteSession initialValue() {
        return createSession();
    }
};

每個線程都擁有自己的SQLiteSession對象。多個線程進行數據操作的時候需要注意和處理保持數據的原子性

  • SQLiteStatement

SQLiteStatement類代表一個sql語句,其父類爲SQLiteProgram,從上面可以看到,insert操作會先構造出SQLiteStatement,其構造方法:

SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
        CancellationSignal cancellationSignalForPrepare) {
    mDatabase = db;
    mSql = sql.trim();

    int n = DatabaseUtils.getSqlStatementType(mSql);
    switch (n) {
        case DatabaseUtils.STATEMENT_BEGIN:
        case DatabaseUtils.STATEMENT_COMMIT:
        case DatabaseUtils.STATEMENT_ABORT:
            mReadOnly = false;
            mColumnNames = EMPTY_STRING_ARRAY;
            mNumParameters = 0;
            break;

        default:
            boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
            SQLiteStatementInfo info = new SQLiteStatementInfo();
            db.getThreadSession().prepare(mSql,
                    db.getThreadDefaultConnectionFlags(assumeReadOnly),
                    cancellationSignalForPrepare, info);
            mReadOnly = info.readOnly;
            mColumnNames = info.columnNames;
            mNumParameters = info.numParameters;
            break;
    }

    // 參數初始化操作
}

可以看到其會調用SQLiteSession::prepare()操作,又是轉發到SQLiteConnection::prepare()操作,進行SQL語法預編譯,並會返回行列信息到SQLiteStatementInfo中。

再看下插入函數public long executeForLastInsertedRowId(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)通過前面的SQLiteStatement將sql語句和參數組成sql並傳遞進來,通過PreparedStatement acquirePreparedStatement(String sql)獲取PreparedStatement對象,再通過nativeExecuteForLastInsertedRowId( mConnectionPtr, statement.mStatementPtr)native方法執行sql語句。

在獲取PreparedStatement的時候,可以看到PreparedStatement通過一個mPreparedStatementCache來進行緩存操作,具體是一個LruCache<String, PreparedStatement>來完成sql的緩存

replace、delete

同理的操作有replace()、replaceOrThrow、delete、updateupdateWithOnConflict、execSQL等函數。

讀者可按照前面思路分析

query

現在重點分析一下SQLiteDatabase的查詢操作:

從源碼可以看出查詢操作最終會調用rawQueryWithFactory():


public Cursor rawQueryWithFactory(
        CursorFactory cursorFactory, String sql, String[] selectionArgs,
        String editTable, CancellationSignal cancellationSignal) {
    acquireReference();
    try {
        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
                cancellationSignal);
        return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
                selectionArgs);
    } finally {
        releaseReference();
    }
}

可以看出先構造出SQLiteDirectCursorDriver,再調用其query操作:


// SQLiteDirectCursorDriver::query():
public Cursor query(CursorFactory factory, String[] selectionArgs) {
    final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
    final Cursor cursor;
    try {
        query.bindAllArgsAsStrings(selectionArgs);

        if (factory == null) {
            cursor = new SQLiteCursor(this, mEditTable, query);
        } else {
            cursor = factory.newCursor(mDatabase, this, mEditTable, query);
        }
    } catch (RuntimeException ex) {
        query.close();
        throw ex;
    }

    mQuery = query;
    return cursor;
}

流程圖:
query

可以看出先構造出SQLiteQuery,在構造出SQLiteCursor,並返回SQLiteCursor對象。

所以得到的Cursor的原型是SQLiteCursor類,你會發現沒有其他操作,那麼查詢數據是在哪裏呢?

SQLiteCursor分析:

public final boolean moveToFirst() {
    return moveToPosition(0);
}

public final boolean moveToPosition(int position) {
    // Make sure position isn't past the end of the cursor
    final int count = getCount();
    if (position >= count) {
        mPos = count;
        return false;
    }

    // Make sure position isn't before the beginning of the cursor
    if (position < 0) {
        mPos = -1;
        return false;
    }

    // Check for no-op moves, and skip the rest of the work for them
    if (position == mPos) {
        return true;
    }

    boolean result = onMove(mPos, position);
    if (result == false) {
        mPos = -1;
    } else {
        mPos = position;
    }

    return result;
}

public int getCount() {
    if (mCount == NO_COUNT) {
        fillWindow(0);
    }
    return mCount;
}

private void fillWindow(int requiredPos) {
    clearOrCreateWindow(getDatabase().getPath());

    try {
        if (mCount == NO_COUNT) {
            int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
            mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
            mCursorWindowCapacity = mWindow.getNumRows();
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
            }
        } else {
            int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
                    mCursorWindowCapacity);
            mQuery.fillWindow(mWindow, startPos, requiredPos, false);
        }
    } catch (RuntimeException ex) {
        // Close the cursor window if the query failed and therefore will
        // not produce any results.  This helps to avoid accidentally leaking
        // the cursor window if the client does not correctly handle exceptions
        // and fails to close the cursor.
        closeWindow();
        throw ex;
    }
}

protected void clearOrCreateWindow(String name) {
    if (mWindow == null) {
        mWindow = new CursorWindow(name);
    } else {
        mWindow.clear();
    }
}

到這裏你會發現CursorWindow,那這個對象是幹嘛的呢?從文檔上看可以發現其保存查詢數據庫的緩存,那麼數據是緩存在哪的呢?先看器構造器:

public CursorWindow(String name) {
    // ...
    mWindowPtr = nativeCreate(mName, sCursorWindowSize);

    // .. 
}

nativeCreate通過JNI調用CursorWindow.cpp的create():

status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
    String8 ashmemName("CursorWindow: ");
    ashmemName.append(name);

    status_t result;
    // 創建共享內存
    int ashmemFd = ashmem_create_region(ashmemName.string(), size);
    if (ashmemFd < 0) {
        result = -errno;
    } else {
        result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
        if (result >= 0) {
            // 內存映射
            void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
            // ...
    }
    *outCursorWindow = NULL;
    return result;
}

可以看到查詢數據是通過創建共享內存來保存的,但是數據在哪裏被保存了呢?

繼續分析上面SQLiteCursor:: fillWindow()函數:
mQuery.fillWindow(mWindow, startPos, requiredPos, true);

其最終會調用SQLiteConnection::executeForCursorWindow,也是通過JNI調用cpp文件將查詢數據保存到共享內存中。

至於共享內存的知識點,可以參考 Android系統匿名共享內存Ashmem

總結

經過上面分析,關於數據庫的操作應該有了大致的瞭解:
這裏寫圖片描述

當然裏面也有些地方也是可以加以改善,取得更好的效率。

而且你會發現SQLiteConnection裏面包含許多native方法,通過jni與sqlite進行交互,除了在android提供的sqlite庫的基礎上優化之外,也可以基於SQLiteConnection,甚至是完全使用c++來實現數據庫的封裝也是可以的

最後,如果本文有哪些地方不足或者錯誤,還請指出,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章