Android ContentProvider的執行緒安全(二)
框架層原理
從Provider使用方(Client App)通過getContentResolver()獲取到Provider的遠端物件,是一個三方的流程:
(1)Client App向AMS獲取Provider遠端物件;
(2)AMS會檢查對應的Provider遠端物件有沒有釋出到AMS,如果有,則直接返回給Client App;如果沒有,則啟動Provider App程序,並觸發Provider App程序安裝Provider;
(3)Provider App安裝完成Provider之後,會呼叫AMS釋出Provider遠端物件到AMS;
(4)AMS將Provider物件返回給Client App。
以上過程中,對於Client App看起來,整個過程是同步的;在AMS中,獲取Provider相關的方法都有同步鎖,所以這個Provider遠端物件實際上是同一個。下圖說明這個過程:
Client App獲取到Binder遠端Provider物件之後,呼叫其資料操作方法,是在Provider App的Binder執行緒池中執行。
所以,在Provider層面,系統框架為App做到了Provider的單例,但沒有做到Provider的執行緒安全。這需要在資料層面上自行實現。
對於資料庫的場景,Android提供了SQLite。下面要看框架層對於SQLite的執行緒安全情況。
2.SQLiteDatabase/SQLiteOpenHelper執行緒安全
Android底層集成了SQLite資料庫,在框架層,提供了一系列的API輔助訪問SQLite資料庫。這些類位於android.database.sqlite。
對於資料庫,主要有兩類操作涉及執行緒安全:第一、建立/升級/降級資料庫;第二、資料的增刪改查操作。
2.1 建立/升級/降級資料庫
SQLiteOpenHelper是Android框架層提供給APP使用的開啟SQLite資料庫幫助API,常常和Provider捆綁使用。通過實現如下方法來實現資料庫建立、升級、降級:
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
* @param db The database.
*/
public abstract void onCreate(SQLiteDatabase db);
/**
* Called when the database needs to be upgraded. The implementation
* should use this method to drop tables, add tables, or do anything else it
* needs to upgrade to the new schema version.
*
* <p>
* The SQLite ALTER TABLE documentation can be found
* <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
* you can use ALTER TABLE to rename the old table, then create the new table and then
* populate the new table with the contents of the old table.
* </p><p>
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
* </p>
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**
* Called when the database needs to be downgraded. This is strictly similar to
* {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
* However, this method is not abstract, so it is not mandatory for a customer to
* implement it. If not overridden, default implementation will reject downgrade and
* throws SQLiteException
*
* <p>
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
* </p>
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
throw new SQLiteException("Can't downgrade database from version " +
oldVersion + " to " + newVersion);
}
這些回撥都不需要顯式呼叫,那麼這些方法在內部是怎樣被呼叫?在SQLiteOpenHelper.getWritableDatabase()/getReadableDatabase()
/**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
* called.
*
* <p>Once opened successfully, the database is cached, so you can
* call this method every time you need to write to the database.
* (Make sure to call {@link #close} when you no longer need the database.)
* Errors such as bad permissions or a full disk may cause this method
* to fail, but future attempts may succeed if the problem is fixed.</p>
*
* <p class="caution">Database upgrade may take a long time, you
* should not call this method from the application main thread, including
* from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened for writing
* @return a read/write database object valid until {@link #close} is called
*/
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
/**
* Create and/or open a database. This will be the same object returned by
* {@link #getWritableDatabase} unless some problem, such as a full disk,
* requires the database to be opened read-only. In that case, a read-only
* database object will be returned. If the problem is fixed, a future call
* to {@link #getWritableDatabase} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned
* in the future.
*
* <p class="caution">Like {@link #getWritableDatabase}, this method may
* take a long time to return, so you should not call it from the
* application main thread, including from
* {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened
* @return a database object valid until {@link #getWritableDatabase}
* or {@link #close} is called.
*/
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
SQLiteOpenHelper在設計上是將資料庫建立升降級的邏輯放到實際使用資料庫的時候才做,是一種懶載入機制,也正是這樣的設計,使得APP可以將這部分邏輯從Provider的onCreate()移到SQLiteOpenHelper的onCreate(),減少Provider.onCreate()的壓力。Provider.onCreate()是APP程序在啟動的時候執行在主執行緒。
可以看到,SQLiteOpenHelper.getWritableDatabase()/getReadableDatabase()業務邏輯的實現在另一個方法getDatabaseLocked(),在呼叫getDatabaseLocked()時候加了自身物件的同步鎖。這樣一來,資料庫建立/升級/降級的邏輯也就被自身物件的同步鎖包含。
第一部分中分析過,Provider是單例物件,但可能會在多個執行緒中執行資料操作的方法。那麼,如果Provider中使用的是同一個SQLiteOpenHelper例項,是可以保證資料庫建立/升級/降級執行緒安全的。