ContentProvider:内容提供程序,提供安全的数据访问机制,支持跨进程通信(IPC),主要用于向其他应用提供数据。通过Context中ContentResolver对象作为客户端与contentprovider进行通讯,一般是对其数据的CRUD操作。Android本身提供了音频、视频、图像和个人联系信息的contentprovider。
contentprovider以一个或多个表的形式将数据呈现给外部应用。contentprovider不需要具有主键,也无需将_ID用作其主键的列名(如果存在主键),但是如果要将来自contentprovider的数据与ListView绑定,则其中一个列名必须是_ID.
访问:
应用从具有 ContentResolver 客户端对象的内容提供程序访问数据。 此对象具有调用提供程序对象(ContentProvider 的某个具体子类的实例)中同名方法的方法。 ContentResolver 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。
客户端应用进程中的 ContentResolver 对象和拥有提供程序的应用中的 ContentProvider 对象可自动处理跨进程通信。 ContentProvider 还可充当其数据存储区和表格形式的数据外部显示之间的抽象层。
// 查找并返回结果集,用户字典为例
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // URI映射至提供程序中的表 from table_name
mProjection, // 要查找的列 col,col...
mSelectionClause // 选择行的条件 where col = value
mSelectionArgs, // 参数替换选择条件中?占位符
mSortOrder); // 显示顺序order by col,col...
其中内容URI包括整个提供程序的符号名称(授权)和一个指向表的名称(路径),如上述代码中URI是
content://user_dictionary/words
其中user_dictionary字符串时提供程序的授权,words字符串是表的路径。content://始终显示,并将此标识为内容URI。还有允许将ID值追加到URI末尾来访问表中某行的。
如从上述表中检索_ID为4的行,URI为:
Uri singleUri = ContentUris.withAppendedId(UserDictionary,Words.CONTENT_URI, 4);
在更新或删除某一行时常会用到id。
Uri和Uri.Builder类提供构建字符串格式规范的URI的方法。ContentUris包含可以将ID值追加到URI后的方法。
注意:官方建议要在子线程中进行异步执行查询(ContentResolver.query())。通常使用CursorLoader类执行此操作。
从contentprovider检索数据,依照以下步骤:
1.请求provider的读取访问权限;
2.定义将查询发送至提供程序的代码。
这个权限无法在运行时获取,应使用
一般读取权限的准确名称可以通过其文档获取。例如上述的用户字典provider在其清单中定义了权限android.permission.READ_USER_DICTIONARY用于读取用户字典。当然还有增删改的权限:android.permission.WRITE_USER_DICTIONARY
// 要查找的列投影
String[] mProjection =
{
UserDictionary.Words._ID, // Contract class constant for the _ID column name
UserDictionary.Words.WORD, // Contract class constant for the word column name
UserDictionary.Words.LOCALE // Contract class constant for the locale column name
};
// where条件
String mSelectionClause = null;
// 占位符对应参数
String[] mSelectionArgs = {""};
// 从UI获取文字
mSearchString = mSearchWord.getText().toString();
// 此处要进行输入检查,防止非法输入
// 如果是空串,则默认查找所有
if (TextUtils.isEmpty(mSearchString)) {
// 设置选择子句为空,返回所有结果
mSelectionClause = null;
mSelectionArgs[0] = "";
} else {
// 构建选择子句
mSelectionClause = UserDictionary.Words.WORD + " = ?";
// 将用户输入的字符串放入对应
mSelectionArgs[0] = mSearchString;
}
// 查表并返回结果集
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // 单词表对应的URI
mProjection, // 查找的列
mSelectionClause // 选择子句
mSelectionArgs, // 选择子句占位符参数
mSortOrder); // 排序方式
// 有些provider执行时如果出错会返回空值,有些会抛出异常
if (null == mCursor) {
// 处理异常
} else if (mCursor.getCount() < 1) {
// 查询成功,但是没有数据
} else {
// 查询成功,有数据,可以处理
}
上述的查询操作其实与数据库中查询SQL类似:
SELECT _ID,word,locale FROM words WHERE wore = <用户输入参数> ORDER BY word ASC;
当然,如果provider对应的数据就是数据库中的数据,可能就会遇到SQL注入这样的攻击。
例如 String mSelectionClause = "var = " + mUserInput,如mUserInput = "nothig; DROP TABLE *;"会导致数据库删除所有表。
解决方法当然是使用占位符?(占位符会将参数进行转义操作),防止恶意注入SQL。
1)如果没有与选择条件匹配的行,则会返回Cursor.getCount()为0的Cursor对象;
2)如果出现内部错误,则结果将取决于具体的provider,可能会返回null,也可能抛出异常;
3)provider可能会根据查询的发出者性质来限制对列的访问。例如:联系人提供程序限定只有同步适配器才能访问某些列,这些列不会被返回值Activity或者服务;
4)一般,Cursor通过SimpleCursorAdapter与ListView关联,但要注意,这种关联方式,Cursor中必须包含_ID列。代码如下:
// 定义要从Cursor中获取的列名列表
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // Contract class constant containing the word column name
UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
};
// 定义与上述列名对应的数据要显示到的UI的id
int[] mWordListItems = { R.id.dictWord, R.id.locale};
// 创建Adapter
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // ApplicationContext
R.layout.wordlistrow, // 布局文件
mCursor, // 查询结果集
mWordListColumns, // cursor中需要的列名
mWordListItems, // 布局文件中的对应控件id
0); // Flags (一般用不着)
mWordList.setAdapter(mCursorAdapter);
5)查询数据,示例:
// 确定名为WORD的列的列索引
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
if (mCursor != null) {
while (mCursor.moveToNext()) {
// 获取值
newWord = mCursor.getString(index);
}
} else {
// ...
}
// 用于接收插入结果的URI
Uri mNewUri;
ContentValues mNewValues = new ContentValues();
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
mNewUri = getContentResolver().insert(
UserDictionary.Word.CONTENT_URI,
mNewValues
);
其中newUri格式如下:content://user_dictionary/word/ContentValues mUpdateValues = new ContentValues();
// 更新条件
String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?";
String[] mSelectionArgs = {"en_%"};
// 被更新的行数
int mRowsUpdated = 0;
/*
* 设置要更新的字段与值,此处是置空
*/
mUpdateValues.putNull(UserDictionary.Words.LOCALE);
mRowsUpdated = getContentResolver().update(
UserDictionary.Words.CONTENT_URI,
mUpdateValues
mSelectionClause
mSelectionArgs
);
注意要进行输入校验。
// 删除条件
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};
// 被删除行数
int mRowsDeleted = 0;
//执行删除
mRowsDeleted = getContentResolver().delete(
UserDictionary.Words.CONTENT_URI,
mSelectionClause
mSelectionArgs
);
注意要进行输入校验。
provider会维护其定义的每个内容URI的MIME数据类型,使用包含复杂 数据结构或者文件的provider时,通常要MIME类型。例如联系人provider中ContactsContract.Data会使用MIME类型标记每行中存储的联系人数据类型。使用ContentRsolver.getType()获取与内容URI对应的MIME类型。
批量访问:可以通过ContentProviderOperation类中的方法创建一批访问调用,通过ContentResolver.applyBatch()应用,返回结果数组。
适用于插入大量航,或者通过同一方法调用在多表中插入,或者通常用于跨进程将一组操作作为事务处理执行。
异步查询:在单独线程中执行查询,一般使用CursorLoader。
通过Intent访问数据:Intent提供对provider的间接访问。即使不具备访问权限,也可以访问provider中的数据。
通过临时权限获取访问权限:
将Intent发送至具有权限的应用,然后接收包含URI权限的结果Intent。
具有永久权限的应用将通过在结果Intent中设置标志来授予临时权限:
读:FLAG_GRANT_READ_URI_PERMISSION,写:FLAG_GRANT_WRITE_URI_PERMISSION
但是这些标志不会为其授权包含在内容URI中的提供程序提供常规的读取或写入权限。访问权限仅适用于URI。(还不是太理解,先记下吧)
一般是文件数据和数据库,甚至可以使网络数据。
表数据应该具有主键,如果想让结果链接到ListView,则必须含有列名_ID;
文件数据需要告诉用户,要使用ContentResolver来访问数据;
二进制大型对象(BLOB)类型存储大小或结构会发生变化的数据,如协议缓冲区或JSON结构。一般要通过MIME类型来指示数据类型。
ContentProvider的每个数据访问方法都将内容URI作为参数,可以利用他确定要访问的表、行或文件。
授权:provider通常具有单一授权。为避免与其他provider冲突,应使用域名作为基础。例如:如果包名为:com.example.
路径:通常我们通过追加指向单个表的路径来根据授权创建内容URI。例如:com.example.
内容URI ID:provider通过接收末尾含有ID的内容URI来确定行。当用户将数据Cursor使用CursorAdapter绑定到ListView时(此时Cursor必须有一列为_ID),用户选中其中一行,可以根据其ID来执行查询或者修改操作。例如:com.example.
注意其中有通用匹配符号*和#,*表示任意长度的有效字符串,#则表示任意长度的数字字符串。
必须方法:
query()查找,返回Cursor。如果是数据库,可以直接返回数据库的query返回的Cursor。如果没有匹配行,则返回一个实例,但是getCount()为0,出现异常返回null。如果不是数据库,则要创建实例返回。
insert()插入,返回插入行的内容URI,末尾含有ID;使用withAppendedId()向表内容URI追加新行的_ID值。
update()更新,返回更新行数;
delete()删除,返回删除的行数;
getType()返回内容URI对应的MIME类型,必须实现;
onCreate()初始化内容提供程序,系统会在创建provider之后立即调用此方法,只要ContentResolver对象尝试访问该provider时,才会创建它。
*getSystemTypes(),系统在provider提供文件时要实现的方法。
1.除onCreate()外,其他方法必须是线程安全的。
2.避免在onCreate()中执行长时间操作,将初始化任务推迟到实际需要时进行。
3.不需要的方法可以忽略。