ContentProvider是Android的四大组件之一,可见其重要性。我们使用到ContentProvider时,也要同Activity等其它组件一样,在AndroidManifest.xml中注册对应的组件才能使用。为什么会用ContentProvider来作为数据存储方式之一呢?
ContentProvider在android中的作用是对外共享数据(应用之间数据共享),可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据进行添删改查。与其它数据共享方式相比:如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
Android中的ContentProvider机制可支持在多个应用中存储和读取数据。这也是跨应用共享数据的方式之一,还有文件,sharePreference,SQLite数据库等方式存储共享数据库,但是ContentProvider更好的提供了数据共享接口的统一性。在android系统中,没有一个公共的内存区域,供多个应用共享存储数据。Android提供了一些主要数据类型的Content provider,比如音频、视频、图片和私人通讯录等。可在android.provider包下面找到一些android提供的Content provider。可以获得这些Content provider,查询它们包含的数据,当然前提是已获得适当的读取权限。Android系统将这种机制应用到方方面面。比如:联系人提供器专为不同应用程序提供联系人数据;设置提供器专为不同应用程序提供系统配置信息,包括内置的设置应用程序等。
今天用单元测试的方式简单介绍ContentProvider的使用:
-------------------------------使用ContentProvider共享数据,一般用到Sqlite数据库-----------------------------------------------------------
public class DBHelpTool {
private static DatabaseHelper dbHelper;
private static SQLiteDatabase db;
/**数据库名*/
private static final String DB_NAME = "kawa.db";
/**数据库版本*/
private static final int DB_VERSION = 1;
private final Context mContent;
public DBHelpTool(Context mContent) {
this.mContent = mContent;
}
/**定义一个抽象类,用于数据库的创建和版本的管理 */
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
/**创建一个老师teacher表,三个字段分别是_id(自增长主键),name和age*/
String sql = "create table if not exists teacher (_id integer primary key autoincrement, name text,age integer)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 数据库升级操作
}
}
/**打开数据库操作*/
public DBHelpTool open() throws SQLException {
dbHelper = new DatabaseHelper(mContent);
db = dbHelper.getWritableDatabase();
return this;
}
/**关闭数据库操作*/
public void closeclose() {
db.close();
dbHelper.close();
}
/**
* 插入数据
* 参数:tableName 表名
* values 要插入的列对应值
*/
public long insert(String tableName, ContentValues values) {
return db.insert(tableName, null, values);
}
/**
* 删除数据
* 参数:tableName 表名
* deleteCondition 删除的条件
* deleteArgs 如果deleteCondition中有“=?”号,将用此数组中的值替换
*/
public int delete(String tableName, String deleteCondition, String[] deleteArgs) {
return db.delete(tableName, deleteCondition, deleteArgs);
}
/**
* 修改数据
* 参数:tableName 表名
* initialValues 要更新的列
* selection 更新的条件
* selectArgs 如果selection中有“=?”号,将用此数组中的值替换
*/
public int update(String tableName, ContentValues initialValues, String selection, String[] selectArgs) {
int returnValue = db.update(tableName, initialValues, selection, selectArgs);
return returnValue;
}
/**
* 查询相应条件的所有数据
* 参数:tableName 表名
* columns 返回的列
* selection 查询条件
* selectArgs 如果selection中有“?”号,将用此数组中的值替换
*/
public Cursor findList(String tableName, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {
return db.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy);
}
private static DatabaseHelper dbHelper;
private static SQLiteDatabase db;
/**数据库名*/
private static final String DB_NAME = "kawa.db";
/**数据库版本*/
private static final int DB_VERSION = 1;
private final Context mContent;
public DBHelpTool(Context mContent) {
this.mContent = mContent;
}
/**定义一个抽象类,用于数据库的创建和版本的管理 */
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
/**创建一个老师teacher表,三个字段分别是_id(自增长主键),name和age*/
String sql = "create table if not exists teacher (_id integer primary key autoincrement, name text,age integer)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 数据库升级操作
}
}
/**打开数据库操作*/
public DBHelpTool open() throws SQLException {
dbHelper = new DatabaseHelper(mContent);
db = dbHelper.getWritableDatabase();
return this;
}
/**关闭数据库操作*/
public void closeclose() {
db.close();
dbHelper.close();
}
/**
* 插入数据
* 参数:tableName 表名
* values 要插入的列对应值
*/
public long insert(String tableName, ContentValues values) {
return db.insert(tableName, null, values);
}
/**
* 删除数据
* 参数:tableName 表名
* deleteCondition 删除的条件
* deleteArgs 如果deleteCondition中有“=?”号,将用此数组中的值替换
*/
public int delete(String tableName, String deleteCondition, String[] deleteArgs) {
return db.delete(tableName, deleteCondition, deleteArgs);
}
/**
* 修改数据
* 参数:tableName 表名
* initialValues 要更新的列
* selection 更新的条件
* selectArgs 如果selection中有“=?”号,将用此数组中的值替换
*/
public int update(String tableName, ContentValues initialValues, String selection, String[] selectArgs) {
int returnValue = db.update(tableName, initialValues, selection, selectArgs);
return returnValue;
}
/**
* 查询相应条件的所有数据
* 参数:tableName 表名
* columns 返回的列
* selection 查询条件
* selectArgs 如果selection中有“?”号,将用此数组中的值替换
*/
public Cursor findList(String tableName, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {
return db.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy);
}
----------------------------------对应的数据表teacher实体类------------------------------------------------
public class Teacher implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Teacher other = (Teacher) obj;
if (age != other.age)
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
private static final long serialVersionUID = 1L;
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Teacher other = (Teacher) obj;
if (age != other.age)
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
------------------------简单的常量类-------------------------------------------------
public class UrlConstant {
public static String PROVIDER_URL = "com.ldm.provider.myprovider";
public static String TABLE_NAME = "teacher";
}
public static String PROVIDER_URL = "com.ldm.provider.myprovider";
public static String TABLE_NAME = "teacher";
}
--------------------------今天的主角,我们的ContentProvider,继承ContentProvider就对应有增删改查方法--------------------------------------
public class TestProvider extends ContentProvider {
private DBHelpTool mDBTool;
/**匹配工具类UriMatcher:
* Android中是用Uri代表要操作的数据,所以我们需要解析Uri并从Uri中获取数据。
* Android系统提供了两个操作Uri的工具类:分别为UriMatcher和ContentUris
* UriMatcher类用于匹配Uri,它的用法如下:
* 首先第一步把你需要匹配Uri路径全部给注册,如下:
* 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
* 给注册的Uri路径中增加定义好的Uri路径:
* MATCHER.addURI(UrlConstant.CONTENTPROVIDER, "stu", UrlConstant.TEACHER_LIST);//返回匹配码为1
* MATCHER.addURI(UrlConstant.CONTENTPROVIDER, "stu/#", UrlConstant.TEACHER_DETAIL);//返回匹配码为2
* 注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配
**/
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int TEACHERS = 1;
private static final int TEACHER = 2;
static {
MATCHER.addURI(UrlConstant.PROVIDER_URL, "person", TEACHERS);
MATCHER.addURI(UrlConstant.PROVIDER_URL, "person/#", TEACHER);
}
@Override
public boolean onCreate() {
mDBTool = new DBHelpTool(this.getContext());
mDBTool.open();
return false;
}
/**查询数据*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (MATCHER.match(uri)) {
case TEACHERS:
return mDBTool.findList("person", projection, selection, selectionArgs, null, null, sortOrder);
case TEACHER:
/** 查询某个ID的数据: 通过ContentUris这个工具类解释出ID
* ContentUris 类用于获取Uri路径后面的ID部分
* 1)为路径加上ID: withAppendedId(uri, id)
* 2)从路径中获取ID: parseId(uri)
* */
long id = ContentUris.parseId(uri);
String where = " _id=" + id;
if (!"".equals(selection) && selection != null) {
where = selection + " and " + where;
}
return mDBTool.findList(UrlConstant.TABLE_NAME, projection, where, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("unknow uri" + uri.toString());
}
}
/**返回当前操作的数据的mimeType*/
@Override
public String getType(Uri uri) {
switch (MATCHER.match(uri)) {
case TEACHERS:
return "vnd.android.cursor.dir/teacher";
case TEACHER:
return "vnd.android.cursor.item/teacher";
default:
throw new IllegalArgumentException("Error Uri:" + uri.toString());
}
}
/**添加数据*/
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri insertUri = null;
switch (MATCHER.match(uri)) {
case TEACHERS:
long rowid = mDBTool.insert(UrlConstant.TABLE_NAME, values);
insertUri = ContentUris.withAppendedId(uri, rowid);
break;
default:
throw new IllegalArgumentException("Error Uri:" + uri.toString());
}
return insertUri;
}
/**删除数据*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (MATCHER.match(uri)) {
case TEACHERS:
count = mDBTool.delete(UrlConstant.TABLE_NAME, selection, selectionArgs);
return count;
case TEACHER:
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = mDBTool.delete("person", where, selectionArgs);
return count;
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
}
/**修改数据*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch (MATCHER.match(uri)) {
case TEACHERS:
count = mDBTool.update(UrlConstant.TABLE_NAME, values, selection, selectionArgs);
break;
case TEACHER:
// 通过ContentUri工具类得到ID
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = mDBTool.update("person", values, where, selectionArgs);
break;
default:
throw new IllegalArgumentException("Error Uri:" + uri.toString());
}
return count;
}
}
private DBHelpTool mDBTool;
/**匹配工具类UriMatcher:
* Android中是用Uri代表要操作的数据,所以我们需要解析Uri并从Uri中获取数据。
* Android系统提供了两个操作Uri的工具类:分别为UriMatcher和ContentUris
* UriMatcher类用于匹配Uri,它的用法如下:
* 首先第一步把你需要匹配Uri路径全部给注册,如下:
* 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
* 给注册的Uri路径中增加定义好的Uri路径:
* MATCHER.addURI(UrlConstant.CONTENTPROVIDER, "stu", UrlConstant.TEACHER_LIST);//返回匹配码为1
* MATCHER.addURI(UrlConstant.CONTENTPROVIDER, "stu/#", UrlConstant.TEACHER_DETAIL);//返回匹配码为2
* 注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配
**/
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int TEACHERS = 1;
private static final int TEACHER = 2;
static {
MATCHER.addURI(UrlConstant.PROVIDER_URL, "person", TEACHERS);
MATCHER.addURI(UrlConstant.PROVIDER_URL, "person/#", TEACHER);
}
@Override
public boolean onCreate() {
mDBTool = new DBHelpTool(this.getContext());
mDBTool.open();
return false;
}
/**查询数据*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (MATCHER.match(uri)) {
case TEACHERS:
return mDBTool.findList("person", projection, selection, selectionArgs, null, null, sortOrder);
case TEACHER:
/** 查询某个ID的数据: 通过ContentUris这个工具类解释出ID
* ContentUris 类用于获取Uri路径后面的ID部分
* 1)为路径加上ID: withAppendedId(uri, id)
* 2)从路径中获取ID: parseId(uri)
* */
long id = ContentUris.parseId(uri);
String where = " _id=" + id;
if (!"".equals(selection) && selection != null) {
where = selection + " and " + where;
}
return mDBTool.findList(UrlConstant.TABLE_NAME, projection, where, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("unknow uri" + uri.toString());
}
}
/**返回当前操作的数据的mimeType*/
@Override
public String getType(Uri uri) {
switch (MATCHER.match(uri)) {
case TEACHERS:
return "vnd.android.cursor.dir/teacher";
case TEACHER:
return "vnd.android.cursor.item/teacher";
default:
throw new IllegalArgumentException("Error Uri:" + uri.toString());
}
}
/**添加数据*/
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri insertUri = null;
switch (MATCHER.match(uri)) {
case TEACHERS:
long rowid = mDBTool.insert(UrlConstant.TABLE_NAME, values);
insertUri = ContentUris.withAppendedId(uri, rowid);
break;
default:
throw new IllegalArgumentException("Error Uri:" + uri.toString());
}
return insertUri;
}
/**删除数据*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (MATCHER.match(uri)) {
case TEACHERS:
count = mDBTool.delete(UrlConstant.TABLE_NAME, selection, selectionArgs);
return count;
case TEACHER:
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = mDBTool.delete("person", where, selectionArgs);
return count;
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
}
/**修改数据*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch (MATCHER.match(uri)) {
case TEACHERS:
count = mDBTool.update(UrlConstant.TABLE_NAME, values, selection, selectionArgs);
break;
case TEACHER:
// 通过ContentUri工具类得到ID
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = mDBTool.update("person", values, where, selectionArgs);
break;
default:
throw new IllegalArgumentException("Error Uri:" + uri.toString());
}
return count;
}
}
------------------------单元测试,相当于另外一个应用,对我们提供的ContentProvider进行数据操作-------------------------------
/**
* 先介绍下Uri:
* Uri代表了要操作的数据,Uri主要包含了两部分信息:
* 1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作。
* 一个Uri由以下几部分组成:
* 1.scheme:ContentProvider的scheme已经由Android所规定(不能更改):content://。
* 2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
* 3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
* ? 要操作teacher表中id为10的记录,可以构建这样的路径:/teacher/10
* ? 要操作teacher表中id为10的记录的name字段, teacher/10/name
* ? 要操作teacher表中的所有记录,可以构建这样的路径:/teacher
* 要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:
* 要操作xml文件中teacher节点下的name节点,可以构建这样的路径:/teacher/name
* 如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
* Uri uri = Uri.parse("content://com.ldm.provider.testprovider/teacher")
*/
public class ProjectTestCase extends AndroidTestCase {
/**添加数据操作*/
public void testInsert() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person");
ContentValues values = new ContentValues();
values.put("name", "liming");
values.put("age", 22);
contentResolver.insert(url, values);
}
/**删除数据操作*/
public void testDelete() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person/1");
contentResolver.delete(url, null, null);
}
/**更新数据操作*/
public void testUpdate() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person/1");
ContentValues values = new ContentValues();
values.put("name", "wangwu");
values.put("age", 20);
contentResolver.update(url, values, null, null);
}
/**查询数据操作*/
public void testQuery() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person");
Cursor cursor = contentResolver.query(url, new String[] { "_id", "name", "age" }, null, null, "_id");
while (cursor.moveToNext()) {
System.out.println("_id:" + cursor.getInt(cursor.getColumnIndex("_id")));
System.out.println("name:" + cursor.getString(cursor.getColumnIndex("name")));
System.out.println("age:" + cursor.getInt(cursor.getColumnIndex("age")));
}
}
}
* 先介绍下Uri:
* Uri代表了要操作的数据,Uri主要包含了两部分信息:
* 1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作。
* 一个Uri由以下几部分组成:
* 1.scheme:ContentProvider的scheme已经由Android所规定(不能更改):content://。
* 2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
* 3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
* ? 要操作teacher表中id为10的记录,可以构建这样的路径:/teacher/10
* ? 要操作teacher表中id为10的记录的name字段, teacher/10/name
* ? 要操作teacher表中的所有记录,可以构建这样的路径:/teacher
* 要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:
* 要操作xml文件中teacher节点下的name节点,可以构建这样的路径:/teacher/name
* 如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
* Uri uri = Uri.parse("content://com.ldm.provider.testprovider/teacher")
*/
public class ProjectTestCase extends AndroidTestCase {
/**添加数据操作*/
public void testInsert() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person");
ContentValues values = new ContentValues();
values.put("name", "liming");
values.put("age", 22);
contentResolver.insert(url, values);
}
/**删除数据操作*/
public void testDelete() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person/1");
contentResolver.delete(url, null, null);
}
/**更新数据操作*/
public void testUpdate() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person/1");
ContentValues values = new ContentValues();
values.put("name", "wangwu");
values.put("age", 20);
contentResolver.update(url, values, null, null);
}
/**查询数据操作*/
public void testQuery() throws Exception {
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri url = Uri.parse("content://" + UrlConstant.PROVIDER_URL + "/person");
Cursor cursor = contentResolver.query(url, new String[] { "_id", "name", "age" }, null, null, "_id");
while (cursor.moveToNext()) {
System.out.println("_id:" + cursor.getInt(cursor.getColumnIndex("_id")));
System.out.println("name:" + cursor.getString(cursor.getColumnIndex("name")));
System.out.println("age:" + cursor.getInt(cursor.getColumnIndex("age")));
}
}
}
---------------------------还是要说说AndroidManifest.xml中对应的组件注册---------------------------------------------------------------
ContentProvier注册:
<provider
android:name="com.ldm.test.provider.TestProvider"
android:authorities="com.ldm.provider.myprovider" >和UrlConstant中的PROVIDER_URL一致
</provider>
android:name="com.ldm.test.provider.TestProvider"
android:authorities="com.ldm.provider.myprovider" >和UrlConstant中的PROVIDER_URL一致
</provider>
注册单元测试:
<uses-library android:name="android.test.runner" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:label="Tests for My App"
android:targetPackage="com.ldm.test" />应用包名
android:name="android.test.InstrumentationTestRunner"
android:label="Tests for My App"
android:targetPackage="com.ldm.test" />应用包名