In Android, sqlite is used. The most commonly used classes are SQLiteOpenHelper and SQLiteDatabased. The most commonly used operation is to create and open a database and operate on data. The longest operation is insert delete update and query. Query, that is, the select Operation, involves cursor and so on.
The preceding operations mainly involve SQLiteDatabase SQLiteSession SQLiteConnectionPool SQLiteConnection. This article briefly analyzes the internal process of operating sqlite on Android.
1. Main class member variables
Public final class SQLiteDatabase extends SQLiteClosable {private static WeakHashMap
SActiveDatabases = new WeakHashMap
(); // Store reference private final ThreadLocal for all opened Databases
MThreadSession = new ThreadLocal
() {@ Override protected SQLiteSession initialValue () {// each thread has its own mThreadSeesion return createSession () ;}}; private final CursorFactory mCursorFactory; // Cursor factory class, to customize the Cursor private final SQLiteDatabaseConfiguration mConfigurationLocked; // configure the database private SQLiteConnectionPool mConnectionPoolLocked; // The database connection pool ......}
Public final class SQLiteSession {private final extends mConnectionPool; // connection pool private SQLiteConnection mConnection; // connection private int mConnectionFlags; private int mConnectionUseCount; private Transaction mTransactionPool; // Transaction pool private Transaction mTransactionStack; // transaction stack ......}
Public final class SQLiteConnectionPool implements Closeable {private final SQLiteDatabaseConfiguration mConfiguration; private int mMaxConnectionPoolSize; private boolean mIsOpen; private int mNextConnectionId; private ConnectionWaiter mConnectionWaiterPool; // The Connection wait pool is actually a chain consisting of waiting connections private ConnectionWaiter mConnectionWaiterQueue; // connection wait queue private final ArrayList
MAvailableNonPrimaryConnections = new ArrayList
(); // Strong reference, non-master connection private SQLiteConnection mAvailablePrimaryConnection; // The master connection has only one private final WeakHashMap
MAcquiredConnections = new WeakHashMap
(); // Weak reference, connected ......}
Public final class SQLiteConnection implements CancellationSignal. onCancelListener {private final SQLiteConnectionPool mPool; private final SQLiteDatabaseConfiguration mConfiguration; private final int mConnectionId; private final boolean mIsPrimaryConnection; private final boolean mIsReadOnlyConnection; private final PreparedStatementCache listener; // The stmt cache strongly references private PreparedSt Atement mPreparedStatementPool; private int mConnectionPtr; // native layer SQLiteConnection pointer ......}
2. Call status when opening the database
When we use SQLiteOpenHelper:
①
Create a new help class. When getReadableDatabase or getWritableDatabase is created, the second entry to ② is newly opened.
If you have already used the help class, if you have already used the manual mDatabase. close, the second entry in ② is opened.
When getReadableDatabase is used, whether getReadableDatabase or getWritableDatabase is used last time, mDatabase is directly returned,
When getWritableDatabase is used, if the previous request is getWritableDatabase, The mDataBase is still directly returned. If the last request is getReadableDatabase, the first entry in ② is opened in read/write mode.
②
If you already have a help class, if you want to write but read-only, that is, the last time getReadableDatabas was getWritableDatabase, re-open db. reopenreadwrite in read/write mode.
Otherwise, if getReadableDatabase is enabled, use SQLiteDatabase. openDatabase to open the read-only database. If getWritableDatabase is enabled through mContext. openOrCreateDatabase, it is still opened through SQLiteDatabase. openDatabase. At this time, the flag has been changed to create_if.
See SQLiteDatabase
Public static SQLiteDatabase openDatabase (String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {SQLiteDatabase db = new SQLiteDatabase (path, flags, factory, errorHandler); db. open (); // open will call openInner to omit return db;} private void openInner () {synchronized (mLock) {assert mConnectionPoolLocked = null; mConnectionPoolLocked = SQLiteConnectionPool. open (mConfigurationLocked); mCloseGuardLocked. open ("close");} synchronized (sActiveDatabases) {sActiveDatabases. put (this, null); // put in sActiveDatabases }}
SQLiteDatabase holds its own connection pool and is obtained when it is open, in SQLiteConnectionPool
Public static SQLiteConnectionPool open (SQLiteDatabaseConfiguration configuration) {SQLiteConnectionPool pool = new SQLiteConnectionPool (configuration); pool. open (); return pool;} private void open () {mAvailablePrimaryConnection = openConnectionLocked (mConfiguration, true/* primaryConnection */); // open the connection pool to open a master connection. mIsOpen = true; mCloseGuard. open ("close");} private SQLiteConnection openConnectionLocked (SQLiteDatabaseConfiguration configuration, boolean primaryConnection) {final int connectionId = mNextConnectionId ++; return SQLiteConnection. open (this, configuration, // call connection. open () connectionId, primaryConnection );}
SQLiteConnection is called at the native level, and the open method is relatively simple.
Private void open (){//---!!! NativeOpen and set the corresponding parameter mConnectionPtr = nativeOpen (mConfiguration. path, mConfiguration. openFlags, mConfiguration. label, SQLiteDebug. DEBUG_ SQL _STATEMENTS, SQLiteDebug. DEBUG_ SQL _TIME); setPageSize (); setForeignKeyModeFromConfiguration (); setWalModeFromConfiguration (); setJournalSizeLimit (); setAutoCheckpointInterval (); setLocaleFromConfiguration ();}
The process is very simple. What is confusing is the role of SQLiteConnectionPool here. What is even more confusing is that the connection pool only displays a primary connection.
The most important member in SQLiteConnectionPool is acquireConnection, which indicates the class and method to call this member.
As you can see, some are related methods when starting a transaction, some are used when preparing statement, and some are used by cursor when querying. Some operations are related to SQL statements with or without returned results, but there is no external call or internal call. Actually, it is not. The last part is the commonly used insert delete update. The details are as follows.
<喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> Examples/Txicj4KPHA + PC9wPgo8cD48L3A + Examples "brush: java;"> public void execSQL (String SQL) throws SQLException {// execute a single SQL executeSql (SQL, null);} private int executeSql (String SQL, Object [] bindArgs) throws SQLException {...... SQLiteStatement statement = new SQLiteStatement (this, SQL, bindArgs); // get statement try {return statement.exe cuteUpdateDelete ();} finally {statement. close () ;}} public int executeUpdateDelete () {acquireReference (); try {return getsession(cmd.exe cuteForChangedRowCount (// getSession getSql (), getBindArgs (), getConnectionFlags (), null) ;}catch (sqlitedatabase‑uptexception ex) {on‑uption (); throw ex ;}finally {releaseReference ();}}
Protected final SQLiteSession getSession () {// return mDatabase in SQLiteProgram. getThreadSession ();} SQLiteSession getThreadSession () {// return mThreadSession in SQLiteDatabase. get (); // corresponds to part 1 and each thread has its own Session}
Continue to view getsession(cmd.exe cuteForChangedRowCount
// Public int executeForChangedRowCount (String SQL, Object [] bindArgs, int connectionFlags, CancellationSignal cancellationSignal ){...... AcquireConnection (SQL, connectionFlags, cancellationSignal); // get connection try {return mConnection.exe cuteForChangedRowCount (SQL, bindArgs, // execute cancellationSignal through connection);} finally {releaseConnection ();}} // private void acquireConnection (String SQL, int connectionFlags, CancellationSignal cancellationSignal) {if (mConnection = null) {assert mConnectionUseCount = 0; mConnection = mConnectionPool. acquireConnection (SQL, connectionFlags, // obtain the connection cancellationSignal in the connection pool); // might throw mConnectionFlags = connectionFlags;} mConnectionUseCount + = 1 ;}
Finally, SQLiteConnectionPool. acquireConnection is found.
// Public SQLiteConnection acquireConnection (String SQL, int connectionFlags, CancellationSignal cancellationSignal) {return waitForConnection (SQL, connectionFlags, callback);} private SQLiteConnection waitForConnection (String SQL, int connectionFlags, cancellationSignal cancellationSignal) {final boolean wantPrimaryConnection = // whether the master connection is required, obtained through Flag (connectionFlag S & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY )! = 0; final ConnectionWaiter waiter; synchronized (mLock) {SQLiteConnection connection = null; if (! WantPrimaryConnection) {// try to get non-master connection = tryAcquireNonPrimaryConnectionLocked (SQL, connectionFlags);} if (connection = null) {// --- try to get master connection = tryAcquirePrimaryConnectionLocked (connectionFlags);} if (connection! = Null) {return connection;} // if no connection is available, generate a waiter final int priority = getPriority (connectionFlags); final long startTime = SystemClock. uptimeMillis (); waiter = obtainConnectionWaiterLocked (Thread. currentThread (), startTime, priority, wantPrimaryConnection, SQL, connectionFlags); // Insert the queue ConnectionWaiter predecessor = null according to the priority; ConnectionWaiter successor = mConnectionWaiterQueue; while (success Or! = Null) {if (priority> successor. mPriority) {waiter. mNext = successor; break;} predecessor = successor; successor = successor. mNext;} if (predecessor! = Null) {predecessor. mNext = waiter;} else {mConnectionWaiterQueue = waiter;} nonce = waiter. mNonce ;}......}
In this example, connectionFlags is used to determine whether to obtain the primary connection. For example, when the database is opened in step 1, the primary connection is used.
Private SQLiteConnection tryAcquirePrimaryConnectionLocked (int connectionFlags) {// you can obtain the master connection and return SQLiteConnection connection = mAvailablePrimaryConnection; if (connection! = Null) {mAvailablePrimaryConnection = null; finishAcquireConnectionLocked (connection, connectionFlags); return connection;} // null for (SQLiteConnection acquiredConnection: mAcquiredConnections. keySet () {if (acquiredConnection. isPrimaryConnection () {return null ;}// a new primary connection does not exist. It is only possible that connection = openConnectionLocked (mConfiguration, true/* primaryConnection */) occurs during the first access; finishAcquireConnectionLocked (connection, connectionFlags); return connection ;}
In other cases, for example, the update in this section depends on the statement attribute and is represented by its member variable mReadOnly. It is determined when the SQL statement is converted to stmt, that is, prepare. For example, the begin commit command is false.
Private SQLiteConnection tryAcquireNonPrimaryConnectionLocked (String SQL, int connectionFlags) {// try to obtain the next connection SQLiteConnection connection in the non-master connection queue; final int availableCount = mavaillenabonprimaryconnections. size (); if (availableCount> 1 & SQL! = Null) {// If SQL! = Null first use connection for (int I = 0; I <availableCount; I ++) {connection = mAvailableNonPrimaryConnections that contains the same SQL statement in the cache. get (I); if (connection. isPreparedStatementInCache (SQL) {mAvailableNonPrimaryConnections. remove (I); finishAcquireConnectionLocked (connection, connectionFlags); // might throw return connection ;}} if (availableCount> 0) {// otherwise, the next connection is obtained, actually the last connection = mAvailableNonP of the pool RimaryConnections. remove (availableCount-1); finishAcquireConnectionLocked (connection, connectionFlags); // might throw return connection;} // --- expand the connection pool if necessary, that is, the pool is not connected, int openConnections = mAcquiredConnections. size (); if (mAvailablePrimaryConnection! = Null) {openConnections + = 1;} if (openConnections> = mMaxConnectionPoolSize) {return null;} connection = openConnectionLocked (mConfiguration, // open a new non-master connection, connect to nativeOpen false/* primaryConnection */); finishAcquireConnectionLocked (connection, connectionFlags); // puts the new connection into mAcquiredConnections return connection ;}
So how does the connection in mAvailableNonPrimaryConnections come from?
When acquireConnection is used, releaseConnection must be used.
// Private void releaseConnection () {assert mConnection in SQLiteSession! = Null; assert mConnectionUseCount> 0; if (-- mConnectionUseCount = 0) {try {mConnectionPool. releaseConnection (mConnection); // might throw} finally {mConnection = null ;}}// public void releaseConnection (SQLiteConnection connection) {synchronized (mLock) in SQLiteConnectionPool) {AcquiredConnectionStatus status = mAcquiredConnections. remove (connection); if (status = null) {throw new Ille GalStateException ("Cannot perform this operation" + "because the specified connection was not acquired" + "from this pool or has already been released.");} if (! MIsOpen) {closeConnectionAndLogExceptionsLocked (connection);} else if (connection. isPrimaryConnection () {if (recycleConnectionLocked (connection, status) {assert mAvailablePrimaryConnection = null; mAvailablePrimaryConnection = connection; // put the master connection} wakeConnectionWaitersLocked ();} else if (mAvailableNonPrimaryConnections. size ()> = mMaxConnectionPoolSize-1) {closeConnectionAndLogExceptionsLocked (connection);} else {if (recycleConnectionLocked (connection, status) {mAvailableNonPrimaryConnections. add (connection); Put in non-master connection} wakeConnectionWaitersLocked ();}}}
A connection may belong to SQLiteConnectionPool or SQLiteSession
SQLiteSession obtains the connection from SQLiteConnectionPool through acquire and returns it through release.
4. Summary
① In Android SQLite, most operations must go through SQLiteDatabase-> SQLiteSession-> SQLiteConnectionPool-> SQLiteConnection
② The SQLiteOpenHelper class can help to achieve a maximum of one SQLiteDatabase object in an instance, no matter how many times getReadableDatabase getWritableDatabase has passed through db. close ().
③ SQLiteDatabase. openDatabase is the process of constructing the SQLiteDatabase object. The essence is the process of constructing the SQLiteDatabase member variable SQLiteConnectionPool, which is a process of obtaining the primaryConnection.
④ Each thread has its own SQLiteSession and only one SQLiteSession. Each SQLiteSession has only one SQLiteConnection at a time point (obtained from the connection pool when necessary and returned after use ), this ensures that a thread has only one SQLiteConnection to a SQLiteDatabase at a certain time. Transactions are also implemented through sessions, so transactions between threads are independent.
⑤ SQLiteConnectionPool is in charge of the connection pool of a SQLiteDatabase. Ensure that there is only one PrimaryConnection. If it is idle, it will be returned. If it is being used by another session, it will return NULL. If it does not exist, it will be created. For non-PrimaryConnection, the same stmt will be preferentially selected in the connection pool. If there is no same get pool, if the pool is empty (multiple threads use multiple connections at the same time), create a non-primary connection.
6. The relationship is as follows: