Android 之内容提供者 内容解析者 内容观察者
contentProvider:ContentProvider在Android中的作用是对外提供数据,除了可以为所在应用提供数据外,还可以共享数据给其他应用,这是Android中解决应用之间数据共享的机制。
通过ContentProvider我们可以对数据进行增删改查的操作。使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
ContentProvider在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据进行添删改查。
关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。
那么,这里为何要使用ContentProvider对外共享数据呢?
是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:
采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。
一般的数据库比如sqlitedatabase,是不能跨应用访问的,比如A.apk中的数据不能被B.apk使用,ContentProvider则可以跨平台访问数据。
默认的内容提供者方式应用有音视频、图片、通讯录、日历等。
示例:
第一步.首先在清单文件中添加单元测试授权和包
第二步.ContentProvider对外提供了完整的增删查改的操作,对内是通过数据库的操作实现的。
首先创建一个DBHelper类,让它继承SQLiteOpenHelper
1 package com.example.android_07contentprovider;
2
3 import android.content.Context;
4 import android.database.DatabaseErrorHandler;
5 import android.database.sqlite.SQLiteDatabase;
6 import android.database.sqlite.SQLiteDatabase.CursorFactory;
7 import android.database.sqlite.SQLiteOpenHelper;
8
9 public class DBHelper extends SQLiteOpenHelper {
10
11
12 private static String name="mydb.db";
13 private static int version=1;
14 public DBHelper(Context context) {
15 super(context, name, null, version);
16 // TODO Auto-generated constructor stub
17 }
18 @Override
19 public void onCreate(SQLiteDatabase db) {
20 // TODO Auto-generated method stub
21 String sql="create table student(id integer primary key autoincrement,name varchar(64),address varchar(64))";
22 db.execSQL(sql);//对表的创建
23 }
24
25 @Override
26 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
27 // TODO Auto-generated method stub
28
29 }
30
31 }
第三步.定义一个内容提供者studentProvider 继承ContentProvider。
并在清单文件中进行注册,在注册ContentProvider时,采用了authorities(主机名/域名)的方法对它进行唯一标识,
ContentProvider的标识类似于网站的域名,通过此域名我们可以准确访问到对应网站,网站为我们提供数据。
Authorities就是ContentProvider的域名。注册方法如下:
1 <provider android:name=".studentProvider" android:authorities="com.example.android_07contentprovider.studentProvider"></provider>
0.在create方法中实例化一个DBhelper对象:helper=new DBHelper(getContext());
1.在内容提供者内部声明一个UriMatcher对象,用来与内容解析者发送的地址进行匹配判断
2.声明标识位,用来判断操作单条还是多条记录
3.声明静态代码模块,用来定义匹配规则 *用来匹配所有的文字,#用来匹配所有的数字
matcher.addURI("授权地址", "一般设置成表名", 标志位);
1 static{
2 /**静态代码块定义匹配规则
3 * 清单文件中注册的授权地址
4 * 路径 通常设置为表名
5 */
6 matcher.addURI("com.example.android_07contentprovider.studentProvider", "student/#", STUDENT);
7 matcher.addURI("com.example.android_07contentprovider.studentProvider", "student", STUDENTS);
8 }
4.重写getType()方法,处理头部类型,来判断操作单条还是多条记录
1 public String getType(Uri uri) {
2 // TODO Auto-generated method stub
3 int flag=matcher.match(uri);
4 switch (flag) {
5 case STUDENT:
6 return "vnd.android.cursor.item/student";
7 case STUDENTS:
8 return "vnd.android.cursor.dir/students";
9 }
10 return null;
11 }
5.重写插入方法,返回uri供其他应用使用
1 @Override
2 public Uri insert(Uri uri, ContentValues values) {
3 // TODO Auto-generated method stub
4 Uri resUri=null;
5 int flag=matcher.match(uri);//uri参数是外部传递过来的规则 与提供者的规则进行匹配
6 switch (flag) {
7 case STUDENTS:
8 SQLiteDatabase db=helper.getWritableDatabase();
9 long id=db.insert("student", null, values);//返回id为插入当前行的行号
10 resUri=ContentUris.withAppendedId(uri, id);
11 break;
12 }
13 Log.i(TAG, "---->>"+resUri.toString());
14 return resUri;//content//返回给其他应用使用
15 }
6.测试:
a.需要定义一个内容解析者:ContentResolver resolver=getContext().getContentResolver();
b.调用内容解析者的insert方法:resolver.insert(uri, values);
c得到uri 和contentValues
最终源码:
项目结构:
MainActivity无变化
DBHelper工具类:
1 package com.example.android_07contentprovider;
2
3 import android.content.Context;
4 import android.database.DatabaseErrorHandler;
5 import android.database.sqlite.SQLiteDatabase;
6 import android.database.sqlite.SQLiteDatabase.CursorFactory;
7 import android.database.sqlite.SQLiteOpenHelper;
8
9 public class DBHelper extends SQLiteOpenHelper {
10
11
12 private static String name="mydb.db";
13 private static int version=1;
14 public DBHelper(Context context) {
15 super(context, name, null, version);
16 // TODO Auto-generated constructor stub
17 }
18 @Override
19 public void onCreate(SQLiteDatabase db) {
20 // TODO Auto-generated method stub
21 String sql="create table student(id integer primary key autoincrement,name varchar(64),address varchar(64))";
22 db.execSQL(sql);//对表的创建
23 }
24
25 @Override
26 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
27 // TODO Auto-generated method stub
28
29 }
30
31 }
studentProvider:
1 package com.example.android_07contentprovider;
2
3 import android.content.ContentProvider;
4 import android.content.ContentUris;
5 import android.content.ContentValues;
6 import android.content.UriMatcher;
7 import android.database.Cursor;
8 import android.database.sqlite.SQLiteDatabase;
9 import android.net.Uri;
10 import android.util.Log;
11
12 public class studentProvider extends ContentProvider {
13
14 private static final String TAG="studentProvider";
15
16 private DBHelper helper=null;
17
18
19 private static final UriMatcher matcher=new UriMatcher(UriMatcher.NO_MATCH);
20 //标志位 分别表明操作单条、多条记录
21 private static final int STUDENT=1;
22 private static final int STUDENTS=2;
23 static{
24 /**静态代码块定义匹配规则
25 * 清单文件中注册的授权地址
26 * 路径 通常设置为表名
27 */
28 matcher.addURI("com.example.android_07contentprovider.studentProvider", "student/#", STUDENT);
29 matcher.addURI("com.example.android_07contentprovider.studentProvider", "student", STUDENTS);
30 }
31 public studentProvider() {
32 // TODO Auto-generated constructor stub
33 }
34
35 @Override
36 public boolean onCreate() {
37 // TODO Auto-generated method stub
38 helper=new DBHelper(getContext());
39 return false;
40 }
41
42 @Override
43 public Cursor query(Uri uri, String[] projection, String selection,
44 String[] selectionArgs, String sortOrder) {
45 // TODO Auto-generated method stub
46 Cursor cursor=null;
47 try {
48 int flag=matcher.match(uri);//uri参数是外部传递过来的规则 与提供者的规则进行匹配
49 SQLiteDatabase db=helper.getReadableDatabase();
50 switch (flag) {
51 case STUDENT:
52 //delete from student where id=? id是通过客户端传递过来的 需要进行截取
53 long id=ContentUris.parseId(uri);
54 String where_value="id="+id;
55 if(selection!=null&&!selection.equals("")){
56 where_value+="and"+selection;
57 }
58 cursor=db.query("student", null, where_value, selectionArgs, null, null, null, null);
59 break;
60 case STUDENTS:
61 cursor=db.query("student", null, null, selectionArgs, null, null, null, null);
62 break;
63 }
64
65 } catch (Exception e) {
66 // TODO: handle exception
67 }
68 return cursor;
69 }
70
71 /**
72 *
73 */
74 @Override
75 public String getType(Uri uri) {
76 // TODO Auto-generated method stub
77 int flag=matcher.match(uri);
78 switch (flag) {
79 case STUDENT:
80 return "vnd.android.cursor.item/student";
81 case STUDENTS:
82 return "vnd.android.cursor.dir/students";
83 }
84 return null;
85 }
86
87 @Override
88 public Uri insert(Uri uri, ContentValues values) {
89 // TODO Auto-generated method stub
90 Uri resUri=null;
91 int flag=matcher.match(uri);//uri参数是外部传递过来的规则 与提供者的规则进行匹配
92 switch (flag) {
93 case STUDENTS:
94 SQLiteDatabase db=helper.getWritableDatabase();
95 long id=db.insert("student", null, values);//返回id为插入当前行的行号
96 resUri=ContentUris.withAppendedId(uri, id);
97 break;
98 }
99 Log.i(TAG, "---->>"+resUri.toString());
100 return resUri;//content//返回给其他应用使用
101 }
102
103 @Override
104 public int delete(Uri uri, String selection, String[] selectionArgs) {
105 // TODO Auto-generated method stub
106 int count=-1;//表示影响数据库的行数
107 try {
108 int flag=matcher.match(uri);//uri参数是外部传递过来的规则 与提供者的规则进行匹配
109 SQLiteDatabase db=helper.getWritableDatabase();
110 switch (flag) {
111 case STUDENT:
112 //delete from student where id=? id是通过客户端传递过来的 需要进行截取
113 long id=ContentUris.parseId(uri);
114 String where_value="id="+id;
115 if(selection!=null&&!selection.equals("")){
116 where_value+="and"+selection;
117 }
118 count=db.delete("student", where_value, selectionArgs);
119 break;
120 case STUDENTS:
121 count=db.delete("student", selection, selectionArgs);
122 break;
123 }
124
125 } catch (Exception e) {
126 // TODO: handle exception
127 }
128 return count;
129 }
130
131 @Override
132 public int update(Uri uri, ContentValues values, String selection,
133 String[] selectionArgs) {
134 // TODO Auto-generated method stub
135 int count=-1;//表示影响数据库的行数
136 try {
137 //update student set name=?,address=? where id=?
138 int flag=matcher.match(uri);//uri参数是外部传递过来的规则 与提供者的规则进行匹配
139 SQLiteDatabase db=helper.getWritableDatabase();
140 switch (flag) {
141 case STUDENT:
142 //delete from student where id=? id是通过客户端传递过来的 需要进行截取
143 long id=ContentUris.parseId(uri);
144 String where_value="id="+id;
145 if(selection!=null&&!selection.equals("")){
146 where_value+="and"+selection;
147 }
148 count=db.update("student", values,where_value, selectionArgs);
149 break;
150 }
151
152 } catch (Exception e) {
153 // TODO: handle exception
154 }
155 return count;
156 }
157
158 }
测试类(内容解析者)
1 package com.example.android_07contentprovider;
2
3 import android.content.ContentResolver;
4 import android.content.ContentValues;
5 import android.database.Cursor;
6 import android.net.Uri;
7 import android.test.AndroidTestCase;
8
9 public class MyTest extends AndroidTestCase {
10
11 public MyTest() {
12 // TODO Auto-generated constructor stub
13 }
14 public void insert(){
15 //访问内容提供者的步骤
16 //1.需要一个内容解析者
17 ContentResolver resolver=getContext().getContentResolver();
18 //2.content://+授权 +表名来访问内容提供者
19 Uri uri=Uri.parse("content://com.example.android_07contentprovider.studentProvider/student");
20 ContentValues values=new ContentValues();
21 values.put("name", "zhangsan");
22 values.put("address", "zhogngguo");
23 resolver.insert(uri, values);
24 }
25 public void delete(){
26 ContentResolver resolver=getContext().getContentResolver();
27 //单条删除
28 /*Uri uri=Uri.parse("content://com.example.android_07contentprovider.studentProvider/student/1");
29 resolver.delete(uri, null,null);*/
30 //多条删除
31 Uri uri=Uri.parse("content://com.example.android_07contentprovider.studentProvider/student");
32 resolver.delete(uri, null,null);
33 }
34 public void modify(){
35 ContentResolver resolver=getContext().getContentResolver();
36 Uri uri=Uri.parse("content://com.example.android_07contentprovider.studentProvider/student/4");
37 ContentValues values=new ContentValues();
38 values.put("name", "zhaohaohao");
39 values.put("address", "jiaozuo");
40 resolver.update(uri, values, null, null);
41 }
42 public void query(){
43 ContentResolver resolver=getContext().getContentResolver();
44 //查询单条记录
45 Uri uri=Uri.parse("content://com.example.android_07contentprovider.studentProvider/student/5");
46 Cursor cursor=resolver.query(uri, null, null, null, null);
47 while(cursor.moveToNext()){
48 System.out.println(cursor.getString(cursor.getColumnIndex("name")));
49 }
50 //查询多条记录
51 /*Uri uri=Uri.parse("content://com.example.android_07contentprovider.studentProvider/student");
52 Cursor cursor=resolver.query(uri, null, null, null, null);
53 while(cursor.moveToNext()){
54 System.out.println(cursor.getString(cursor.getColumnIndex("name")));
55 }*/
56 }
57 }
--------------------------------------------------
内容观察者ContentObserver类详解:
观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于 数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。
1.在oncreate方法中注册一个观察者
1 // 注册内容观察者
2 getContentResolver().registerContentObserver(airplaneUri, false, airplaneCO);
抽象类ContentResolver类中的方法原型如下:
public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。
参数:uri 需要观察的Uri(需要在UriMatcher里注册,否则该Uri也没有意义了)
notifyForDescendents 为false 表示精确匹配,即只匹配该Uri
为true 表示可以同时匹配其派生的Uri,举例如下:
假设UriMatcher 里注册的Uri共有一下类型:
1 、content://com.qin.cb/student
2 、content://com.qin.cb/student/#
3、 content://com.qin.cb/student/schoolchild(派生的Uri)
假设我们当前需要观察的Uri为content://com.qin.cb/student,如果发生数据变化的 Uri 为 content://com.qin.cb/student/schoolchild ,当notifyForDescendents为 false,那么该ContentObserver会监听不到,
但是当notifyForDescendents 为ture,能捕捉该Uri的数据库变化。
2.实现ContentObserver observer
1 //用来观察系统里短消息的数据库变化 ”表“内容观察者,只要信息数据库发生变化,都会触发该ContentObserver 派生类 2 public class SMSContentObserver extends ContentObserver { 3 private static String TAG = "SMSContentObserver"; 4 5 private Context Context ; 6 7 public SMSContentObserver(Context context,Handler handler) { 8 this.context = context ; 9 10 } 11 /** 12 * 当所监听的Uri发生改变时,就会回调此方法 13 * 14 * @param selfChange 此值意义不大 一般情况下该回调值false 15 */ 16 @Override 17 public void onChange(boolean selfChange){ 18 Log.i(TAG, "the sms table has changed"); 19 20 //查询发件箱里的内容 21 Uri outSMSUri = Uri.parse("content://sms") ; 22 23 Cursor c = Context.getContentResolver().query(uri, null, null, null,"date desc"); 24 if(c != null){ 25 26 Log.i(TAG, "the number of send is"+c.getCount()) ; 27 28 StringBuilder sb = new StringBuilder() ; 29 //循环遍历 30 while(c.moveToNext()){ 31 // sb.append("发件人手机号码: "+c.getInt(c.getColumnIndex("address"))) 32 // .append("信息内容: "+c.getInt(c.getColumnIndex("body"))) 33 // .append("是否查看: "+c.getInt(c.getColumnIndex("read"))) 34 // .append("发送时间: "+c.getInt(c.getColumnIndex("date"))) 35 // .append("\n"); 36 sb.append("发件人手机号码: "+c.getInt(c.getColumnIndex("address"))) 37 .append("信息内容: "+c.getString(c.getColumnIndex("body"))) 38 .append("\n"); 39 } 40 c.close(); 41 sysout(sb.tostring()) ; 42 } 43 } 44 45 }
构造方法 public void ContentObserver(Handler handler)
说明:所有 ContentObserver的派生类都需要调用该构造方法
参数: handler Handler对象。可以是主线程Handler(这时候可以更新UI 了),也可以是任何Handler对象。
重写方法
void onChange(boolean selfChange)
功能:当观察到的Uri发生变化时,回调该方法去处理。所有ContentObserver的派生类都需要重载该方法去处理逻辑。
参数:selfChange 回调后,其值一般为false,该参数意义不大。
以上讲清楚了如何让第三方观察公共内存消息邮箱的变化,那么系统短信应用 是如何发布出去自己的变化呢?
如果是我们自定义的ContentProvider数据源,而非系统的应用,例如短信应用,如果直接使用ContentObserver,那么数据源发生改变 后,就不能监听到任何反应。
实际上每个ContentProvider数据源发生改变后,如果想通知其监听对象ContentObserver时,必须在其对应方法 update / insert / delete时,
显示的调用context.getContentReslover().notifychange(Uri uri , ContentObserver observer)方法,回调监听处理逻辑。否则,我们的ContentObserver是不会监听到数据发生改变的。
参数:uri指的是数据源发生变化后,将变化放到哪一块内存空间中 可以是content://aaa.bbb.ccc
observer显示地指定一个观察者,指的是指定一个人来响应自己的操作,可以为null
接下来就可以自定义一个第三方应用,来接收刚才放到content://aaa.bbb.ccc中的内容了
步骤如上:1.在oncreate方法中注册一个观察者;2.实现ContentObserver observer