创建Content Provider
Content Provider为公布数据提供了一个接口,别的APP使用Content Resolver来使用该接口所提供的数据。
作为4大组件之一,创建一个新的Content Provider需要继承一个抽象类ContentProvider:
public class MyContentProvider extends ContentProvider
就像之前描述的数据库的Contract/Helper类,也同样最好是去包含静态数据库常量---尤其是列名等。
你需要重写onCreate方法去初始化底层数据源,同样还有重写query,update,delete,insert和getType这些方法,使Content Resolver可以与数据进行交互。
注册Content Provider
像Activity,Service,Content Provider,必须在Mainfest中注册。使用一个provider标签:包含一个name和authorities属性。
authorities属性 去定义Provider的authority的基础URI。Content Provider的authority代表一个数据库。
每个Content Provider的authority必须是唯一的,所以定义基础URI通常以包名作为路径。如:
com.<CompanyName>.provider.<ApplicationName>
例子:
<provider android:name=”.MyContentProvider”
android:authorities=”com.paad.skeletondatabaseprovider”/>
公布你的Content ProviderURI地址
每个Content Provider应该使用公有静态常量 CONTENT_URI ,使其更容易被访问。
如:
public static final Uri CONTENT_URI =
Uri.parse(“content://com.paad.skeletondatabaseprovider/elements”);
类似与上述这例子表示请求表中所有的记录,而:
content://com.paad.skeletondatabaseprovider/elements/5
表示某行记录。(接数字)
所有这些形式都支持你去访问你的Provider。这么做最简单方式是去使用UriMatcher,一个非常有用得类,用来解析URI和决定它的格式。
// Create the constants used to differentiate between the different URI
// requests.
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Populate the UriMatcher object, where a URI ending in
//‘elements’ will correspond to a request for all items,
// and ‘elements/[rowID]’ represents a single row.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”,
“elements”, ALLROWS);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”,
“elements/#”, SINGLE_ROW);
}
你可能使用类似的技术去暴露某个Content Provider更多的URI,这些URI可以代表不同的数据集,或是不同的表。
创建Content Provider的数据库
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construct the underlying database.
// Defer opening the database until you need to perform
// a query or transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(),
MySQLiteOpenHelper.DATABASE_NAME, null,
MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}
注意:Content Provider的onCreate是在主线程中执行的,数据库一旦打开后,若程序还在运行,没必要马上又把它关掉,这出于效率问题。你可能担心资源问题,其实事实是系统如果真需要额外资源,你的APP会被杀死,然后相关的数据库也会被关。
实现Content Provider查询
为了支持Content Provider相关的查询,你必须实现query和getType方法。Content Resolver使用这些方法去获取低沉的数据而无需关心它的细节和实现。
注意:UriMatcher对象通常用来完善事务和查询的请求,SQLite Query Builder更方便作为执行基于行查询的帮手。
例子:实现Content Provider查询的框架代码
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Open the database.
SQLiteDatabase db;
try {
db = myOpenHelper.getWritableDatabase();
} catch (SQLiteException ex) {
db = myOpenHelper.getReadableDatabase();
}
// Replace these with valid SQL statements if necessary.
String groupBy = null;
String having = null;
// Use an SQLite Query Builder to simplify constructing the
// database query.
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// If this is a row query, limit the result set to the passed in row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + “=” + rowID);
default: break;
}
// Specify the table on which to perform the query. This can
// be a specific table or a join as required.
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// Execute the query.
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, groupBy, having, sortOrder);
// Return the result Cursor.
return cursor;
}
实现查询后,你必须还要指定一个MIME类型去鉴定返回的数据。重写getType方法,返回一个唯一描述你数据的字符串。
此类型返回必须包含2种形式,其一为单个条目,另外一个为所有的条目:
Single item:
vnd.android.cursor.item/vnd.<companyname>.<contenttype>
All items:
vnd.android.cursor.dir/vnd.<companyname>.<contenttype>
例子:
@Override
public String getType(Uri uri) {
// Return a string that identifies the MIME type
// for a Content Provider URI
switch (uriMatcher.match(uri)) {
case ALLROWS:
return “vnd.android.cursor.dir/vnd.paad.elemental”;
case SINGLE_ROW:
return “vnd.android.cursor.item/vnd.paad.elemental”;
default:
throw new IllegalArgumentException(“Unsupported URI: “ +
uri);
}
}
Content Provider事务
像query方法,当然还有delete,insert,update方法,实现后,由Content Resolver调用,从而其它APP就可以使用。
当执行修改数据集的事务,比较好的方式是去调用Content Resolver的notifyChange方法。这个方法会通知所有内容观察者。
怎么用? 直接看一些框架代码:
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID
+ (!TextUtils.isEmpty(selection) ?
“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// To return the number of deleted items you must specify a where
// clause. To delete all rows and return a value pass in “1”.
if (selection == null)
selection = “1”;
// Perform the deletion.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE,
selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
// Return the number of deleted items.
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// To add empty rows to your database by passing in an empty
// Content Values object you must use the null column hack
// parameter to specify the name of the column that can be
// set to null.
String nullColumnHack = null;
// Insert the values into the table
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE,
nullColumnHack, values);
// Construct and return the URI of the newly inserted row.
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
}
else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID
+ (!TextUtils.isEmpty(selection) ?
“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// Perform the update.
int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE,
values, selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
Tip:ContentUris类包含一个withAppendedId方法,此方法很方便得帮助构建一个附加指定行ID的Uri。
Content Provider 存储文件
以前提到过,数据库里保存文件,通常建议保存的是路径->一个合适的Uri.
通常表中文件类型的数据列名取名_data形式。
重写openFile方法
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
// Find the row ID and use it as a filename.
String rowID = uri.getPathSegments().get(1);
// Create a file object in the application’s external
// files directory.
String picsDir = Environment.DIRECTORY_PICTURES;
File file =
new File(getContext().getExternalFilesDir(picsDir), rowID);
// If the file doesn’t exist, create it now.
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
Log.d(TAG, “File creation failed: “ + e.getMessage());
}
}
// Translate the mode parameter to the corresponding Parcel File
// Descriptor open mode.
int fileMode = 0;
if (mode.contains(“w”))
fileMode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (mode.contains(“r”))
fileMode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains(“+”))
fileMode |= ParcelFileDescriptor.MODE_APPEND;
// Return a Parcel File Descriptor that represents the file.
return ParcelFileDescriptor.open(file, fileMode);
}
完整的Content Provider 实现框架代码:
public class MyContentProvider extends ContentProvider {
public static final Uri CONTENT_URI =
Uri.parse(“content://com.paad.skeletondatabaseprovider/elements”);
// Create the constants used to differentiate between
// the different URI requests.
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Populate the UriMatcher object, where a URI ending
// in ‘elements’ will correspond to a request for all
// items, and ‘elements/[rowID]’ represents a single row.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”,
“elements”, ALLROWS);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”,
“elements/#”, SINGLE_ROW);
}
// The index (key) column name for use in where clauses.
public static final String KEY_ID = “_id”;
// The name and column index of each column in your database.
// These should be descriptive.
public static final String KEY_COLUMN_1_NAME = “KEY_COLUMN_1_NAME”;
// TODO: Create public field for each column in your table.
// SQLite Open Helper variable
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construct the underlying database.
// Defer opening the database until you need to perform
// a query or transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(),
MySQLiteOpenHelper.DATABASE_NAME, null,
MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Open the database.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Replace these with valid SQL statements if necessary.
String groupBy = null;
String having = null;
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// If this is a row query, limit the result set to the
// passed in row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + “=” + rowID);
default: break;
}
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, groupBy, having, sortOrder);
return cursor;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID
+ (!TextUtils.isEmpty(selection) ?
“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// To return the number of deleted items, you must specify a where
// clause. To delete all rows and return a value, pass in “1”.
if (selection == null)
selection = “1”;
// Execute the deletion.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE,
selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// To add empty rows to your database by passing in an empty
// Content Values object, you must use the null column hack
// parameter to specify the name of the column that can be
// set to null.
String nullColumnHack = null;
// Insert the values into the table
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE,
nullColumnHack, values);
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
}
else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID
+ (!TextUtils.isEmpty(selection) ?
“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// Perform the update.
int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE,
values, selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
@Override
public String getType(Uri uri) {
// Return a string that identifies the MIME type
// for a Content Provider URI
switch (uriMatcher.match(uri)) {
case ALLROWS:
return “vnd.android.cursor.dir/vnd.paad.elemental”;
case SINGLE_ROW:
return “vnd.android.cursor.item/vnd.paad.elemental”;
default:
throw new IllegalArgumentException(“Unsupported URI: “ + uri);
}
}
private static class MySQLiteOpenHelper extends SQLiteOpenHelper {
// [ ... SQLite Open Helper Implementation ... ]
}
}