ContentProvider 内容提供者
ContentProvider的作用是把私有数据共享给其他用户访问。当用户进程通过内容提供者访问拥有私有数据的应用时,该应用会自动启动进程。使用Contentprovider一般对应的是数据库的操作,以增删改查为主,所以必须要对SQLite数据库的增删改查有一定了解
定义
- 自定义类继承ContentProvider
- 清单文件中配置地址和是否可读取:
<provider android:name ="com.haha.myprovider.CPTest"
//提供地址名,不允许相同的地址
android:authorities="com.haha.pro"
//是否可以导出数据
android:exported="true" >
</provider>
- 使用
-
对于内容接收者:
①获取该程序内部的数据,一般写在onCreate方法中
②复写内容接收者的增删改查方法
class CPTest extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
- 对于其他应用程序,通过ContentResolver来增删改查
- 获取内容分解器
private ContentResolver cr = getContentResolver();
- 具体操作
- 增加
ContentValues cv = new ContentValues();
cv.put("字段名", "值");
cr.insert("表路径Uri", cv);
- 删除
cr.delete("表路径Uri","条件(例如name=?)","对应的参数数组")
- 修改
cr.update("表路径Uri",ContentValues,"条件","条件中对应的参数数组")
- 查询
cr.query("表路径Uri","字段名称","条件","条件中对应的参数数组","排序规则")
内容提供者的筛选器UriMatcher
内容提供者的筛选和判别是通过判断一条uri跟指定的多条uri中的哪条匹配来决定的,UriMatcher是用来筛选Uri的类。
关于Uri的介绍,可以参照一下Uri详解之——Uri结构与代码提取,UriMatcherr 类主要用于匹配Uri,其使用方法如下:
//初始化,UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//注册需要的api
private static final int FLAG1= 1;
private static final int FLAG2 = 2;
matcher.addURI("com.haha.a", "hehe", FLAG1);
matcher.addURI("com.haha.b", "hehe/#", FLAG2);
//与已经注册的Uri进行匹配
Uri uri = Uri.parse("content://" + "com.haha.a" + "/hehe");
int match = matcher.match(uri);
switch (match)
{
case FLAG1:
return "content:com.haha.a/hehe";
case FLAG2:
return "content:com.haha.b/hehe";
default:
return null;
}
addURI的三个参数,第一个为authority,一般为用户包名;第二个参数代表路径,其中“#”号可以代表任意数字,路径的末尾携带的数据或者文本;第三个参数为标识符,即Uri匹配之后返回的数值
示例
- 增加数据
外部调用者:
ContentResolver cr = getContentResolver();
ContentValues con = new ContentValues();
con.put("数据库字段名","要插入的数据");
cr.insert(Uri.parse("内容提供者的地址名"),con);
内容接收者:
//把数据库操作API设置为全局变量
private SQLiteDatabase db;
@Override
public boolean onCreate() {
MyOpenHelper openHelper = new MyOpenHelper(getContext());//该类是我们自定义的数据库类
db = openHelper.getWritableDatabase();
return false ;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
db.insert( "person",null , values);
return null ;
}
- 读写短信
系统短信是存在于数据库中的,系统短信存在于系统应用:com.android.providers.telepho中。里面有多个表格,只需要关注sms表。
sms表中只需要关注4个字段,需要注意的一点是threa_id是会话id,不是线程id,同一个号码对应同一个id。
body:短信内容
address:短信的发件人或收件人号码
date:短信时间
type:1为收到,2为发送,5为发送失败
- 读取数据库中的短信
//查询短信
ContentResolver con=getContentResolver();
Cursor query = con.query(Uri.parse( "content://sms"), new String[]{"address" ,"body" ,"date" ,"type" }, null, null, null );
//Message为我们自己构建的信息Bean
Message mes = new Message ();
while(query.moveToNext()){
mes.setAddress(query.getString(0));
mes.setBody(query.getString(1));
mes.setDate(query.getString(2));
mes.setType(query.getString(3));
}
- 写入短信
短信的写入是一个耗时操作,一般不写在主线程中,我们开启一条线程来进行写入,注意点是写入短信需要读写两个权限,而查询短信只需要读权限,动态权限的申请不在此罗列
new Thread(new Runnable() {
@Override
public void run() {
ContentValues values = new ContentValues();
values.put( "address", "120" );
values.put( "body", "你好!sx" );
values.put( "date", System.currentTimeMillis());
values.put( "type", 2);
Uri insert = con.insert(Uri.parse( "content://sms"), values);
}
}).start();
- 操作系统联系人
联系人在系统应用:com.android.providers.contact中,联系人数据库是contact2.db
该数据库中有n多表,其中我们只需要关注两个表就可以:
(1) rawcontacts表:
contact_id:联系人id
(2)data表:联系人的具体信息,一个信息占一行
data1:信息的具体内容
raw_contact_id:联系人id,描述信息属于哪个联系人
mimetype_id:描述信息是属于什么类型
- 获取系统联系人
Uri.parse(“content://com.android.contacts/data”)这是查询的data视图,是由多张表构成的,并非是单独的data表,所以当我们查询mimetype_id字段时候会报出不存在该列名的异常。查询字段类型的时候,字段名为mimetype,不需要通过mimetype_id来获取字段类型了,所以直接查询mimetype来获取字段类型就可以了。
/*
* 获取联系人
* 1.首先查询raw_contacts表的contact_id字段获取联系人id
* 2.通过联系人id作为where条件获取data视图(由多个表组成)中的data1字段(具体信息)和 mimetype字段(信息的具体类型)
* 3.通过类型判断具体信息的类型。
*/
//获取系统联系人操作,首先查询系统联系人id,需要查询raw_contacts表;
Cursor query = con.query(Uri.parse( "content://com.android.contacts/raw_contacts" ), new String[]{ "contact_id"},
null,null , null);
//根据联系人id查询data表
while(query.moveToNext()){
//取出联系人id
String id = query.getString(0);
if(id==null )//如果联系人已经删除的话,该id为null
continue;
//根据联系人id查询data视图,获取到联系人的具体信息和类型
Cursor data = con.query(Uri.parse( "content://com.android.contacts/data" ), new String[]{ "data1","mimetype" },
"raw_contact_id=?", new String[]{id}, null );
//根据信息的类型来判定信息
Contacts user = new Contacts();
while(data.moveToNext()){
String da = data.getString(0);
String type = data.getString(1);
if("vnd.android.cursor.item/email_v2" .equals(type)){
user.setEmail(da);
} else if("vnd.android.cursor.item/name" .equals(type)){
user.setName(da);
} else if("vnd.android.cursor.item/phone_v2" .equals(type)){
user.setPhone(da);
}
}
}
- 插入联系人
先查询raw_contacts表,确定新的联系人的id应该是多少 ,然后把确定的联系人id插入raw_contacts表 注意点:当我们删除了一个联系人,在raw_contacts表中该联系人的id变为null,但是在data表中却不会删除该联系人,这时在raw_contacts表中不能通过判断联系人id来插入数据了,但是主键是不会更改的所以我们通过主键来获取该插入第几个联系人
/*
* 1.查询最新联系人的主键
* 2.插入联系人的位置是主键+1
* 3.先插入联系人id
* 4.再把主要内容插入data表中
* */
//查询最后一个主键
Cursor query = con.query(Uri.parse( "content://com.android.contacts/raw_contacts" ), new String[]{ "_id"},null, null , null);
//当前联系人应该插入的位置
int index = 1;
if(query.moveToLast()){
index = query.getInt(0)+1;
}
//先插入主键id
ContentValues values = new ContentValues();
values.put( "contact_id", index);
con.insert(Uri.parse( "content://com.android.contacts/raw_contacts" ),values);
//在data视图中插入联系人具体信息
values.clear();
values.put("data1", "小花");
values.put("mimetype", "vnd.android.cursor.item/name" );
values.put("raw_contact_id", index);
con.insert(Uri.parse ("content://com.android.contacts/data" ), values);
values.clear();
values.put("data1", "10086");
values.put("mimetype", "vnd.android.cursor.item/phone_v2" );
values.put("raw_contact_id", index);
con .insert(Uri.parse ("content://com.android.contacts/data" ), values);
values.clear();
values.put("data1", "10086@qq.com");
values.put("mimetype", "vnd.android.cursor.item/email_v2" );
values.put("raw_contact_id", index);
con .insert(Uri.parse ("content://com.android.contacts/data" ), values);
ContentObserver 内容观察者
内容观察者是指当数据库数据改变时,内容提供者会发出通知,在内容提供者的uri上注册一个内容观察者,就可以收到数据改变的通知
- 在外部程序中进行内容观察者的注册
//获取Reselver
ContentResolver con = getContentResolver();
//注册内容观察者,监听短信数据库
con.registerContentObserver(Uri. parse("content://sms"),true,new MyContentObserver(new Handler()));
第一个参数为要监听的表路径
第二个参数:
true:以第一个参数 uri作为根目录的所有表格都能收到改变的通知
false:精确指定的URI上的数据改变,才会收到通知
第三个参数:内容观察者
- 自定义类实现内容监听
class MyContentObserver extends ContentObserver{
public MyContentObserver(Handler handler) {
super(handler);
}
//复习onChange方法,当监听对象状态改变时进行回调
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
- 在内容提供者的增删该查时,可以选择向内容观察者提供内容已更改的消息:
//向内容观察者发送修改数据的通知
getContext().getContentResolver().notifyChange(
uri, //指定哪个 uri上的数据改变了
null//第二个参数用于指定内容观察者来接收该消息
);