上一篇大概讲了SQLiteDatabase基本使用和数据库的帮助类SQLiteOpenHelper。
SQLite是封装好的一套C库,那么android是怎么调用的呢?以及实现的它的思路呢
首先看一下SQLiteDatabase的UML类图:
首先看SQLiteDatabase类,从类图看主要提供增删改查方法和执行sql的方法,另外持有一个SQLiteConnectionPool、和一个持有SQLiteSession的ThreadLocal用来处理共享对象多线程的问题,还有一个数据库配置SQLiteDatabaseConfiguration下面会提到这个类。
// The connection pool for the database, null when closed.
// The pool itself is thread-safe, but the reference to it can only be acquired
// when the lock is held.
// INVARIANT: Guarded by mLock.
private SQLiteConnectionPool mConnectionPoolLocked;
private final ThreadLocal mThreadSession = new ThreadLocal() {
@Override
protected SQLiteSession initialValue() {
return createSession();
}
};
// The database configuration.
// INVARIANT: Guarded by mLock.
private final SQLiteDatabaseConfiguration mConfigurationLocked;
SQLiteProgram 将SQL语句简单的分类和分发给SQLiteConnection准备执行,通过持有的SQLiteDatabase获取到SQLiteSession然后将分类的语句传给SQLiteConnection来准备执行。
SQLiteStatement继承于SQLiteProgram ,分发给SQLiteSession执行sql语句,以及对数据库的引用和释放也都在这里。
SQLiteSession数据库操作是使用会话来进行的,主要用来管理事务和数据库连接的生命周期,持有SQLiteConnectionPool,所以可以拿到SQLiteConnection。
SQLiteConnection 数据库连接,通过navive方法来操作C库。包括连接数据库、数据库语法校验、事务。。。
SQLiteConnectionPool 数据库连接池,管理数据库连接以及释放的链接。
SQLiteDatabaseConfiguration 生成数据库连接池的时候会用到的数据库配置类。路径、标签、状态、默认大小、SQL语句缓存大小。。。
大致有了流程之后,我们再仔细的分析一下SQLiteDatabase是怎么执行增删改查操作的。
我们就看数据库插入一条数据的执行流程吧,
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
db.open();
return db;
}
当SQLiteDatabase执行openDatabase的时候,创建了数据库对象并执行open方法。
private void open() {
try {
try {
openInner();
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
openInner();
}
} catch (SQLiteException ex) {
Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
close();
throw ex;
}
}
private void openInner() {
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
}
}
可以看到打开的时候其实是创建了一个数据库连接池,并且这个打开操作是加了一个异步锁的。并且将创建的数据库对象存到了weakhashmap中。
SQLiteConnectionPool的创建和打开其实就是创建了连接池和一个SQLiteConnection(数据库主连接)。上面提到的数据库配置类SQLiteDatabaseConfiguration就是用来配置连接池的。
所以打开数据库大致就是:
1、打开SQLiteConnectionPool。
2、创建SQLiteConnection。
android.database.sqlite.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;
}
}
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
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.size() > 0)
? 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(')');
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
从代码来看首先将需要插入的数据和表名拆分成SQL语句然后通过SQLiteStatement将语句进一步的拆分,然后通过获取一个数据库会话SQLiteSession来执行操作。
然后我们看
android.database.sqlite.
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;
}
if (bindArgs != null && bindArgs.length > mNumParameters) {
throw new IllegalArgumentException("Too many bind arguments. "
+ bindArgs.length + " arguments were provided but the statement needs "
+ mNumParameters + " arguments.");
}
if (mNumParameters != 0) {
mBindArgs = new Object[mNumParameters];
if (bindArgs != null) {
System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
}
} else {
mBindArgs = null;
}
}
SQLiteStatement的父类SQLiteProgram将SQL语句拆分和重新的组装,然后通过SQLiteDatabase获取一个SQLiteSession去执行命令。
public void execute() {
acquireReference();
try {
getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
SQLiteSession因为创建的时候引用SQLiteConnectionPool,所以通过连接池获取一个SQLiteConnection连接来去真正的将操作命令分发给C库来执行数据库语句。
获取数据库连接
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;
}
public void execute(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
mConnection.execute(sql, bindArgs, cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
总结一下大概就是:
1、SQLiteStatement先将命令分析和处理。
2、SQLiteSession通过SQLiteConnectionPool获取一个SQLiteConnection真正的去执行操作语句。
至此一个数据库操作语句从开始的打开数据库以及执行插入方法到真正的C库执行就完成,同样的删除、修改以及执行SQL都是差不多。但是源码还有很多的细节东西比如每次打开或者操作数据库的时候都会获取引用以及释放引用。还有锁的问题都是很不错可以学习的地方。尤其是对于引用和释放SQLiteDatabase通过一个简单内部计数原则来实现还是很有借鉴意义的,当有引用的时候int 加1,当引用释放掉减1,这样当关闭的时候可以通过判断计数是否小于0来真正的关闭,以防有其他在引用的时候关闭造成的数据库异常。