摘要:Android四大组件,Activity,Service,Broadcast,ContentProvider,这是Android入门的基础课程,也是Android面试中常考的题目,很基础,借此机会和大家分享。
ContentProvider
ContentProvider继承Object,实现了ComponentCallbacks2
概述
ContentProvider是Android组件之一,为应用程序提供数据,对数据进行封装,并通过ContentResolver接口为应用程序提供数据。只有当需要在多个应用程序间数据共享时,才使用该组件。例如联系人信息,需要在多个应用程序中共享,通过ContentProvider进行数据共享,其他情况下可以使用SQLiteDatabase进行数据存储。
如果要复制并从应用程序到其它应用程序粘贴复杂的数据或文件,也需要自定义ContentProvider。
创建自定义Provider时,需要在Android manifest文件中进行声明,同其他四大组件
主要需实现的方法
- onCreate() 初始化provider时调用
- query(Uri, String[], String, String[], String) 返回调用者需要查询的数据
- insert(Uri, ContentValues) 将数据插入provider
- update(Uri, ContentValues, String, String[]) 更新provider中的数据
- delete(Uri, String, String[]) 删除provider中的数据
- getType(Uri) 返回provider中数据的MIME type
注:数据访问方法(insert(),update())可能会被多个线程同时访问,必须保证线程安全,onCreate()方法由系统主线程调用,必须避免执行冗长操作
基础
访问ContentProvider
应用程序通过ContentResolver对象访问ContentProvider中的数据。ContentResolver对象中使用和provider中同名的方法,包括基本的CRUD(创建,检索,更新和删除,create, retrieve, update, and delete)访问持久化数据。
注:访问Provider需要在应用程序manifest文件中声明特定的权限。
例如,获取用户字典中的数据,通过User Dictionary Provider
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // 相当于from table
mProjection, // 要返回的数据列名
mSelectionClause // 选择的条件
mSelectionArgs, // 条件参数
mSortOrder); // 对返回数据排序
Content URIs
content uri 用来在provider中标记数据,content uri包含provider的符号名,一个指向数据表的路径名,uri作为provider方法的参数
例如User Dictionary中的words表的URI
content://user_dictionary/words
content:// 代表这是一个content uri
user_dictionary 表示权限
words 表示这个表的路径
通过_ID查找指定记录:
Uri singleUri = ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
注:URI和Uri.Builder类包含方便的方法将字符串构建为格式化的URI对象。ContentUris包含方便的方法将ID值构建进URI对象中。
检索数据
从provider中检索数据包括以下步骤:
1. 请求获取provider的读取访问权限
2. 定义发送给provider查询数据的代码
获取访问权限
通过< uses-permission >在manifest文件中指定所需要的权限,权限名称由provider定义。当应用程序被安装后,权限将自动被赋予相应的请求。
构建查询
// 定义要返回的列的映射
String[] mProjection =
{
UserDictionary.Words._ID, // _ID
UserDictionary.Words.WORD, // WORD
UserDictionary.Words.LOCALE // LOCALE
};
// 定义选择的条件
String mSelectionClause = null;
// 选择条件所需的参数
String[] mSelectionArgs = {""};
选择条件由逻辑表达式,布尔表达式,列名和值等组合。
/*
* 定义一个只包含一个元素的字符串数组参数
*/
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, // content uri
mProjection, // 返回结果集所包含的列的映射
mSelectionClause // 选择条件
mSelectionArgs, // 条件参数
mSortOrder); // 结果集排序
// 当发生异常,返回空或抛出异常
if (null == mCursor) {
/*
* 插入代码来处理错误
* 打印错误日志
*/
// 如果返回结果集为空,也可能是未检索到相关数据
} else if (mCursor.getCount() < 1) {
/*
* 插入代码通知用户查询不成功,但不是错误,提示用户插入数据
*/
} else {
// 插入处理结果的代码
}
对应的SQL语句为:
SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
防止恶意输入
如果用下面的方式构造选择条件,将会导致恶意的SQL脚本注入
// 错误的构造条件
String mSelectionClause = "var = " + mUserInput;
// var = nothing; DROP TABLE *;
正确的使用方式是:直接将用户的输入绑定到查询,作为一个参数,而不是作为一条SQL语句执行,这样就不会产生恶意的SQL脚本注入。
// 通过占位符匹配用户输入参数
String mSelectionClause = "var = ?";
用户参数查询参数定义
String[] selectionArgs = {""};
根据占位符匹配用户参数
selectionArgs[0] = mUserInput;
显示查询结果
当返回的结果不为空,没有抛出异常,并且返回的数据是一个list,则将数据游标通过SimpleCursorAdapter将数据填充到listview中显示。
// 定义list列表的列,用于显示数据
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // Contract class constant containing the word column name
UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
};
// 定义列表显示数据控件的ID
int[] mWordListItems = { R.id.dictWord, R.id.locale};
// 创建SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // 应用程序上下文
R.layout.wordlistrow, // 自定义的列表项布局文件
mCursor, // 返回的结果集游标
mWordListColumns, // 结果集中数据列名称数组
mWordListItems, // 自定义列表项中的ID数组,与数据列名称数组对应
0); // Flags(标志位,通常情况下不使用)
// 为ListView设置适配器
mWordList.setAdapter(mCursorAdapter);
注:为了支持ListView中使用Cursor,返回的结果集中必须包含_ID列,这也就需要在设计数据表示,将索引列设计为 _ID。
从结果集中获取数据
// 获取word列的索引值
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
/*
* 使用Cursor前判断是否为空或是否包含异常
*/
if (mCursor != null) {
/*
* 获取数据前,先将游标指向第一条数据,判断是否包含下一条数据
*/
while (mCursor.moveToNext()) {
// 获取指定列的数据
newWord = mCursor.getString(index);
// 处理检索到的数据
...
// 结束循环
}
} else {
// 插入代码通知用户Cursor为空或处理异常
}
Content Provider Permissions
插入,更新,删除数据
插入数据
// 创建一个Uri对象,用于插入数据
Uri mNewUri;
...
// 创建ContentValues对象用于保存要插入的数据
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, // content uri
mNewValues // 要插入的数据
);
插入后返回插入数据的URI,通过ContentUris.parseId()可以解析插入记录所在的ID值。
content://user_dictionary/words/<id_value>
更新数据
ContentValues mUpdateValues = new ContentValues();
// 选择要更新的列
String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?";
String[] mSelectionArgs = {"en_%"};
// 返回被更新的记录统计
int mRowsUpdated = 0;
...
/*
* Sets the updated value and updates the selected words.
*/
mUpdateValues.putNull(UserDictionary.Words.LOCALE);
mRowsUpdated = getContentResolver().update(
UserDictionary.Words.CONTENT_URI, // 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
);