Android组件系列----ContentProvider内容提供者
【声明】
欢迎转载,但请保留文章原始出处→_→
生命壹号:http://www.cnblogs.com/smyhvae/
文章来源:http://www.cnblogs.com/smyhvae/p/4108017.html
【正文】
一、ContentProvider简介:
ContentProvider内容提供者(四大组件之一)主要用于在不同的应用程序之间实现数据共享的功能。
ContentProvider可以理解为一个Android应用对外开放的接口,只要是符合它所定义的Uri格式的请求,均可以正常访问执行操作。其他的Android应用可以使用ContentResolver对象通过与ContentProvider同名的方法请求执行,被执行的就是ContentProvider中的同名方法。所以ContentProvider有很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的,来看 下面这一张图:
Android附带了许多有用的ContentProvider,但是本文暂时不涉及到这么多(本文将学习如何创建自己的ContentProvider)。Android中自带的ContentProvider包括:
- Browser:存储如浏览器的信息。
- CallLog:存储通话记录等信息。
- Contacts Provider:存储联系人(通讯录)等信息。
- MediaStore:存储媒体文件的信息。
- Settings:存储设备的设置和首选项信息。
此外,还有日历、
ContentProvider的方法:
如果要创建自己的内容提供者,需要新建一个类继承抽象类ContentProvider,并重写其中的抽象方法。抽象方法如下:
boolean onCreate()
初始化提供者 Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 查询数据,返回一个数据Cursor对象。其中参数selection和selectionArgs是外部程序提供的查询条件 Uri insert(Uri uri, ContentValues values)
插入一条数据。参数values是需要插入的值 int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
根据条件更新数据 int delete(Uri uri, String selection, String[] selectionArgs)
根据条件删除数据 String getType(Uri uri)
返回MIME类型对应内容的URI
除了onCreate()和getType()方法外,其他的均为CRUD操作,这些方法中,Uri参数为与ContentProvider匹配的请求Uri,剩下的参数可以参见SQLite的CRUD操作,基本一致。
备注:还有两个非常有意思的方法,必须要提一下,call()和bulkInsert()方法,使用call,理论上可以在ContentResolver中执行ContentProvider暴露出来的任何方法,而bulkInsert()方法用于插入多条数据。
Uri:
在Android中,Uri是一种比较常见的资源访问方式。而对于ContentProvider而言,Uri也是有固定格式的:<srandard_prefix>://<authority>/<data_path>/<id>
- <srandard_prefix>:ContentProvider的srandard_prefix始终是content://。
- <authority>:ContentProvider的名称。
- <data_path>:请求的数据类型。
- <id>:指定请求的特定数据。
在ContentProvider的CRUD操作,均会传递一个Uri对象,通过这个对象来匹配对应的请求。那么如何确定一个Uri执行哪项操作呢?需要用到一个UriMatcher对象,这个对象用来帮助内容提供者匹配Uri。它所提供的方法非常简单,仅有两个:
- void addURI(String authority,String path,int code):添加一个Uri匹配项,authority为AndroidManifest.xml中注册的ContentProvider中的authority属性;path为一个路径,可以设置通配符,#表示任意数字,*表示任意字符;code为自定义的一个Uri代码。
- int match(Uri uri):匹配传递的Uri,返回addURI()传递的code参数。
二、代码举例:
最终所有工程文件的目录结构如下:
PersonDao是增删改查数据库的工具类,并在PersonContentProvider中得到调用。DBHelper用于初始化SQLite数据库。
PersonContentProvider用于向外提供增删改查的接口。并最终在ContentResolverTest的MyTest.java中进行单元测试,实现CRUD。
本文的核心类是:PersonContentProvider和MyTest
下面来看一下具体的实现步骤。
新建工程文件ContetProviderTest01。
(1)新建类PersonDao:用于进行对SQLite的CRUD操作。代码如下:
PersonDao.java:
1 package com.example.contentprovidertest01.dao; 2 3 import android.content.ContentValues; 4 import android.content.Context; 5 import android.database.Cursor; 6 import android.database.sqlite.SQLiteDatabase; 7 8 import com.example.contentprovidertest01.db.DBHelper; 9 10 public class PersonDao { 11 private DBHelper helper = null; 12 13 public PersonDao(Context context) { 14 helper = new DBHelper(context); 15 } 16 17 //方法:插入操作,返回的long类型为:插入当前行的行号 18 public long insertPerson(ContentValues values) { 19 long id = -1; 20 SQLiteDatabase database = null; 21 try { 22 database = helper.getWritableDatabase(); 23 id = database.insert("person", null, values); 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } finally { 27 if (database != null) { 28 database.close(); 29 } 30 } 31 return id; 32 } 33 34 public int deletePerson(String whereClause, String[] whereArgs) { 35 int count = -1; 36 SQLiteDatabase database = null; 37 try { 38 database = helper.getWritableDatabase(); 39 count = database.delete("person", whereClause, whereArgs); 40 } catch (Exception e) { 41 e.printStackTrace(); 42 } finally { 43 if (database != null) { 44 database.close(); 45 } 46 } 47 return count; 48 } 49 50 public int updatePerson(ContentValues values, String whereClause, 51 String[] whereArgs) { 52 SQLiteDatabase database = null; 53 int count = -1; 54 try { 55 database = helper.getWritableDatabase(); 56 count = database.update("person", values, whereClause, whereArgs); 57 } catch (Exception e) { 58 e.printStackTrace(); 59 } finally { 60 if (null != database) { 61 database.close(); 62 } 63 } 64 return count; 65 } 66 67 public Cursor queryPersons(String selection, String[] selectionArgs) { 68 SQLiteDatabase database = null; 69 Cursor cursor = null; 70 try { 71 database = helper.getReadableDatabase(); 72 cursor = database.query(true, "person", null, selection, 73 selectionArgs, null, null, null, null); 74 } catch (Exception e) { 75 e.printStackTrace(); 76 } finally { 77 if (null != database) { 78 // database.close(); 79 } 80 } 81 return cursor; 82 } 83 84 }
(2)新建类DBHelper:用于初始化SQLiate数据库
DBHelper.java:
1 package com.example.contentprovidertest01.db; 2 3 import android.content.Context; 4 import android.database.sqlite.SQLiteDatabase; 5 import android.database.sqlite.SQLiteOpenHelper; 6 7 public class DBHelper extends SQLiteOpenHelper { 8 9 private static String name = "mydb.db"; // 数据库的名字 10 private static int version = 1; // 数据库的版本 11 12 public DBHelper(Context context) { 13 super(context, name, null, version); 14 } 15 16 @Override 17 public void onCreate(SQLiteDatabase db) { 18 // 只能支持基本数据类型:varchar int long float boolean text blob clob 19 // 建表语句执行 20 String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))"; 21 db.execSQL(sql); 22 } 23 24 @Override 25 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 26 // TODO Auto-generated method stub 27 String sql = "alter table person add sex varchar(8)"; 28 db.execSQL(sql); 29 } 30 31 }
(3)【核心】新建类PersonContentProvider,继承ContetProvider
PersonContentProvider.java:
1 package com.example.contentprovidertest01; 2 3 import com.example.contentprovidertest01.dao.PersonDao; 4 5 import android.content.ContentProvider; 6 import android.content.ContentUris; 7 import android.content.ContentValues; 8 import android.content.UriMatcher; 9 import android.database.Cursor; 10 import android.net.Uri; 11 import android.os.Bundle; 12 import android.util.Log; 13 14 public class PersonContentProvider extends ContentProvider { 15 16 private final String TAG = "PersonContentProvider"; 17 private PersonDao personDao = null; 18 private static final UriMatcher URI_MATCHER = new UriMatcher( 19 UriMatcher.NO_MATCH);// 默认的规则是不匹配的 20 private static final int PERSON = 1; // 操作单行记录 21 private static final int PERSONS = 2; // 操作多行记录 22 // 往UriMatcher中添加匹配规则。注意,这里面的url不要写错了,我就是因为写错了,半天没调试出来。哎··· 23 static { 24 // 添加两个URI筛选 25 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 26 "person", PERSONS); 27 // 使用通配符#,匹配任意数字 28 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 29 "person/#", PERSON); 30 } 31 32 public PersonContentProvider() { 33 34 } 35 36 @Override 37 public boolean onCreate() { 38 // 初始化一个数据持久层 39 personDao = new PersonDao(getContext()); 40 //Log.i(TAG, "--->>onCreate()被调用"); 41 return true; 42 } 43 44 @Override 45 public Uri insert(Uri uri, ContentValues values) { 46 Uri resultUri = null; 47 // 解析Uri,返回Code 48 int flag = URI_MATCHER.match(uri); 49 switch (flag) { 50 case PERSONS: 51 //调用数据库的访问方法 52 long id = personDao.insertPerson(values); //执行插入操作的方法,返回插入当前行的行号 53 resultUri = ContentUris.withAppendedId(uri, id); 54 Log.i(TAG,"--->>插入成功, id=" + id); 55 Log.i(TAG,"--->>插入成功, resultUri=" + resultUri.toString()); 56 System.out.println("insert success"); 57 break; 58 } 59 return resultUri; 60 } 61 62 //方法:删除记录。注:参数:selection和selectionArgs是查询的条件,是由外部(另一个应用程序)传进来的 63 @Override 64 public int delete(Uri uri, String selection, String[] selectionArgs) { 65 int count = -1; //影响数据库的行数 66 try { 67 int flag = URI_MATCHER.match(uri); 68 switch (flag) { 69 case PERSON: 70 // delete from student where id=? 71 // 单条数据,使用ContentUris工具类解析出结尾的Id 72 long id = ContentUris.parseId(uri); 73 String where_value = "id = ?"; 74 String[] args = { String.valueOf(id) }; 75 count = personDao.deletePerson(where_value, args); 76 break; 77 case PERSONS: 78 count = personDao.deletePerson(selection, selectionArgs); 79 break; 80 } 81 } catch (Exception e) { 82 e.printStackTrace(); 83 } 84 Log.i(TAG, "--->>删除成功,count=" + count); 85 return count; 86 } 87 88 @Override 89 public int update(Uri uri, ContentValues values, String selection, 90 String[] selectionArgs) { 91 int count = -1; 92 try { 93 int flag = URI_MATCHER.match(uri); 94 switch (flag) { 95 case PERSON: 96 long id = ContentUris.parseId(uri); 97 String where_value = " id = ?"; 98 String[] args = { String.valueOf(id) }; 99 count = personDao.updatePerson(values, where_value, args); 100 break; 101 case PERSONS: 102 count = personDao 103 .updatePerson(values, selection, selectionArgs); 104 break; 105 } 106 } catch (Exception e) { 107 e.printStackTrace(); 108 } 109 Log.i(TAG, "--->>更新成功,count=" + count); 110 return count; 111 } 112 113 @Override 114 public Cursor query(Uri uri, String[] projection, String selection, 115 String[] selectionArgs, String sortOrder) { 116 Cursor cursor = null; 117 try { 118 int flag = URI_MATCHER.match(uri); 119 switch (flag) { 120 case PERSON: 121 long id = ContentUris.parseId(uri); 122 String where_value = " id = ?"; 123 String[] args = { String.valueOf(id) }; 124 cursor = personDao.queryPersons(where_value, args); 125 break; 126 case PERSONS: 127 cursor = personDao.queryPersons(selection, selectionArgs); 128 break; 129 } 130 } catch (Exception e) { 131 e.printStackTrace(); 132 } 133 Log.i(TAG, "--->>查询成功,Count=" + cursor.getCount()); 134 return cursor; 135 } 136 137 @Override 138 public String getType(Uri uri) { 139 int flag = URI_MATCHER.match(uri); 140 switch (flag) { 141 case PERSON: 142 return "vnd.android.cursor.item/person"; // 如果是单条记录,则为vnd.android.cursor.item/ 143 // + path 144 145 case PERSONS: 146 return "vnd.android.cursor.dir/persons"; // 如果是多条记录,则为vnd.android.cursor.dir/ 147 // + path 148 } 149 return null; 150 } 151 152 @Override 153 public Bundle call(String method, String arg, Bundle extras) { 154 Log.i(TAG, "--->>" + method); 155 Bundle bundle = new Bundle(); 156 bundle.putString("returnCall", "call被执行了"); 157 return bundle; 158 } 159 }
18行的UriMatcher类的作用是:匹配内容uri,默认的规则是不匹配的。UriMatcher提供了一个addURI方法:
- void android.content.UriMatcher.addURI(String authority, String path, int code)
这三个参数分别代表:权限、路径、和一个自定义代码。一般第一个参数是uri(包名.内容提供者的类名),第二个参数一般是数据库的表名。
27行:匹配规则的解释:*表示匹配任意字符,#表示匹配任意数字。注:如果内部的匹配规则越多,越容易访问。
138行的getType(Uri uri)方法:所有的内容提供者都必须提供的一个方法。用于获取uri对象所对应的MIME类型。
然后,每编写一个内容提供者,都必须在清单文件中进行声明。在AndroidManifest.xml中<application>节点中增加,格式如下:
<provider android:name=".内容提供者的类名" android:authorities="包名.内容提供者的类名" > </provider>
第3行表示的是uri路径,毕竟Contet Provider是通过路径来访问的。
所以在本程序中,在AndroidManifest.xml的<application>节点中增加如下代码:
<provider android:name=".PersonContentProvider" android:authorities="com.example.contentprovidertest01.PersonContentProvider" > </provider>
(4)单元测试类:
这里需要涉及到另外一个知识:ContentResolver内容访问者。
要想访问ContentProvider,则必须使用ContentResolver。可以通过ContentResolver来操作ContentProvider所暴露处理的接口。一般使用Content.getContentResolver()方法获取ContentResolver对象。第一段中已经提到:ContentProvider有很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的。所以它也存在insert、query、update、delete等方法。于是单元测试类可以这样写:(注:单元测试如果不清楚,可以参考另外一篇文章: JUnit单元测试的使用)
MyTest.java:
1 package com.example.contentresolvertest; 2 3 import android.content.ContentResolver; 4 import android.content.ContentValues; 5 import android.database.Cursor; 6 import android.net.Uri; 7 import android.os.Bundle; 8 import android.test.AndroidTestCase; 9 import android.util.Log; 10 11 public class MyTest extends AndroidTestCase { 12 13 public MyTest() { 14 // TODO Auto-generated constructor stub 15 16 } 17 18 public void calltest() { 19 ContentResolver contentResolver = getContext().getContentResolver(); 20 Uri uri = Uri 21 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 22 Bundle bundle = contentResolver.call(uri, "method", null, null); 23 String returnCall = bundle.getString("returnCall"); 24 Log.i("main", "-------------->" + returnCall); 25 } 26 27 //测试方法:向数据库中添加记录。如果之前没有数据库,则会自动创建 28 public void insert() { 29 // 使用内容解析者ContentResolver访问内容提供者ContentProvider 30 ContentResolver contentResolver = getContext().getContentResolver(); 31 ContentValues values = new ContentValues(); 32 values.put("name", "生命贰号"); 33 values.put("address", "湖北"); 34 // content://authorities/person 35 // http:// 36 Uri uri = Uri 37 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 38 contentResolver.insert(uri, values); 39 } 40 41 //测试方法:删除单条记录。如果要删除所有记录:content://com.example.contentprovidertest01.PersonContentProvider/person 42 public void delete() { 43 ContentResolver contentResolver = getContext().getContentResolver(); 44 Uri uri = Uri 45 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2");//删除id为1的记录 46 contentResolver.delete(uri, null, null); 47 } 48 49 //测试方法:根据条件删除记录。 50 public void deletes() { 51 ContentResolver contentResolver = getContext().getContentResolver(); 52 Uri uri = Uri 53 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 54 String where = "address=?"; 55 String[] where_args = { "HK" }; 56 contentResolver.delete(uri, where, where_args); //第二个参数表示查询的条件"address=?",第三个参数表示占位符中的具体内容 57 } 58 59 //方法:根据id修改记录。注:很少有批量修改的情况。 60 public void update() { 61 ContentResolver contentResolver = getContext().getContentResolver(); 62 Uri uri = Uri 63 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2"); 64 ContentValues values = new ContentValues(); 65 values.put("name", "李四"); 66 values.put("address", "上海"); 67 contentResolver.update(uri, values, null, null); 68 } 69 70 //方法:根据条件来修改记录。 71 public void updates() { 72 ContentResolver contentResolver = getContext().getContentResolver(); 73 Uri uri = Uri 74 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/student"); 75 ContentValues values = new ContentValues(); 76 values.put("name", "王五"); 77 values.put("address", "深圳"); 78 String where = "address=?"; 79 String[] where_args = { "beijing" }; 80 contentResolver.update(uri, values, where, where_args); 81 } 82 83 //测试方法:查询所有记录。如果要查询单条记录:content://com.example.contentprovidertest01.PersonContentProvider/person/1 84 public void query() { 85 ContentResolver contentResolver = getContext().getContentResolver(); 86 Uri uri = Uri 87 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 88 Cursor cursor = contentResolver.query(uri, null, null, null, null); 89 while (cursor.moveToNext()) { 90 Log.i("MyTest", 91 "--->>" 92 + cursor.getString(cursor.getColumnIndex("name"))); 93 } 94 } 95 96 //测试方法:根据条件查询所有记录。 97 public void querys() { 98 ContentResolver contentResolver = getContext().getContentResolver(); 99 Uri uri = Uri 100 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 101 String where = "address=?"; 102 String[] where_args = { "深圳" }; 103 Cursor cursor = contentResolver.query(uri, null, where, where_args, 104 null); 105 while (cursor.moveToNext()) { 106 Log.i("main", 107 "-------------->" 108 + cursor.getString(cursor.getColumnIndex("name"))); 109 } 110 } 111 112 }
既然ContetProvider实现的是跨应用访问数据,那这个测试类Test.java就应该写在另一个应用程序中才行。于是,我们新建另外一个工程文件ContentResolverTest,在里面添加单元测试,里面的代码其实和上方的Test.java的代码是一模一样的。运行单元测试,依然能在ContentResolverTest中实现对ContentProviderTest01中的CRUD.核心在于:使用应用1中的内容解析者ContentResolver访问应用2中的内容提供者ContentProvider
现在运行ContentProviderTest01中的单元测试类:
1、运行insert()方法,实现插入操作。后台打印如下:
上图中红框部分表明,这个uri就是代表内容提供者中,person表中,id为1的数据。
此时,打开file Explorer,进行查看,发现确实多了个文件:
注意:如果SQLite中之前没有mydb.db这个数据库,当实现插入操作时,会自动创建mydb.db这个数据库,并自动创建person表(因为在PersonDao类中执行了getWritableDatabase()方法)。
现在将上图中的mydb.db导出,然后用SQLiteExpert软件打开,输入sql查询语句,就可以看到person表中的数据了:
如果再执行insert()方法,又会继续添加一条记录(id是自动增长的)。
2、运行query()方法,查询所有记录(目前一共两条记录)。后台输出效果如下:
经测试,其他方法也都是可以执行的。
事实证明,新建的另外一个工程文件ContentResolverTest中,在里面运行单元测试,也是可以执行的(单元测试的代码不变,实现的CRUD功能也一模一样),也就是说,能够对ContentProviderTest01中的SQLite进行CRUD操作。例如,运行query()方法,后台输出如下:
这样,我们的目的也就达到了。
【特别注意】
需要特别注意的是,代码中uri不要写错了,这些错误一旦发生,很难被发现。具体表现在:
1、清单文件中:
<provider android:name=".内容提供者的类名" android:authorities="包名.内容提供者的类名" > </provider>
如:
<provider android:name=".PersonContentProvider" android:authorities="com.example.contentprovidertest01.PersonContentProvider" > </provider>
2、ContentProvider类中的UriMatcher中的uri:
1 private static final UriMatcher URI_MATCHER = new UriMatcher( 2 UriMatcher.NO_MATCH);// 默认的规则是不匹配的 3 private static final int PERSON = 1; // 操作单行记录 4 private static final int PERSONS = 2; // 操作多行记录 5 // 往UriMatcher中添加匹配规则。注意,这里面的url不要写错了,我就是因为写错了,半天没调试出来。哎··· 6 static { 7 // 添加两个URI筛选 8 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 9 "person", PERSONS); 10 // 使用通配符#,匹配任意数字 11 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 12 "person/#", PERSON); 13 }
3、ContentProvider类中的getType()方法里面的代码:
1 @Override 2 public String getType(Uri uri) { 3 int flag = URI_MATCHER.match(uri); 4 switch (flag) { 5 case PERSON: 6 return "vnd.android.cursor.item/person"; // 如果是单条记录,则为vnd.android.cursor.item/ 7 // + path 8 case PERSONS: 9 return "vnd.android.cursor.dir/persons"; // 如果是多条记录,则为vnd.android.cursor.dir/ 10 // + path 11 } 12 return null; 13 }
4、ContentResolver类中的uri:(以insert()方法为例)
1 //测试方法:向数据库中添加记录。如果之前没有数据库,则会自动创建 2 public void insert() { 3 // 使用内容解析者ContentResolver访问内容提供者ContentProvider 4 ContentResolver contentResolver = getContext().getContentResolver(); 5 ContentValues values = new ContentValues(); 6 values.put("name", "生命贰号"); 7 values.put("address", "湖北"); 8 // content://authorities/person 9 // http:// 10 Uri uri = Uri 11 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 12 contentResolver.insert(uri, values); 13 }
【工程文件】
链接:http://pan.baidu.com/s/1hq7VO12
密码:0a49