我们一般回调用SQLiteOpenHelper的getWritableDatabase,getReadableDatabase来打开一个可写或者只读数据库,其实都是通过getDatabaseLocked来实现,writable值不同而已,源码分析如下
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// 用户调用 mDatabase.close()关闭数据库,下面会重新打开
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// 只读或者不要求写操作,会使用已经打开的数据库
return mDatabase;
}
}
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
if (writable && db.isReadOnly()) {
//打开可写模式,所以getReadableDatabase可以转换成getWritableDatabase
db.reopenReadWrite();
}
} else if (mName == null) {
//mName是数据库名字
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 {
//打开可预写数据库
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(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);
}
//mMinimumSupportedVersion 默认是0,实例helper时可以设置
if (version > 0 && version < mMinimumSupportedVersion) {
File databaseFile = new File(db.getPath());
onBeforeDelete(db);
db.close();
//输出旧数据库,并查询打开
if (SQLiteDatabase.deleteDatabase(databaseFile)) {
mIsInitializing = false;
return getDatabaseLocked(writable);
} else {
throw new IllegalStateException("Unable to delete obsolete database "
+ mName + " with version " + version);
}
} else {
db.beginTransaction();//开启业务,保持原子性
try {
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(db);
if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
//异常情况会关闭数据库
db.close();
}
}
}
小总结:数据库未关闭可以复用,所以getReadableDatabase也有可能的可写的,取决于上一次操作,打开数据库后查询版本号是否,就行升级或降级处理,或者删除数据库重头开始执行,最终才返回数据库,如果是升级数据库,时间可能比较久,不建议在主线程执行,以免造成卡顿,所以要处理好版本升级的问题。
//SQLiteDatabase.java
public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
@DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1);
db.open();
return db;
}
SQLiteConnectionPool
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;
}
根据传入的参数拼接sql语句,再交给SQLiteStatement处理
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
//冲突模式 {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};默认无
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(')');
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();
}
}
SQLiteStatement 初始化数据,设置flag,只读或者可写,或者WAL(SQLiteStatement是SQLiteStatement的父类)
//SQLiteProgram.java 初始化参数,是否只写,字段,值
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);
mReadOnly = info.readOnly;
mColumnNames = info.columnNames;
mNumParameters = info.numParameters;
break;
}
// SQLiteDatabase.java 设置flag
int getThreadDefaultConnectionFlags(boolean readOnly) {
int flags = readOnly ? SQLiteConnectionPool.CONNECTION_FLAG_READ_ONLY :
SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY;
if (isMainThread()) {
flags |= SQLiteConnectionPool.CONNECTION_FLAG_INTERACTIVE;
}
return flags;
}
SQLiteSession 最终执行
//SQLiteDatabase.java
SQLiteSession getThreadSession() {
//每个线程持不同Session对象
return mThreadSession.get(); // initialValue() throws if database closed
}
Session保存在ThreadLocal,所以每个thread都有自己的Session实例
public long executeInsert() {
acquireReference();
try {
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
SQLiteConnection naviti方法封装,真正执行sql方法
// SQLiteSession.java 获取SQLiteConnection对象
private void acquireConnectionSQLiteConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // might throw
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
使用了lock,未开启WAL只能同时执行一个读或写操作,mMaxConnectionPoolSize=1,只有mAvailablePrimaryConnection主连接,打开WAL,可同时执行多个读操作,但写只能同时存在一个,读写可以并行,
//SQLiteConnectionPool.java
public SQLiteConnection acquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
SQLiteConnection con = waitForConnection(sql, connectionFlags, cancellationSignal);
synchronized (mLock) {
if (mIdleConnectionHandler != null) {
mIdleConnectionHandler.connectionAcquired(con);
}
}
return con;
}
private SQLiteConnection waitForConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
//是否获取主链接,只读操作为false,查看上面的getThreadDefaultConnectionFlags方法
final boolean wantPrimaryConnection =
(connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
......
// Try to acquire a connection.
SQLiteConnection connection = null;
if (!wantPrimaryConnection) {
// 从连接列表获取,写操作不从这里获取
// private final ArrayList mAvailableNonPrimaryConnections = new ArrayList();
connection = tryAcquireNonPrimaryConnectionLocked(
sql, connectionFlags); // might throw
}
if (connection == null) {
// 从主连接获取
//private SQLiteConnection mAvailablePrimaryConnection;
connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
}
if (connection != null) {
return connection;
}
打开WAL可以增加连接数,SQLiteDatabase.enableWriteAheadLogging disableWriteAheadLogging,可打开或关闭读写并行,打开后内存消耗也会增加, 下面为enableWriteAheadLogging的注释
* When write-ahead logging is not enabled (the default), it is not possible for
* reads and writes to occur on the database at the same time. Before modifying the
* database, the writer implicitly acquires an exclusive lock on the database which
* prevents readers from accessing the database until the write is completed.
* It is a good idea to enable write-ahead logging whenever a database will be
* concurrently accessed and modified by multiple threads at the same time.
* However, write-ahead logging uses significantly more memory than ordinary
* journaling because there are multiple connections to the same database.
* So if a database will only be used by a single thread, or if optimizing
* concurrency is not very important, then write-ahead logging should be disabled.
//SQLiteDatabase.java 获取最多的执行数,由系统决定
private void setMaxConnectionPoolSizeLocked() {
if (!mConfiguration.isInMemoryDb()
&& (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
} else {
// We don't actually need to always restrict the connection pool size to 1
// for non-WAL databases. There might be reasons to use connection pooling
// with other journal modes. However, we should always keep pool size of 1 for in-memory
// databases since every :memory: db is separate from another.
// For now, enabling connection pooling and using WAL are the same thing in the API.
mMaxConnectionPoolSize = 1;
}
}