要解决的问题:
有两个工程A和工程B,工程A中通过SQLiteOpenHelper的子类创建了一个自产自销的数据库,工程B中没有数据库,但是工程B想要与工程A共用同一个数据库。
为实现以上目标,需要2个系统类:
- ContentProvider 内容提供者 Andord四大组件之一
- ContentResolver 内容观察者
两者结合起来可以进程间(多个工程之间)数据的共享
使用方式:
工程A(要向其他工程开放数据的):
- 先准备一个SQLiteOpenHelper的子类,用于管理当前工程自产自销的数据库
- 为了把自产自销的数据库开放出去,需要通过ContentProvider进行以下操作:
a. 创建一个ContentProvider的子类
b. 到清单文件中注册ContentProvider的子类
<!-- 在application标签中注册ContentProvider子类的信息
android:name 用于指定子类的具体路径
android:authorities 设置当前子类对应的作者名,属性值写任意字符串即可
用于稍后再其他工程中通过ContentResolver连接此类时,设置的Uri路径中的authorities
android:exported="true" 允许进行数据开放操作
-->
<provider
android:name="com.example.day14_5_contentprovider.MyProvider"
android:authorities="com.ay.sql"
android:exported="true"
></provider>
c. 在子类的增删改查方法中填写相应代码
MyProvider子类:
public class MyProvider ext ends ContentProvider {
//初始化用于比较Uri路径类型的UriMatcher对象
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int VIP = 1;
private static final int VIP_ID = 3;
private static final int VIP_LIKE = 4;
private static final int BLACK = 2;
static {
//添加比对参照物
/**
* 代表向matcher对象中存入参照物: content://com.ay.oye/vip ,
* 后期当通过matcher调用match方法进行比对时,一旦与当前参照物相同,match方法的返回值就是VIP对应的数据1
*/
matcher.addURI("com.ay.oye", "vip", VIP);
matcher.addURI("com.ay.oye", "black", BLACK);
matcher.addURI("com.ay.oye", "vip/#", VIP_ID); //#代表任意数字
matcher.addURI("com.ay.oye", "vip/*", VIP_LIKE); //* 代表任意字符
}
private SQLiteDatabase db;
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
db = new MyHelper(getContext()).getReadableDatabase();
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
Cursor cursor = null;
switch (matcher.match(uri)) {
case VIP:
//查询的是vip表
//通过db对象调用系统封装好数据库中用于实现查询的query方法
cursor = db.query("vip", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BLACK:
//查询的是black表
cursor = db.query("black", projection, selection, selectionArgs, null, null, sortOrder);
break;
case VIP_ID:
if (selection == null) {
cursor = db.query("vip", projection, "_id = "+uri.getLastPathSegment(), null, null, null, sortOrder);
} else {
cursor = db.query("vip", projection, selection+"and _id = "+uri.getLastPathSegment(), selectionArgs, null, null, sortOrder);
}
break;
case VIP_LIKE:
if (selection == null) {
cursor = db.query("vip", projection, "name like ?", new String[] {"%"+uri.getLastPathSegment()+"%"}, null, null, sortOrder);
} else {
cursor = db.query("vip", projection, selection + " and name like "+"'"+"%"+uri.getLastPathSegment()+"%"+"'", selectionArgs, null, null, sortOrder);
}
break;
}
return cursor;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return matcher.match(uri)+"";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
long num = 0;
switch (matcher.match(uri)) {
case VIP:
num = db.insert("vip", "_id", values);
break;
case BLACK:
num = db.insert("black", "_id", values);
break;
}
return Uri.withAppendedPath(uri, num+"");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
switch (matcher.match(uri)) {
case VIP:
db.delete("vip", selection, selectionArgs);
break;
case BLACK:
db.delete("black", selection, selectionArgs);
break;
case VIP_ID:
if (selection == null) {
db.delete("vip", "_id = "+uri.getLastPathSegment(), null);
} else {
db.delete("vip", selection+" and _id = "+uri.getLastPathSegment(), selectionArgs);
}
break;
case VIP_LIKE:
break;
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
工程B中(从工程A获取数据的):
1. 初始化ContentResolver对象
//初始化ContentResolver对象
cr = getContentResolver();
2. 通过ContentResolver对象调用增删改查方法实现数据库操作
如:
public void click (View v) {
switch (v.getId()) {
case R.id.but_add:
/**
* 先以增加为例
* 参数:
* 1. Uri 用于描述路径
* 拼接特点:
* scheme:// authorities / data
* scheme 主题 ,代表当前路径的具体类型,即是一个文件路径还是一个电话等
*
* 2.ContentValues ,用于存储要添加的数据
*/
ContentValues values = new ContentValues();
values.put("name", "花花");
values.put("age", 28);
/**
* 执行流程: 根据Uri路径中的作者名在整个设备的所有应用中寻找,哪个ContentProvider的子类注册了 相同的作者名
* 找到后运行该子类中的insert方法
*/
Uri uri = cr.insert(Uri.parse("content://com.ay.sql/pr"), values);
//想要从insert方法的返回的uri对象中获取到新添加的数据对应的id值的话
String id = uri.getLastPathSegment();
Log.i("oye", "工程6 新添加数据对应的id为: : "+id);
break;
case R.id.but_query:
/**
* 参数:
* 1. Uri路径 ,
* 2. 要查询表中的哪些列,填null代表获取所有列
* 3, 查询的条件
* 3, 用于替换条件中?的值
* 5. 排序
*/
Cursor cursor = cr.query(Uri.parse("content://com.ay.sql/pr"), null, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
Log.i("oye", "工程6中获取到的数据位: "+name);
}
break;
case R.id.but_delete:
int num = cr.delete(Uri.parse("content://com.ay.sql/pr"), "_id=?", new String[]{"3"});
Log.i("oye", "工程6中删除的条数为:"+num);
break;
case R.id.but_update:
ContentValues cv = new ContentValues();
cv.put("name", "王老六");
int num1 = cr.update(Uri.parse("content://com.ay.sql/pr"), cv, "_id = ?", new String[]{"5"});
Log.i("oye", "工程6中删除的条数为:"+num1);
break;
}
}
相对来说常见的使用如 :
系统的通话记录工程通过ContentProvider将通话记录开放了出来,我们想要获取通过记录信息,只需通过ContentResolver调用相应的增删改查方法即可
通过ContentResolver读取常见的系统数据:
1. 通话记录信息
2. 短信记录信息
3. 联系人通讯录信息
一、通过ContentResolver读取通话记录:
读取前的准备: 确保用于测试的设备上有通话记录,否则读取不出内容
如果是模拟器处理通话记录的方式:
1.拨出电话: 直接使用模拟器上的拨号软件即可
2.接听电话 (只有原生模拟器可以模拟接听电话):选择打开Window—ShowVIew --- Other—Android中的Emulator Control标签,在此标签中可以实现向模拟器中拨出电话,发出短信的功能
系统通话记录表的存储位置:
/*
* 通话记录的数据库的位置:data/data/com.android.providers.contacts/databases/contacts2.db
* 表名为:calls
* 对应的作者名:call_log
* 通过query方法读取内容
*/
通话记录表中的主要列:
number 存储电话号
type 存储通话类型,1 代表来电(接听的电话) 2 代表去电(拨出的电话)
date 通话的日期
duration 通话的持续时长
连接通过记录使用的Uri:
Uri.parse("content://call_log/calls")
具体的读取方式:
Cursor cursor = cr.query(Uri.parse("content://call_log/calls"), null, null, null, null);
while(cursor.moveToNext()) {
//电话号码
String number = cursor.getString(cursor.getColumnIndex("number"));
//是来电还是去电
String s = cursor.getString(cursor.getColumnIndex("type"));
String type = s.equals("1") ? "来电(接听的电话)":"去电(拨出的电话)";
//时间
String date = cursor.getString(cursor.getColumnIndex("date"));
//获取通话时长
String duration = cursor.getString(cursor.getColumnIndex("duration"));
Log.i("oye", "通过话记录中的数据位: "+number +" "+date+" "+type+" "+duration);
}
需要添加的权限:
<!-- 添加读取通话记录的权限 -->
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<!-- 添加修改写入通话记录的权限 -->
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
二、通过ContentResolver读取短信记录:
系统短信记录表的存储位置:
data/data/com.android.provider.telephony/databases/mmssms.db中的sms表
连接使用的Uri:
content://sms
表中比较重要的列:
address 电话号码
date 接收短信的日期
date_sent 发送短信的日期
read 用于标识短信是否已读, 0代表未读,1代表已读
type 用于标识短信类型 1 接收的短信,2发送的短信 3 发送失败,在草稿箱的短信
body 短信内容
读取方式:
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(Uri.parse("content://sms"), null, null, null, null);
while (cursor.moveToNext()) {
String number = cursor.getString(cursor.getColumnIndex("address"));
String mess = cursor.getString(cursor.getColumnIndex("body"));
String read = cursor.getString(cursor.getColumnIndex("read")).equals("0") ? "未读":"已读";
String type = cursor.getString(cursor.getColumnIndex("type")).equals("1")?"接收的短信":"发送的短信";
Log.i("oye", "获取到的短信内容为: "+number+" "+mess+" "+read+" "+type);
}
}
需要的权限:
<!-- 读取短信记录的权限 -->
<uses-permission android:name="android.permission.READ_SMS"/>
<!-- 修改短信记录的权限 -->
<uses-permission android:name="android.permission.WRITE_SMS"/>
三、通过ContentResolver读取系统通讯录联系人
系统通讯录联系人表的存储位置:
data/data/com.android.providers.contacts/databases/Contacts2.db的数据库文件中
读取联系人信息的主要表:
1.raw_contacts
用于存储联系人的名字以及该联系人对应的唯一id值
2.data
用于存储所有联系人的所有数据,但是注意:
(1)一行中并没有存储一个联系人的所有信息
而是将一个联系人的所有信息拆分成了多行存储
判断哪几行数据是属于同一个联系人的方式为:
根据每一行的raw_contact_id的值判断,值相同的则为同一联系人的所有数据
(2)基本上大部分的数据都是存储在data1列中,为了判断本行data1中的数据属于哪种类型(是电话号码还是姓名),需要根据mimetype_id这一列中的值进行判断
3.Mimetypes
用于存储所有的数据类名,如:电话号,姓名,邮箱地址等
读取方式:
读取方式一:
private ArrayList<MyC> queryAll() {
ArrayList<MyC> list = new ArrayList<MyC>();
// 1. 读取raw_contacts这张表,目的:获取每一个人联系人的唯一id标识,本次得到的idCursor中存储了所有联系人id值
Cursor idCursor = cr.query(
Uri.parse("content://com.android.contacts/raw_contacts"), null,
"deleted = 0", null, null);
while (idCursor.moveToNext()) {
// 获取联系人的id
int id = idCursor.getInt(idCursor.getColumnIndex("_id"));
MyC myc = new MyC();
myc.setId(id);
// 2. 根据或得到的id值到data表中读取数据,本次得到的cursor对象中存储了一个联系人的多方面数据
Cursor cursor = cr.query(
Uri.parse("content://com.android.contacts/data"), null,
"raw_contact_id = " + id, null, null);
// 3. 循环cursor对象,读取联系人数据
while (cursor.moveToNext()) {
// 获取data1这一列的值
String data1 = cursor.getString(cursor.getColumnIndex("data1"));
Log.i("oye", "当前行中data1 的数据为: " + data1);
String mime = cursor.getString(cursor
.getColumnIndex("mimetype"));
if (mime.equals("vnd.android.cursor.item/name")) {
// 代表当前行的data1列中存储的是姓名
myc.setName(data1);
} else if (mime.equals("vnd.android.cursor.item/email_v2")) {
// 代表本行的data1中存储的是邮箱地址
myc.setEmail(data1);
} else if (mime
.equals("vnd.android.cursor.item/postal-address_v2")) {
// 家庭住址
myc.setAddress(data1);
} else if (mime.equals("vnd.android.cursor.item/phone_v2")) {
// 存储的是电话号
String data2 = cursor.getString(cursor
.getColumnIndex("data2"));
if (data2.equals("1")) {
// 家庭座机号
myc.setPhone_home(data1);
} else if (data2.equals("2")) {
// 手机号
myc.setPhone_mobile(data1);
}
}
}
list.add(myc);
Log.i("oye", "*************************************");
}
return list;
}
读取方式二:
public void queryAllMethod2() {
// 管理所有联系人的Uri
Cursor c1 = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null,
null, null);
//读取所有联系人的名字
while (c1.moveToNext()) {
String name = c1.getString(c1
.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
Log.i("oye", "c1 ddd " + c1.toString() + " " + name);
}
Cursor c2 = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null,
null, null);
//读取所有的联系人的电话号
while (c2.moveToNext()) {
String number = c2.getString(c2
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i("oye", "c1 ddd " + c2.toString() + " " + number);
}
}
需要添加的权限:
<!-- 读取通讯录联系人的权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!-- 修改,添加通讯录联系人的权限 -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
删除数据的方式:
case R.id.but_delete:
//删除通讯录中联系人
//1. 从raw_contacts表中删除数据
cr.delete(Uri.parse("content://com.android.contacts/raw_contacts"), "_id=2", null);
//2. (可不写)再到data表中删除联系人
cr.delete(Uri.parse("content://com.android.contacts/data"), "raw_contact_id = 2", null);
break;
新增数据的方式:
case R.id.but_insert:
//新增联系人数据
//1.先向raw_contacts表中存入数据,并获取新添加的数据对应的id值
ContentValues values = new ContentValues();
values.put("display_name", "小赵");
Uri uri = cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), values);
//获取新添加的数据对应的id值
String id = uri.getLastPathSegment();
//2. 根据id向data表中分别存入,姓名,邮箱,电话号的信息
//存入姓名信息
values.clear();
values.put("data1", "小赵");
values.put("raw_contact_id", id);
values.put("mimetype", "vnd.android.cursor.item/name");
cr.insert(Uri.parse("content://com.android.contacts/data"), values);
//存入邮箱信息
values.clear();
values.put("data1", "250@250.com");
values.put("raw_contact_id", id);
values.put("mimetype", "vnd.android.cursor.item/email_v2");
values.put("data2", "1");
cr.insert(Uri.parse("content://com.android.contacts/data"), values);
//存入电话号码信息
values.clear();
values.put("data1", "250250250");
values.put("raw_contact_id", id);
values.put("mimetype", "vnd.android.cursor.item/phone_v2");
values.put("data2", "2");
cr.insert(Uri.parse("content://com.android.contacts/data"), values);
break;
修改数据的方式:
case R.id.but_update:
//修改联系人
//修改数据库中的原有内容
ContentValues values2 = new ContentValues();
values2.put("data1", "252252252");
cr.update(Uri.parse("content://com.android.contacts/data"), values2, "raw_contact_id = ? and mimetype = ?", new String[]{"3","vnd.android.cursor.item/phone_v2"});
//向数据库中添加新内容
ContentValues values1 = new ContentValues();
values1.put("data1", "家庭住址:");
values1.put("raw_contact_id", 3);
values1.put("mimetype", "vnd.android.cursor.item/postal-address_v2");
cr.insert(Uri.parse("content://com.android.contacts/data"), values1);
break;