转载请注明出处: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源代码解析的更多相关文章

  1. android7.x Launcher3源代码解析(3)---workspace和allapps载入流程

    Launcher系列目录: 一.android7.x Launcher3源代码解析(1)-启动流程 二.android7.x Launcher3源代码解析(2)-框架结构 三.android7.x L ...

  2. 使用具体解释及源代码解析Android中的Adapter、BaseAdapter、ArrayAdapter、SimpleAdapter和SimpleCursorAdapter

    Adapter相当于一个数据源,能够给AdapterView提供数据.并依据数据创建相应的UI.能够通过调用AdapterView的setAdapter方法使得AdapterView将Adapter作 ...

  3. Spring源代码解析

    Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的启动:http://www.itey ...

  4. Arrays.sort源代码解析

    Java Arrays.sort源代码解析 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类 ...

  5. Spring源代码解析(收藏)

    Spring源代码解析(收藏)   Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的 ...

  6. volley源代码解析(七)--终于目的之Response&lt;T&gt;

    在上篇文章中,我们终于通过网络,获取到了HttpResponse对象 HttpResponse是android包里面的一个类.然后为了更高的扩展性,我们在BasicNetwork类里面看到.Volle ...

  7. Cocos2d-x源代码解析(1)——地图模块(3)

    接上一章<Cocos2d-x源代码解析(1)--地图模块(2)> 通过前面两章的分析,我们能够知道cocos将tmx的信息结构化到 CCTMXMapInfo.CCTMXTilesetInf ...

  8. Android EventBus源代码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  9. 源代码解析Android中View的layout布局过程

    Android中的Veiw从内存中到呈如今UI界面上须要依次经历三个阶段:量算 -> 布局 -> 画图,关于View的量算.布局.画图的整体机制可參见博文 < Android中Vie ...

随机推荐

  1. [转]使用ThinkPHP框架快速开发网站(多图)

    本文转自:http://blog.csdn.net/ruby97/article/details/7574851 这一周一直忙于做实验室的网站,基本功能算是完成了.比较有收获的是大概了解了ThinkP ...

  2. Excel文件导入导出

    /**     * 导入Excel文件数据     *      * @param file 将要导入的Excel文件     * @param fileCheckKeyWord 用于判断导入文件是否 ...

  3. Java_注解之二

    在上一次的注解案例里面配置注解的同时,也添加了一对多(@OneToMany)的关系在里面. 本次将补充上次的缺失:其他三种关联方式的配置. 为了简化配置的复杂度   在此案例中Emp和Dept并不是唯 ...

  4. Laravel (5.5.33) 加载过程---make方法(四)

    /** * Resolve the given type from the container. * * @param string $abstract * @return mixed */ publ ...

  5. Laravel5.1学习笔记i14 系统架构6 Facade

    Facades 介绍  使用 Facades Facade 类参考   #介绍 Facades provide a "static" interface to classes th ...

  6. fcc jQuery 练习

    在页面顶端增加一行script元素,然后写上结束符, 浏览器会运行script 里所有的Javascript,包括jQuery <script>$(document).ready(func ...

  7. cordova科大讯飞语音识别

    cordova-plugin-IFlyspeech 科大讯飞的语音听说读写的cordova插件 Supported Platforms iOS android Installation 插件安装命令: ...

  8. HTML地理位置定位

    最近公司项目需要做一个类似微信朋友圈的互动交友功能,需要显示用户位置信息,因此在网上查了部分资料,记下demo供以后查看学习:(用到了百度api来实现定位功能) <!DOCTYPE html&g ...

  9. 将MongoDB服务器设置成Windows启动服务(win10)

    如题,这个问题也百度了很久,百度还是挺给力的,但是都没能解决问题,后来在大神(原谅我不知道大神叫什么)的指导下,终于设置成功,特分享下设置过程.. MongoDB设置数据库我就不说了...额..算了, ...

  10. Pycharm:debug调试时使用参数

    一种操作方法: 文章链接:MAC下使用Pycharm,debug调试时怎样带参数 今天在网上找了一个例子敲代码,因为我使用的是PyCharm,例子运行时需要带参数,开始不知道怎么带参数,网上搜了大半天 ...