Android 存储优化系列专题
- SharedPreferences 系列
《Android 之不要滥用 SharedPreferences》
《Android 之不要滥用 SharedPreferences(2)— 数据丢失》
- ContentProvider 系列(待更)
《Android 存储选项之 ContentProvider 启动过程源码分析》
《Android 存储选项之 ContentProvider 深入分析》
- 对象序列化系列
《Android 对象序列化之你不知道的 Serializable》
《Android 对象序列化之 Parcelable 深入分析》
《Android 对象序列化之追求完美的 Serial》
- 数据序列化系列(待更)
《Android 数据序列化之 JSON》
《Android 数据序列化之 Protocol Buffer 使用》
《Android 数据序列化之 Protocol Buffer 源码分析》
- SQLite 存储系列
《Android 存储选项之 SQLiteDatabase 创建过程源码分析》
《Android 存储选项之 SQLiteDatabase 源码分析》
《数据库连接池 SQLiteConnectionPool 源码分析》
《SQLiteDatabase 启用事务源码分析》
《SQLite 数据库 WAL 模式工作原理简介》
《SQLite 数据库锁机制与事务简介》
《SQLite 数据库优化那些事儿》
在上一篇《Android 存储选项之 SQLiteDatabase 创建过程源码分析》一文,为大家分析了 Android 平台提供的数据库操作对象 SQLiteDatabase 创建过程,以及关于 SQLiteDatabase、SQLiteConnectionPool、SQLiteConnection 三者之间的紧密关系。
如果说 SQLiteOpenHelper 可能是我们在 Android 平台使用 SQLite 数据库接触到的第一个类,那 SQLiteDatabase 可能就是那个最关键的类了,实际上它内部的绝大多数函数也是围绕数据库操作进行封装的插入、删除、更新、查询和关闭等核心任务。该篇文章也是围绕这些内容对 SQLiteDatabase 源码做进一步的分析。
SQLiteDatabase 介绍
SQLiteDatabase 对象在概念上比较容易理解,与 SQLite C API 的底层数据库对象相似,但是实际情况要复杂得多。
SQLiteDatabase 类中包含 50 多个方法,每个方法都有自己的用途,而且每个方法相对独立于其它方法,它们大部分都是为完成一个简单的数据库任务,比如表的选择、插入、更新、删除语句。这里只是列举了一些重要的方法介绍。详细你可以参照这里。
1. 使用 SQLiteDatabase 类执行普通查询操作
如同有很多创建 SQL 语句的方法,SQLiteDatabase 也提供了大量的方法在 SQLite 数据库中运行 SQL 语句。事实上 Android 提供了多达 16 种方法在 SQLite 数据库中运行一般或特殊的查询。
这些函数包含 SQL 语句函数,如单一表格插入、更新等,也包括一般的执行 DML 和 DDL 的函数。这些函数分组显示如下:
void execSQL(String sql)
void execSQL(String sql, Object[] bindArgs)
Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy,String having, String orderBy, String limit)
Cursor query(String table, String[] columns, String selection, String[ selectionArgs, String groupBy, String having, String orderBy)
Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
Cursor queryWithFactory(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
Cursor rawQuery(String sql, String[] selectionArgs)
Cursor rawQueryWithFactory(SQLiteDatabase,CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable)
虽然函数很多,但是它们可以归结为三个核心函数:execSQL、query 和 rawQuery,它们各自的使用场景相信大家也一定分的出。
2. 增加、删除、修改等操作
上面是一系列数据库查询操作,另外对数据库的插入、更新、删除操作方法如下:
int delete(String table, String whereClause, String[] whereArgs)
long insert(String table, String nullColumnHack, ContentValues initialValues)
...
long replace(String table, String nullColumnHack, ContentValues initialValues)
...
int update(String table, ContentValues values, String whereClause, Stirng[] whereArgs)
...
文中并没有将所有数据库操作 API 进行贴出,不过即使通过方法名我们也很容易理解每个方法的作用。这些方法看上去很吸引人,其实它们的设计主要也是为了面向对象的程序员设计的。
3. compileStatement 函数
SQLiteDatabase 中还有一个方法比较重要,它就是 compileStatement:
publi SQLiteStatement compileStatement(String sql)
将一个 SQL 语句作为参数,返回一个 SQLiteStatement 对象,事实上,正如函数名表示的,这个函数将编译 SQL 语句并允许将其重用。关于它的使用将在后面一篇文章《Android 存储选项之 SQLiteDatabase 优化那些事儿》进行介绍。
4. SQLiteDatabase 类的事务管理
Android 很看重 SQLite 事务管理。在 SQLiteDatabase 中很多方法用来启动、结束和管理事务的。
void beginTransaction()
void beginTransactionWithListener(SQLiteTransactionListener transactionListener)
void endTransaction()
boolean inTransacetion()
void setTransactionSuccessful()
这些方法不用太多解释。beginTransaction() 启动 SQLite 事务,endTransaction() 结束当前的事务。重要的是,决定事务是否被提交或者回滚取决于事务是否被标注了 “clean”。setTrancactionSuccessful() 函数用来设置这个标志位,这个额外的步骤刚开始让人反感,但事实上它保证了在事务提交前对所做更改的检查。如果不存在事务或者事务已经是成功状态,那么 setTransactionSuccessful() 函数就会抛出 IllegalStateException 异常。
isTransaction() 函数测试是否存在活动事务,如果有,则返回 true。
beginTransactionWithListener 函数接收 SQLiteTransaction-Listener 对象,SQLiteTransactionListener 负责监听与事务相关的一切操作,比如提交、回滚或者嵌套地开始一个新事物。
关于 SQLiteDatabase 事务源码分析可以参考后续文章《SQLiteDatabase 启用事务源码分析》
5. SQLiteDatabase 中其它函数
SQLiteDatabase 中还涉及到一些其它方法:
public long getMaximumSize() 返回数据库允许的最大值
public int getVersion() 返回应用创建数据库的版本号
public boolean isDbLockedByCurrentThread() 测试当前线程是否锁住了数据库。
public boolean isDbLockedByOtherThreads() 测试是否有其他线程锁住了数据库。
public static int releaseMemory() 释放书库不再需要的资源,比如内存等。返回释放资源的字节数。
经过上面的一系列介绍,相信你对 SQLiteDatabase 的工作内容有了进一步认识,接下来我们通过源码的角度具体分析下数据库的相关操作。
SQLiteCloseable
在分析 SQLiteDatabase 源码之前我想先来说下 SQLiteCloseable 类。因为 SQLiteDatabase 中会出现大量该逻辑的操作,这里先对其进行说明。
其实关于 SQLiteCloseable 完全可以理解成 Java 为 I/O 提供的 Closeable 标准接口,SQLiteCloseable 就是专门为数据库释放提供的标准 API(SQLiteCloseable 也实现了 Closeable)。
在 android/database 包下类似 SQLiteDatabase、CursorWindow、SQLiteQuery 等都实现了该接口。
通过源码分析它的工作过程,申请操作:
public void acquireReference() {
synchronized (this) {
if (mReferenceCount <= 0) {
//表示数据库已经关闭
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + this);
}
//引用计数++
mReferenceCount++;
}
}
对应释放操作:
public void releaseReference() {
boolean refCountIsZero = false;
synchronized (this) {
//释放对该对象的引用
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
//当前不存在任何引用
//通知关闭
onAllReferencesReleased();
}
}
从源码我们可以看出,其内部通过引用计数的方式确定当前对象是否还被外部引用。
当引用计数 mReferenceCount == 0 时,此时会调用 onAllReferencesReleased 方法,该方法原型:
/**
* Called when the last reference to the object was released by
* a call to {@link #releaseReference()} or {@link #close()}.
*/
protected abstract void onAllReferencesReleased();
这其实也比较好理解,当涉及到具体释放过程时就需要具体实现类自行实现该过程。
以 SQLiteDatabase 中的实现为例:
//在SQLiteDatabase中重写SQLiteCloseable中的onAllReferencesReleased
protected void onAllReferencesReleased() {
dispose(false);
}
看下引用计数在具体实现类 SQLiteDatabase 中的工作过程,我们以查询为例:
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
//引用计数++
acquireReference();
try {
//... 省略
} finally {
//释放
releaseReference();
}
}
每次对 SQLiteDatabase 的访问操作,首先会通过 acquireReference 使引用计数++,访问结束通过 releaseReference 引用计数 --,此时如果 mReferenceCount == 0,回调 SQLiteDatabase 中重写的 onAllReferencesReleased 方法,内部调用 dispose 方法:
private void dispose(boolean finalized) {
final SQLiteConnectionPool pool;
synchronized (mLock) {
if (mCloseGuardLocked != null) {
if (finalized) {
mCloseGuardLocked.warnIfOpen();
}
mCloseGuardLocked.close();
}
//当前SQLiteDatabase对象的连接池
pool = mConnectionPoolLocked;
mConnectionPoolLocked = null;
}
if (!finalized) {
synchronized (sActiveDatabases) {
//移除在WeakHashMap中
sActiveDatabases.remove(this);
}
if (pool != null) {
//关闭连接池
pool.close();
}
}
}
关于 dispose 它接受一个 boolean 类型的字段,表示当前是主动(close)关闭还是被动(finalize 回调)关闭。在 SQLiteDatabase、 SQLiteConnectionPool、SQLiteConnection 等都提供了该套路,以 SQLiteDatabase 的 finalize 为例:
@Override
protected void finalize() throws Throwable {
try {
dispose(true);
} finally {
super.finalize();
}
}
这也是 finalize 典型使用场景,虽然 finalize 方法 的调用时机不确定,但是我们可以利用该方法实现某个对象的兜底回收策略,减小或减少发生内存泄漏的影响或可能。
在 SQLiteDatabase dispose 方法中,我们重点关注下数据库连接池的关闭操作 SQLiteConnectionPool 的 close 方法:
public void close() {
dispose(false);
}
//一样的套路,调用dispose方法
private void dispose(boolean finalized) {
//...省略
if (!finalized) {
synchronized (mLock) {
//已经关闭的连接池将会抛出异常。
throwIfClosedLocked();
//标志是否已经关闭
mIsOpen = false;
//关闭所有数据库连接SQLiteConnection
closeAvailableConnectionsAndLogExceptionsLocked();
final int pendingCount = mAcquiredConnections.size();
if (pendingCount != 0) {
Log.i(TAG, "The connection pool for " + mConfiguration.label
+ " has been closed but there are still "
+ pendingCount + " connections in use. They will be closed "
+ "as they are released back to the pool.");
}
wakeConnectionWaitersLocked();
}
}
}
dispose 方法中包含一个方法名非常长的 closeAvailableConnectionsAndLogExceptionsLocked 方法如下:
private void closeAvailableConnectionsAndLogExceptionsLocked() {
//关闭所有连接池中缓存的非主连接
closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
if (mAvailablePrimaryConnection != null) {
//关闭主连接
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
mAvailablePrimaryConnection = null;
}
}
在数据库连接池 SQLiteConnectionPool 中通过一个 List 对象缓存所有非数据库主连接,关闭该些连接方法如下:
private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
//当前SQLiteDatabase对象的所有非主连连接都被mAvailableNonPrimaryConnections缓存
final int count = mAvailableNonPrimaryConnections.size();
for (int i = 0; i < count; i++) {
//关闭连接池中每个非主连接 SQLiteConnection
closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
}
//清空连接池缓存
mAvailableNonPrimaryConnections.clear();
}
直接遍历该集合关闭所有连接(非主连接)并清空缓存。注意数据库主连接并没有被缓存在该 List 对象中,如上也是直接关闭它。
private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
try {
//直接关闭该连接SQLiteConnection
connection.close(); // might throw
if (mIdleConnectionHandler != null) {
//移除延时关闭连接机制
mIdleConnectionHandler.connectionClosed(connection);
}
} catch (RuntimeException ex) {
Log.e(TAG, "Failed to close connection, its fate is now in the hands "
+ "of the merciful GC: " + connection, ex);
}
}
直接调用 SQLiteConnection 的 close 方法,并移除该连接的延迟关闭消息。
//SQLiteConnection的close方法
void close() {
dispose(false);
}
//一样的套路,还是调用dispose方法
private void dispose(boolean finalized) {
if (mCloseGuard != null) {
if (finalized) {
mCloseGuard.warnIfOpen();
}
mCloseGuard.close();
}
if (mConnectionPtr != 0) {
final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
//调用native层关闭对应native层SQLiteConnection
nativeClose(mConnectionPtr);
//将对native持有句柄置为0
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
}
}
}
每个 Java 层 SQLiteConnection 都会对应一个 native 层 SQLiteConnection,此时调用 nativeClose 方法关闭对应的 native 对象,并将指向的匿名内存描述符 mConnectionPtr 置为 0:
static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
//转换成native层的SQLiteConnection对象
179 SQLiteConnection* connection = reinterpret_cast(connectionPtr);
180
181 if (connection) {
182 ALOGV("Closing connection %p", connection->db);
//关闭该数据库操作句柄
183 int err = sqlite3_close(connection->db);
184 if (err != SQLITE_OK) {
185 // This can happen if sub-objects aren't closed first. Make sure the caller knows.
186 ALOGE("sqlite3_close(%p) failed: %d", connection->db, err);
187 throw_sqlite3_exception(env, connection->db, "Count not close db.");
188 return;
189 }
//删除SQLiteConnection,释放其占用内存
191 delete connection;
192 }
193 }
SQLiteCloseable 辅助完成数据库相关核心类的申请和释放操作,然而它只是 SQLiteDatabase 操作涉及到的其中一个细节,这样的细节功能在 SQLiteDatabae 中还非常的多。感兴趣的朋友可以更深入源码进行分析。
查询操作
相比增加、删除和更新等操作,数据库的查询可能是最复杂的一个过程,下面就我们就先从 SQLiteDatabase 的查询操作开始分析,然后再去分析其它的数据库操作就会变得非常轻松。示例如下:
public String callInBackground(@NonNull SQLiteDatabase db) {
db.beginTransactionNonExclusive();
Cursor cursor = null;
try {
//这里直接使用SQL语句进行查询
cursor = db.rawQuery("SELECT name FROM user LIMIT 100", null);
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
final String name = cursor.getString(cursor.getColumnIndex("name"));
DbLog.e(TAG, "name: " + name);
}
}
db.setTransactionSuccessful();
} catch (Exception e) {
DbLog.print(e);
} finally {
db.endTransaction();
if (cursor != null) {
cursor.close();
}
}
return null;
}
直接使用 SQL 语句调用 SQLiteDatabase 的 rawQuery 方法,SQLiteDatabase 中所有的查询操作最终都会从 rawQueryWithFactory 方法开始:
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
//引用计数 ++
acquireReference();
try {
//创建SQLiteCursorDriver对象
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
cancellationSignal);
//使用SQLiteDirectCursorDriver完成查询
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
//释放引用计数 --
releaseReference();
}
}
这里直接创建 SQLiteDirectCursorDriver,然后调用它的 query 方法:
public Cursor query(CursorFactory factory, String[] selectionArgs) {
//查询使用的是SQLiteQuery
//实际SQLiteCursor内部使用SQLiteQuery开始查询数据
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs);
if (factory == null) {
//默认返回SQLiteCursor
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
//配置的CursorFactory在这里调用,自定义Cursor
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
}
mQuery = query;
return cursor;
}
Cursor 本质只是一个接口,约束了对查询数据获取的接口规则,实际查询返回的 Cursor 类型是 SQLiteCursor。可以看到创建 SQLiteQuery 对象作为参数传入 SQLiteCurosr。先看下 SQLiteQuery 的创建过程:
SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {
super(db, query, null, cancellationSignal);
mCancellationSignal = cancellationSignal;
}
SQLiteQuery 实际仅包含一个非常重要的方法 fillWindow,在遍历 Cursor 时会分析到它;SQLiteQuery 大部分逻辑操作都在其父类 SQLiteProgram 中:
SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
CancellationSignal cancellationSignalForPrepare) {
mDatabase = db;
mSql = sql.trim();
//这里根据SQL语句判断当前的操作类型
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);
//将SQLiteStatementInfo 中数据赋值给当前成员
mReadOnly = info.readOnly;
mColumnNames = info.columnNames;
mNumParameters = info.numParameters;
break;
}
// ... 省略
}
根据 getSqlStatementType 判断当前操作类型,这里直接告诉大家:内部根据 SQL 语句前缀判断其操作类型,查询语句返回的是 STATEMENT_SELECT,此时会走 default 流程。
然后创建 SQLiteStatementInfo 对象,实际它内部仅包含三个成员:
public final class SQLiteStatementInfo {
/**
* The number of parameters that the statement has.
* SQL语句包含的参数数量
*/
public int numParameters;
/**
* The names of all columns in the result set of the statement.
* 返回查询列名称数组
*/
public String[] columnNames;
/**
* True if the statement is read-only.
* 是否是只读的
*/
public boolean readOnly;
}
SQLiteStatementInfo 起到一个出参入参保存数据的角色,后面会分析到;接着看 SQLiteDatabase 的 getThreadSession 方法,它返回一个 SQLiteSession 对象:
SQLiteSession getThreadSession() {
//从当前线程的ThreadLocal中尝试获取
return mThreadSession.get(); // initialValue() throws if database closed
}
在上篇《Android 存储选项之 SQLiteDatabase 的创建过程源码分析》中有给大家提到,SQLite 数据库操作的同一个句柄同一时间只有一个线程在操作。 Android 中每个 Java 层 SQLiteConnection 对应一个 native 层的 SQLiteConnection,每个 SQLiteConnection 中会包含一个 SQLite 数据库操作句柄。
那它是如何保证多线程句柄操作的呢?实际就是通过 ThreadLocal(线程局部变量),看下它在 SQLiteDatabase 中的定义:
private final ThreadLocal mThreadSession = ThreadLocal
.withInitial(this::createSession);
//如果获取不到则会调用这里,为当前线程创建
//一个新的SQLiteSession
SQLiteSession createSession() {
final SQLiteConnectionPool pool;
synchronized (mLock) {
throwIfNotOpenLocked();
pool = mConnectionPoolLocked;
}
return new SQLiteSession(pool);
}
ThreadLocal 能够保证线程私有,简单来说,ThreadLocal 为每个使用该变量的线程提供私有的变量副本,所以每个线程都可以独立地改变自己的部分,而不会影响到其它线程所对应的副本。也就是数据库操作的每个线程都会拥有自己独立的 SQLiteSession,构造方法如下:
public SQLiteSession(SQLiteConnectionPool connectionPool) {
if (connectionPool == null) {
throw new IllegalArgumentException("connectionPool must not be null");
}
//持有当前SQLiteDatabase的数据库连接池
mConnectionPool = connectionPool;
}
然后调用 SQLiteSeesion 的 prepare 方法:
public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal,
SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
//在数据库连接池中获取一个有效的连接SQLiteConnection
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
//调用SQLiteConnection的prepare方法
mConnection.prepare(sql, outStatementInfo); // might throw
} finally {
releaseConnection(); // might throw
}
}
//调用acquireConnection方法到数据库连接池获取一个有效的数据库连接
private void acquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
//从数据库连接池中获取一个有效的连接
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // might throw
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
由于获取数据库连接的内容相对比较多,主要涉及到 SQLiteConnectionPool 中的工作,你可以参考《Android 数据库之 SQLiteConnectionPool 源码分析》。
如果从连接池正常获取到连接,调用它的 prepare 方法,SQLiteConnection 的 prepare 方法如下:
public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
try {
//获取一个PreparedStatement对象
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
//outStatementInfo为在SQLiteProgram中传入的
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
//获取当前查询的所有列长度
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
//如果获取到列长度为0,此时为空数组
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
//根据长度创建String[]
outStatementInfo.columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
//按照顺序获取所有的列名,保存在SQLiteStatementInfo的String数组中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
}
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
优先根据 SQL 语句通过 acquirePreparedStatement 方法尝试获取一个复用的 PreparedStatement 对象,PreparedStatement 表示预编译后的 SQL 语句对象,SQL 语句被预编译并存储在 PreparedStatement 对象中,然后可以使用此对象多次高效地执行该语句。看下 PreparedStatement 的声明:
private static final class PreparedStatement {
// Next item in pool.
//链表结构
public PreparedStatement mPoolNext;
//对应执行的SQL语句
public String mSql;
//对应native层PreparedStatement
public long mStatementPtr;
//参数数量
public int mNumParameters;
//操作类型
public int mType;
//是否只读
public boolean mReadOnly;
// True if the statement is in the cache.
//是否在缓存中
public boolean mInCache;
//是否正在使用
public boolean mInUse;
}
每个数据库连接池都会缓存执行过的 PreparedStatement,其内部通过 LRU 缓存完成,在 SQLiteConnection 中定义如下:
//缓存预编译后的SQL语句,内部采用LRU算法
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
还记得在创建 SQLiteDatabase 过程中执行的相关配置项,其中就包含了每个 SQLiteConnection 缓存 PreparedStatement 的最大数量 maxSqlCacheSize,它默认大小是 25。
看下 PreparedStatement 的创建过程:
private PreparedStatement acquirePreparedStatement(String sql) {
//根据SQL在缓存获取PreparedStatement
//mPreparedStatementCache是个LRU
PreparedStatement statement = mPreparedStatementCache.get(sql);
boolean skipCache = false;
if (statement != null) {
if (!statement.mInUse) {
return statement;
}
// The statement is already in the cache but is in use (this statement appears
// to be not only re-entrant but recursive!). So prepare a new copy of the
// statement but do not cache it.
skipCache = true;
}
//根据SQLiteConnection和sql语句创建native层PreparedStatement
final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
try {
//获取参数长度
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
//操作类型
final int type = DatabaseUtils.getSqlStatementType(sql);
//是否只读
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
//创建Java层PreparedStatement对象,内部保存native层PreparedStatement匿名描述符
statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
if (!skipCache && isCacheable(type)) {
mPreparedStatementCache.put(sql, statement);
statement.mInCache = true;
}
} catch (RuntimeException ex) {
// Finalize the statement if an exception occurred and we did not add
// it to the cache. If it is already in the cache, then leave it there.
if (statement == null || !statement.mInCache) {
nativeFinalizeStatement(mConnectionPtr, statementPtr);
}
throw ex;
}
statement.mInUse = true;
return statement;
}
mPreparedStatementCache.get(sql) 优先在缓存中进行查找,每个 Java 层 PreparedStatement 对象对应一个 native 层 SQLite 库的 Statement。Statement 可以将它理解为一个轻量但只能向前遍历,没有缓存的 Cursor。
重新回到 SQLiteConnection 的 prepare 方法,将计算得到的相关信息保存在 SQLiteStatementInfo 对象中。
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
//获取当前查询的所有列长度
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
//如果获取到列长度为0,此时为空数组
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
//根据长度创建String[]
outStatementInfo.columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
//按照顺序获取所有的列名,保存在SQLiteStatementInfo的String数组中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
}
}
SQLiteStatementInfo 是在 SQLiteProgram 构造方法传递的出参入参(上面已经贴出),内部保存信息实际赋值给了 SQLiteProgram 的成员:
//调用SQLiteSession的prepare方法
//实际调用到SQLiteConnection的prepare方法中,将相关信息保存在
//info(SQLiteStatementInfo)中返回
db.getThreadSession().prepare(mSql,
db.getThreadDefaultConnectionFlags(assumeReadOnly),
cancellationSignalForPrepare, info);
//是否只读
mReadOnly = info.readOnly;
//列名称数组
mColumnNames = info.columnNames;
//参数长度
mNumParameters = info.numParameters;
实际上在查询过程中,SQLiteQuery 的创建工作主要是完成了对查询结果的列名获取(按顺序)。
重新回到 SQLiteDirectCursorDirver 的 query 方法,SQLiteQuery 执行过程分析完成,再看下 SQLiteCursor:
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
if (query == null) {
throw new IllegalArgumentException("query object cannot be null");
}
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
} else {
mStackTrace = null;
}
//SQLiteDirectCursorDriver
mDriver = driver;
mEditTable = editTable;
//保存当前列名和索引(index)位置
mColumnNameMap = null;
//SQLiteQuery
mQuery = query;
//获取查询结果所有的列名
mColumns = query.getColumnNames();
}
这里我们重点看下 SQLiteQuery 的 getColumnNames 方法,实际调用到其父类 SQLiteProgram 中:
final String[] getColumnNames() {
return mColumnNames;
}
还记得在分析 SQLiteProgram 构造方法中获取当前查询的所有列名称:
for (int i = 0; i < columnCount; i++) {
//按照顺序获取所有的列名,保存在SQLiteStatementInfo的String数组中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
在 SQLiteProgram 中根据当前查询数据列的长度,按照顺序获取对应列的名称保存在 String 数组中。
Cursor 数据获取过程
查询返回 Curosr 对象后,此时我们可以通过 Cursor 完成数据的获取过程,下面就分析下它是如何完成数据获取的。
//从Cursor中获取查询数据
final String name = cursor.getString(cursor.getColumnIndex("name"));
DbLog.e(TAG, "name: " + name);
根据字段名称 cursor.getColumnIndex 获取该字段对应的 index 位置:
@Override
public int getColumnIndex(String columnName) {
// Create mColumnNameMap on demand
if (mColumnNameMap == null) {
String[] columns = mColumns;
//遍历查询到的列名数组
int columnCount = columns.length;
HashMap map = new HashMap(columnCount, 1);
for (int i = 0; i < columnCount; i++) {
//存放如Map中
//实际这里计算Column的index就是根据列的顺序
map.put(columns[i], i);
}
mColumnNameMap = map;
}
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
//返回要查询列名的index
Integer i = mColumnNameMap.get(columnName);
if (i != null) {
return i.intValue();
} else {
return -1;
}
}
按照列名数组的先后顺序将列名称与对应的 index 保存在 Map 容器中。此时通过该 Map 容器获取某个列名对应查询数据的 index 位置,并调用 getString 方法获取查询结果:
/**
* 根据列名Index
*/
@Override
public String getString(int columnIndex) {
checkPosition();
//直接从CursorWindow中get
return mWindow.getString(mPos, columnIndex);
}
可以看到此时直接通过 mWindow 变量进行获取,那 mWindow 是什么?又是在哪里创建的?实际上我们从 Cursor 中读取数据之前一般会通过如下检查:
//遍历Cursor
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
//获取查询数据
}
}
无论是 getCount 或 moveToNext 方法都会去填充 mWindow 的数据。
- getCount 方法
//getCount方法
public int getCount() {
if (mCount == NO_COUNT) {
//默认mCount==NO_COUNT
fillWindow(0);
}
return mCount;
}
- moveToNext 方法
//moveToNext方法
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
//调用moveToPosition方法
public final boolean moveToPosition(int position) {
// 确保位置不超过光标的末端
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;
}
//onMove方法在子类SQLiteCursor中重写
//主要用于判断当前光标位置是否超出CursorWindow
boolean result = onMove(mPos, position);
if (result == false) {
mPos = -1;
} else {
mPos = position;
}
return result;
}
在子类 SQLiteCursor 中重写了 onMove 方法:
//newPosition是新的要读取的位置
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())) {
//mWindow == null,表示初次执行
//newPosition < mWindow.getStartPosition() 读取游标位置 < 起始位置
//newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) 读取游标位置 >= 游标起始位置 + 总长度
fillWindow(newPosition);
}
return true;
}
如果当前游标遍历到缓冲区以外的行此时就会调用 fillWindow 重新查询:
private void fillWindow(int requiredPos) {
//创建或清空当前CursorWindow
clearOrCreateWindow(getDatabase().getPath());
try {
Preconditions.checkArgumentNonnegative(requiredPos,
"requiredPos cannot be negative, but was " + requiredPos);
if (mCount == NO_COUNT) {
//调用SQLiteQuery的fillWindow方法
mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
int startPos = mFillWindowForwardOnly ? requiredPos : 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;
}
}
mWindow 实际是在 SQLiteCursor 父类中声明:
//在SQLiteCursor的父类AbstractWindowedCursor声明。
protected CursorWindow mWindow;
fillWindow 方法的作用主要就是填充数据到该 Cursor Window 缓冲区。CursorWindow 创建过程 :
protected void clearOrCreateWindow(String name) {
if (mWindow == null) {
//创建CursorWindow
mWindow = new CursorWindow(name);
} else {
mWindow.clear();
}
}
CursorWindow 构造方法:
public CursorWindow(String name) {
//创建一个定长的CursorWindow
this(name, getCursorWindowSize());
}
//调用该构造方法
public CursorWindow(String name, @BytesLong long windowSizeBytes) {
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "";
//定长内存区域,native层CursorWindow对象
mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
if (mWindowPtr == 0) {
//如果创建失败则直接抛出异常。
throw new CursorWindowAllocationException("Cursor window allocation of " +
windowSizeBytes + " bytes failed. " + printStats());
}
mCloseGuard.open("close");
recordNewWindow(Binder.getCallingPid(), mWindowPtr);
}
创建一块定长的 native 层缓冲区,用于存放查询结果,该大小一般是固定的 2MB 内存空间,也被称作 Cursor Window。
fillWindow 方法就是填充数据到该内存区域,直到 Cursor Window 空间被放满或遍历完结果集。每当执行 moveToNext 会检查当前游标是否遍历到缓冲区以外的行。如果超过,此时会回调 onMove 重新查询。CursorWindow 会先丢弃之间的数据,重新选定一个开始位置,直到缓冲区被再次填满或遍历完结果集。
CursorWindow 定长内存大小获取过程:
//获取系统系统配置,? * 1KB =
private static int getCursorWindowSize() {
if (sCursorWindowSize < 0) {
// The cursor window size. resource xml file specifies the value in kB.
// convert it to bytes here by multiplying with 1024.
sCursorWindowSize = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_cursorWindowSize) * 1024; // ? * 1KB
}
return sCursorWindowSize;
}
SQLiteCursor 中 fillWindow 方法实际委托到 SQLiteQuery 中:
int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
acquireReference();
try {
window.acquireReference();
try {
//getSession返回SQLiteSession,通过ThreadLocal保证线程私有
//执行SQLiteSession的executeForCursorWindow
//实际执行到了SQLiteConnection中
int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
window, startPos, requiredPos, countAllRows, getConnectionFlags(),
mCancellationSignal);
return numRows;
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} catch (SQLiteException ex) {
Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
throw ex;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
}
}
getSession 前面已经说明过,此时调用 SQLiteSession 的 executeForCursorWindow 方法:
public int executeForCursorWindow(String sql, Object[] bindArgs,
CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
int connectionFlags, CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (window == null) {
throw new IllegalArgumentException("window must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
window.clear();
return 0;
}
//获取一个数据库连接
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForCursorWindow(sql, bindArgs,
window, startPos, requiredPos, countAllRows,
cancellationSignal); // might throw
} finally {
//释放连接,其中就有空闲超时机制
releaseConnection(); // might throw
}
}
直接调用 SQLiteConnection 的 executeForCursorWindow 方法:
public int executeForCursorWindow(String sql, Object[] bindArgs,
CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (window == null) {
throw new IllegalArgumentException("window must not be null.");
}
window.acquireReference();
try {
int actualPos = -1;
int countedRows = -1;
int filledRows = -1;
final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
sql, bindArgs);
try {
//从复用池中获取一个PreparedStatement
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
//调用native方法,CursorWindow的文件描述符传递
final long result = nativeExecuteForCursorWindow(
mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
startPos, requiredPos, countAllRows);
actualPos = (int)(result >> 32);
countedRows = (int)result;
filledRows = window.getNumRows();
window.setStartPosition(actualPos);
return countedRows;
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
if (mRecentOperations.endOperationDeferLog(cookie)) {
mRecentOperations.logOperation(cookie, "window='" + window
+ "', startPos=" + startPos
+ ", actualPos=" + actualPos
+ ", filledRows=" + filledRows
+ ", countedRows=" + countedRows);
}
}
} finally {
window.releaseReference();
}
}
最终还是调用 native 函数完成 Cursor Window 缓冲区的数据填充。实际上这个过程是先将查询数据取出保存到 Cursor Window ,然后再从 Cursor Window 在取出(SQLite -> CursorWindow -> Java),数据经历了两次内存拷贝和转换。
Cursor 中提供的直接获取数据过程,都是委托给 CursorWidow 完成:
/**
* 获取字节数组
*/
@Override
public byte[] getBlob(int columnIndex) {
checkPosition();
return mWindow.getBlob(mPos, columnIndex);
}
/**
* 获取String
*/
@Override
public String getString(int columnIndex) {
checkPosition();
//直接从CursorWindow中get
return mWindow.getString(mPos, columnIndex);
}
小结
Android 框架查询数据库使用的是 Cursor 接口,调用 SQLiteDatabase.query(...) 会返回一个 Cursor,它的实际类型是 SQLiteCursor,Cursor 只是定义获取数据的接口规范。之后就可以使用该 Cursor 遍历结果集了。
Cursor 的实现是分配一个固定 2MB 大小的缓冲区,称作 Cursor Window,用于存放查询结果集。
查询时,先分配 Cursor Window,然后执行 SQL 获取结果集填充该区域,直到 Cursor Window 放满或者遍历完结果集。
如果 Cursor 遍历到缓冲区以外的行,Cursor 会丢弃之前缓冲区的所有内容,跳过已查询过的行重新查询,重新选定一个开始位置填充 Cursor Window 直到缓冲区再次填满或遍历完结果集。
ps:这种场景下,先将数据保存到 Cursor Window 后再取出,中间经历了两次内存拷贝和转换,这是没有必要的,另外由于 Cursor Window 是定长的,对于小结果集需要无故分配 2MB 内存,对于大结果集 2MB 不足以放下,遍历到图中还会引发 Cursor 重新查询,这个消耗就相当大了。
至此 SQLiteDatabase 提供的查询操作就分析完了,数据库的查询操作整个过程还是比较繁琐的。此时再去分析数据库插入、删除、更新等操作会变得非常轻松。
插入、删除、更新等其他操作
下面我们分别以插入、删除、更新操作做下分析
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
//调用自己的
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
//调用内部的insertWithOnConflict方法
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
//引用计数++
acquireReference();
try {
//拼接SQL主语
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && !initialValues.isEmpty())
? 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(')');
//拼接value
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
//创建SQLiteStatement
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
//执行其内部的executeInsert
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
//引用计数--
releaseReference();
}
}
从源码可以看出首先根据传递参数拼接出完整的 SQL 语句,接着创建 SQLiteStatement 对象,执行 executeInsert 方法。
在 SQLiteDatabase 中除了查询操作,插入、删除和更新操作都是通过 SQLiteStatement 完成的,构造方法如下
SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
super(db, sql, bindArgs, null);
}
SQLiteStatement 也是继承自 SQLiteProgram,关于 SQLiteProgram 在前面已经做过分析,这里不再赘述。
看下 SQLiteStatement 中提供的插入、删除、更新相关方法:
//数据库插入方法
public long executeInsert() {
//引用计数++
acquireReference();
try {
//这里getSession 返回 SQLiteSession
//这还是执行的SQLiteConnection 的 executeForLastInsertedRowId
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
//引用计数--
releaseReference();
}
}
//数据库更新/删除方法
public int executeUpdateDelete() {
//引用计数++
acquireReference();
try {
//getSession() 返回 SQLiteSession
//最终执行 SQLiteConnection 的 executeForChangedRowCount 方法
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
//引用计数--
releaseReference();
}
}
SQLiteDatabase 的更新和删除方法都是 executeUpdateDelete 方法完成,与 executeInsert 相比几乎没有差别(API 名称不同,完成的业务逻辑不同)。
方法 getSession 整个过程,在分析数据库查询时已经做了详细的介绍,这里不再跟入,直接看 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 {
//一样的套路,获取复用的PreparedStatement
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
//绑定参数
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
//执行native方法
return nativeExecuteForLastInsertedRowId(
mConnectionPtr, statement.mStatementPtr);
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
//回收该PreparedStatement
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
源码中大部分内容在 query 阶段都已经做过分析,其最终核心还是调用了 native 层 nativeExecuteForLastInsertedRowId 方法完成数据库插入任务。
static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz,
469 jlong connectionPtr, jlong statementPtr) {
//转成对应的SQLiteConnection
470 SQLiteConnection* connection = reinterpret_cast(connectionPtr);
//转换成对应的PreparedStatement
471 sqlite3_stmt* statement = reinterpret_cast(statementPtr);
472
473 int err = executeNonQuery(env, connection, statement);
474 return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
475 ? sqlite3_last_insert_rowid(connection->db) : -1;
476}
static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
/最终根据PreparedStatement调用到SQLite中
441 int err = sqlite3_step(statement);
442 if (err == SQLITE_ROW) {
//这里表示不能使用该api完成查询操作
443 throw_sqlite3_exception(env,
444 "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
445 } else if (err != SQLITE_DONE) {
//发生异常,数据库没有正确操作完成
446 throw_sqlite3_exception(env, connection->db);
447 }
448 return err;
449}
Cursor 注意事项
1. Cursor 的关闭逻辑
通过前面的分析,我们得知 Cursor 内部持有一个定长的 native 内存区域 CursorWindow。
看下 Cursor 的 close 方法(本文依据 API Level 28):
//实际SQLiteCursor的close方法
public void close() {
//调用父类的close方法
super.close();
synchronized (this) {
mQuery.close();
mDriver.cursorClosed();
}
}
SQLiteCursor 的继承关系关系:SQLiteCursor —> AbstractWindowedCursor -> AbstractCursor -> ... -> Cursor。
此时调用其父类 AbstractCursor 的 close 方法:
//调用其父类AbstractCursor的close
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
//CursorWindow的关闭过程主要在这里
onDeactivateOrClose();
}
//onDeactivateOrClose在SQLiteCursor的直接父类
//AbstractWindowedCursor中重写
protected void onDeactivateOrClose() {
super.onDeactivateOrClose();
//调用CursorWindow的close
closeWindow();
}
//在这里调用CursorWindow的close方法
protected void closeWindow() {
if (mWindow != null) {
//调用Cursor的close方法
mWindow.close();
mWindow = null;
}
}
2. CursorWindow 的关闭操作
在 Cursor 的 close 方法中调用了 CursorWindow 的 close,想必大家肯定猜得到,此时就是要去释放那块定长的 Cursor Window 缓冲区。CursorWindow 继承自 SQLiteCloseable,关于 SQLiteCloseable 的作用,在文章最开始就已经做了详细的分析。
//调用CursorWindow的close,实际调用到SQLiteCloseable中close方法
public void close() {
releaseReference();
}
public void releaseReference() {
boolean refCountIsZero = false;
synchronized (this) {
//释放对该对象的引用
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
//当前不存在任何引用
//通知关闭
onAllReferencesReleased();
}
}
//在 CursorWindow 中重写了onAllReferencesReleased
protected void onAllReferencesReleased() {
dispose();
}
//释放CursorWindow持有的native内存在这里
private void dispose() {
if (mCloseGuard != null) {
mCloseGuard.close();
}
if (mWindowPtr != 0) {
recordClosingOfWindow(mWindowPtr);
//通知释放CursorWindow的native内存
nativeDispose(mWindowPtr);
//将指向置为0
mWindowPtr = 0;
}
}
正如我们猜想,重点是要释放 Cursor Window 分配的定长内存空间。
3. Cursor 兜底回收策略
上面我们通过主动调用 Cursor 的 close 方法能够有效地避免 CursorWindow 发生内存泄漏,如果我们忘记调用,那 Cursor Window 就一定会发生内存泄漏吗?
SQLiteCursor 的 finalize 方法:
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
//...日志信息,省略
//调用close方法
close();
}
} finally {
super.finalize();
}
}
从这里可以看出,当垃圾收集器开始回收 SQLiteCursor 对象时,在其 finalize 方法中会调用 close 方法,从而避免 CursorWindow 的内存泄漏。
关于 finalize 再多说几句,每当 GC 要回收某个对象时首先会调用它的 finalize 方法,决定是否要调用取决于当前对象是否重写了该方法。finalize 方法只会被调用一次。
总结
相比插入、删除、更新等常用数据库操作,查询要相对繁琐很多,查询时先分配 Curosr Window,然后执行 SQL 获取结果集填充之,直到缓冲区再次填满或遍历完结果集。这样虽然能保证正常工作,但在很多时候却不是最优实现。当查询数据较小是的时候,可能不一定划算,而且数据还要经过两次内存拷贝。这都是没有必要的,因为大多数数据库操作都是获取 Cursor 直接遍历获取数据后关闭。那该如何优化它呢?你可以参考微信开源的 WCDB。
WCDB 完全集成自己的数据库源码,从实现上看提供了 SQLiteDircetCusor 对 native 的 Statement 简单做下封装,直接通过 Statement 完成数据的获取。这样能够很好的解决 Cursor Window 额外的内存消耗,特别是结果集大于 2 MB 的场合。
//直接通过底层的Statement完成数据获取
public long getLong(int column) {
return nativeGetLong(mPreparedStatement.getPtr(), column);
}
public double getDouble(int column) {
return nativeGetDouble(mPreparedStatement.getPtr(), column);
}
public String getString(int column) {
return nativeGetString(mPreparedStatement.getPtr(), column);
}
public byte[] getBlob(int column) {
return nativeGetBlob(mPreparedStatement.getPtr(), column);
}
以上便是个人在学习 SQLiteDatabase 时的心得和体会,文中分析如有不妥或更好的分析结果,还请大家指出!
文章如果对你有帮助,就请留个赞吧!