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的屬性。
創建流程:
能夠看出,創建一個數據庫對象,會創建一個數據庫連接池。而且會創建出一個主連接
數據庫連接池用於管理數據庫連接對象
而數據庫連接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);
}
}
流程圖:
這裏有幾個須要註意一下:
- 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;
}
流程圖:
能夠看出先構造出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++來實現數據庫的封裝也是能夠的
最後,假設本文有哪些地方不足或者錯誤,還請指出。謝謝。
SQLiteOpenHelper/SQLiteDatabase/Cursor源代碼解析