除了共享内存(SDCard)的数据外,其他包括SQLite、SharedPreferences都是仅限于被当前所创建的应用访问,而无法使它们的数据在应用程序之间交换数据,所以Android提供了ContentProvider,ContentProvider具有以下特点:
- 应用程序间共享数据的一种方式
- 为存储和获取数据提供统一接口
- android为一些常见的数据提供了ContentProvider:
- Browser:存储如浏览器的信息。
- CallLog:存储通话记录等信息。
- Contacts:存储联系人等信息。
- MediaStore:存储媒体文件的信息。
- Settings:存储设备的设置和首选项信息。
访问ContentProvider
ContentProvider 实例通过处理来自其他应用的请求来管理对结构化数据集的访问。所有形式的访问最终都会调用 ContentResolver,后者接着调用ContentProvider的具体方法来获取访问权限。
一般使用Context.getContentResolver()方法获取ContentResolver对象。
ContentResolver 可提供insert、query、update、delete等方法.
例如,要从用户字典提供程序中获取字词及其区域设置的列表,则需调用 ContentResolver.query()。
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI,
mProjection,
mSelectionClause
mSelectionArgs,
mSortOrder);
query() 参数 | SELECT 关键字/参数 | 备注 |
---|---|---|
Uri | FROM table_name | Uri 映射至名为 table_name 的提供程序中的表。 |
projection | col,col,col,... | projection 是应该为检索到的每个行包含的列的数组。 |
selection | WHERE col = value | selection 会指定选择行的条件。 |
selectionArgs | (没有完全等效项。选择参数会替换选择子句中 ? 的占位符。) | |
sortOrder | ORDER BY col,col,... | sortOrder 指定行在返回的 Cursor 中的显示顺序。 |
内容 URI
在Android中,Uri是一种比较常见的资源访问方式。每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。
:// / /
:
ContentProvider的标准前缀始终是content://。:URI 的标识,用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。一般为该ContentProvider的包.类的名称 :请求的数据类型, 如数据库的表。 :指定请求的特定数据。如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部
content://user_dictionary/words
content://com.example.app.provider/table3/1
//对应由 表table3中1标识的行的内容 URI。
content://com.example.app.provider/table3/1/name
//对应由 表table3中1标识的行的name字段内容 URI。
UriMatcher
UriMatcher会将内容 URI“模式”映射到整型值。 可以在一个 switch 语句中使用这些整型值,为匹配特定模式的一个或多个内容 URI 执行相应操作。
内容 URI 模式使用通配符匹配内容 URI:
*:匹配由任意长度的任何有效字符组成的字符串
#:匹配由任意长度的数字字符组成的字符串
假设一个具有权限 com.example.app.provider的提供程序能识别以下指向表的内容 URI:
content://com.example.app.provider/table1:一个名为 table1 的表
content://com.example.app.provider/table2/dataset1:一个名为 dataset1 的表
content://com.example.app.provider/table2/dataset2:一个名为 dataset2 的表
content://com.example.app.provider/table3:一个名为 table3 的表
可以使用以下内容 URI 模式:
content://com.example.app.provider/*
//匹配提供程序中的任何内容 URI。
content://com.example.app.provider/table2/*:
//匹配表 dataset1 和表 dataset2 的内容 URI,但不匹配 table1 或 table3 的内容 URI。
content://com.example.app.provider/table3/#
//匹配 table3 中单个行的内容 URI,
//如 content://com.example.app.provider/table3/6 对应由 6 标识的行的内容 URI。
方法 addURI() 会将权限和路径映射到一个整型值。 方法 match() 会返回 URI 的整型值。switch 语句会在查询整个表与查询单个记录之间进行选择:
public class ExampleProvider extends ContentProvider {
...
// Creates a UriMatcher object.
private static final UriMatcher sUriMatcher;
sUriMatcher.addURI("com.example.app.provider", "table3", 1);
sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
...
// Implements ContentProvider.query()
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
...
switch (sUriMatcher.match(uri)) {
// If the incoming URI was for all of table3
case 1:
if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;
// If the incoming URI was for a single row
case 2:
/*
Because this URI was for a single row, the _ID value part is
present. Get the last path segment from the URI;
this is the _ID value.Then, append the value to the WHERE
clause for the query
*/
selection = selection + "_ID = " uri.getLastPathSegment();
break;
default:
...
// If the URI is not recognized, you should do some error handling here.
}
// call the code to actually do the query
}
创建ContentProvider
在Android中,如果要创建自己的内容提供者的时候,需要扩展抽象类ContentProvider,并重写其中定义的各种方法。然后在AndroidManifest.xml文件中注册该ContentProvider。
创建ContentProvider的步骤:
- 创建一个ContentProvider的子类。
- 定义内容Uri
- 创建SQLiteOpenHelper的子类,创建一个用于存储内容的数据库。
- 在ContentProvider的子类中实现query()、insert()、update()、delete()、getType()、onCreate()方法。
- 在AndroidManifest.xml文件中注册自定义的ContentProvider。
:一般只需要设置两个属性即可访问,一些额外的属性就是为了设置访问权限而存在的:
android:name:provider的响应类。
android:authorities:Provider的唯一标识,用于Uri匹配,一般为ContentProvider类的全名。
在实现ContentProvider的方法时需要注意一下几点:
- 所有这些方法(onCreate() 除外)都可由多个线程同时调用,因此它们必须是线程安全方法。
- 避免在 onCreate() 中执行长时间操作。将初始化任务推迟到实际需要时进行。
- 尽管必须实现这些方法,但代码只需返回要求的数据类型,无需执行任何其他操作。 例如,可能想防止其他应用向某些表插入数据。 要实现此目的,可以忽略 insert() 调用并返回 0。