从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的线程安全情况。
Android底层集成了SQLite数据库,在框架层,提供了一系列的API辅助访问SQLite数据库。这些类位于android.database.sqlite。
对于数据库,主要有两类操作涉及线程安全:第一、创建/升级/降级数据库;第二、数据的增删改查操作。
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.
*
*
* The SQLite ALTER TABLE documentation can be found
* "http://sqlite.org/lang_altertable.html">here. 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.
*
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
*
*
* @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
*
*
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
*
*
* @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.
*
* 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.
*
* 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.
*
*
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实例,是可以保证数据库创建/升级/降级线程安全的。