• 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++来实现数据库的封装也是能够的

    最后,假设本文有哪些地方不足或者错误,还请指出。谢谢。

  • 相关阅读:
    "Serialization.SerializationException: 在分析完成之前就遇到流结尾”解决方法
    导出你的GAC Assembly中的DLLS
    SMTP无法发送邮件
    Feature"xxx" for list template "xxx" is not installed in this farm
    一个application多个 URL
    将sharepoint的list绑定到Infopath的下拉框
    Value was either too large or too small for a UInt32
    Event ID 5553 failure trying to synch site"xxxx" for ContentDB "xx" WebApp "xx". Exception message was Cannot insert duplicate key row in object 'dbo.UserMemberships' with unique index
    System.InvalidOperationException: 工作流的事件接收器上下文无效。
    Cannot open Proj.enUS.resx: no such file or folder
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8482895.html
Copyright © 2020-2023  润新知