1.调用系统的ContentProvider
Android系统提供了一些定义好的ContentProvider,可以根据这些ContentProvider的Uri得到相应的数据。比方说想获得手机短信的信息,或者想获得图库、联系人信息…必定有相应的Uri与之对应。知道对应的Uri后,就可以通过ContentResolver对象,根据Uri进行数据访问。

2.举例—手机短信数据的读写
根据Uri获取手机短信的信息,并将其备份和恢复:

首先在DDMS中向模拟器中发几条短信,然后运行程序,点击备份,提示备份成功后,将所有的短信删除,然后点击恢复,打开短信界面发现刚才删除的短信已经恢复,这就是我们要实现的功能。
首先分析一下怎么实现上述效果,如果想备份短信,首先要做的就是获取短信的列表,这一步比较简单,因为谷歌已经封装好,我们要做的就是用Uri去查询短信库,然后拿到数据后需要将数据以XML的形式保存到SD卡里面,当然用其它的方式也可以,只要能将其恢复就行。最后恢复的时候将指定路径的XML文件解析,根据Uri将解析的短信数据插入到系统的短信列表中。
了解了大概思路后,另一个重要的任务就是看看短信的表结构,在模拟器中它的路径是data->data->com.android.providers.telephony->databases下,如下图:

把它导出来,然后用Sqlite数据打开可以看到数据的结构,这里只关心threads表和sms表就够了,threads代表所有会话信息,每个会话代表和一个联系人之间短信的群组;sms代表短信具体信息。在sms表中的thread_id指向了threads表中的_id,指定每条短信的会话id,以便对短信进行分组。
threads表的结构如下:

_id:会话id,用于区分不同的电话号码,系统会为不同的电话号码分配不同的_id。
date:收到信息的时间(如果收到来自同一个phone number多条信息,并且有对于一条信息未读,那么date表示收到的最后一条信息时的时间)
message_count:收到的信息的数目
read: 0. 代表未读; 1.代表已读

sms表的结构如下:

_id:用于区分不同的短信
date::该条短信接收的时间
read:0表未读,1表已读
body: 具体的短信内容

访问手机短信的Uri,主要有以下这么几个:
content://sms/ 所有短信
content://sms/inbox 收件箱
content://sms/sent 已发送
content://sms/draft 草稿
content://sms/outbox 发件箱
content://sms/failed 发送失败
content://sms/queued 待发送列表
这里使用content://sms/,备份所有的短信。

首先要做的就是根据Uri获取短信的列表,这里新建一个SmsManage类,将备份和恢复的方法放到这个类中。
//获取短信列表
public List< SmsData> getSmsList() {
Uri uri = Uri. parse( “content://sms/”);
ContentResolver contentResolver = mContext.getContentResolver();
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if ( null != cursor) {
Log. i( TAG, “cursor.getCount():” + cursor.getCount());
//根据Cursor一条一条的添加到smsList中
while (cursor.moveToNext()) {
int _id = cursor.getInt( cursor.getColumnIndex("_id" ));
int type = cursor.getInt( cursor.getColumnIndex(“type” ));
String address = cursor.getString( cursor.getColumnIndex( “address”));
String body = cursor.getString( cursor.getColumnIndex(“body” ));
String date = cursor.getString( cursor.getColumnIndex(“date” ));
SmsData smsData = new SmsData(_id, type, address, body, date);
smsList.add(smsData);
}
cursor.close();
}
return smsList;
}
根据content://sms/这个Uri查询手机中短信的数据库,得到一个Cursor,这个Cursor就包含了一条一条的短信。然后去遍历这个Cursor将需要的数据添加到smsList中。这样短信数据就拿到了,因为要做的功能是短信备份,所以接下来需要将smsList这个集合中的数据保存到本地,以方便短信恢复的时候去读取保存的这个文件,那么问题来了,怎样将smsList这个集合以文件的形式保存到本地呢?当然方法有很多,这里采用的是使用XmlSerializer将其序列化,需要恢复的时候使用XmlPullParser 将其反序列化,就可以拿到备份的数据,听起来感觉挺高大上的,其实很简单就是对xml的操作。
下面来看看序列化的代码,即将上面得到的集合smsList中的数据生成一个xml文件,并保存到本地。:
//将短信数据保存到 sd卡中
public void saveSmsToSdCard(){
smsList=getSmsList();
//获得一个序列化对象
XmlSerializer xmlSerializer=Xml.newSerializer();
//xml文件保存到sd卡中名字为"sms.xml"
File file= new File(Environment.getExternalStora geDirectory(), “sms.xml”);
FileOutputStream fos;
try {
fos = new FileOutputStream(file);
xmlSerializer.setOutput(fos, “utf-8”);
xmlSerializer.startDocument( “utf-8”, true);
xmlSerializer.startTag( null, “smss”);
xmlSerializer.startTag( null, “count”);
xmlSerializer.text( smsList.size()+ “”);
xmlSerializer.endTag( null, “count”);
for(SmsData smsData: smsList){
xmlSerializer.startTag( null, “sms”);
xmlSerializer.startTag( null, “_id”);
xmlSerializer.text(smsData.get_id()+ “”);
xmlSerializer.endTag( null, “_id”);
xmlSerializer.startTag( null, “type”);
xmlSerializer.text(smsData.getType()+ “”);
xmlSerializer.endTag( null, “type”);
xmlSerializer.startTag( null, “address”); xmlSerializer.text(smsData.getAddress()+ “”);
xmlSerializer.endTag( null, “address”);
xmlSerializer.startTag( null, “body”);
xmlSerializer.text(smsData.getBody()+ “”);
xmlSerializer.endTag( null, “body”);
xmlSerializer.startTag( null, “date”);
xmlSerializer.text(smsData.getDate()+ “”);
xmlSerializer.endTag( null, “date”);
xmlSerializer.endTag( null, “sms”);
}
xmlSerializer.endTag( null, “smss”);
xmlSerializer.endDocument();
fos.flush();
fos.close();
Toast. makeText( mContext, “备份完成”, Toast.LENGTH_SHORT ).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
通过调用以上方法就将短信以xml的形式保存到了本地。
运行这个方法后可以在sd卡中看到sms.xml文件,将其导出来可以看到它的格式如下:

注:实际上xml文件中它是一行,这里为了让大家更清楚的看清结构,手动将其改成上述格式了。
保存到本地后,工作就剩下最后一步了,那就是将这个xml文件反序列化,并将其中的数据一条一条插入到短信的数据库中:
//将指定路径的xml文件中的数据插入到短信数据库中
public void restoreSms(String path) {
File file = new File(path);
//得到一个解析 xml的对象
XmlPullParser parser = Xml.newPullParser();
try {
fis = new FileInputStream(file);
parser.setInput( fis, “utf-8”);
ContentValues values = null;
int type = parser.getEventType();
while (type != XmlPullParser. END_DOCUMENT) {
switch (type) {
case XmlPullParser.START_TAG:
if (“count”.equals(parser.getName())) {
} else if (“sms”.equals( parser.getName())) {
values = new ContentValues();
} else if (“type” .equals( parser.getName())) {
values.put( “type”, parser.nextText());
} else if (“address” .equals( parser.getName())) {
values.put( “address”, parser.nextText());
} else if (“body” .equals( parser.getName())) {
values.put( “body”, parser.nextText());
} else if (“date” .equals( parser.getName())) {
values.put( “date”, parser.nextText());
}
break;
case XmlPullParser. END_TAG:
if ( “sms”.equals(parser.getName())) {
// 如果节点是 sms
Uri uri = Uri.parse( “content://sms/”);
ContentResolver resolver = mContext.getContentResolver();
resolver.insert(uri, values);
System. out.println( “插入成功” );
values = null;
}
break;
}
type=parser.next();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到,在上述方法中判断xml的END_TAG是不是"sms",如果是的话说明一条短信的"type"、“address”、“body”、"date"这些信息已经拿到,然后就根据Uri将这条数据插入到数据库中,就完成一条短信的恢复,待读到 END_DOCUMENT说明xml文件已经读完,此时所备份的短信就全部恢复了。

3.举例—联系人的读写
在Android中,联系人的操作都是通过一个统一的途径来读写数据的,打开
/data/data/com.android.providers.contacts,可以看到联系人的数据源:

导出这个文件,用专业的工具软件打开看一下表结构。
对这个SQLite类型的数据源进行封装后,联系人就以ContentProvider的形式为其他应用进程提供联系人的读写服务,我们就可以操作自己的联系人信息了。
先添加两个联系人到数据源中,如图所示:



每个联系人都有两个电话号码和两个邮箱账号,分别为家庭座机号码、移动手机号码、家庭邮箱账号和工作邮箱账号。当然在添加联系人时有很多其他信息,我们这里没有填写,只选择了最常用的电话和邮箱,主要是方便演示这个过程。
在演示代码之前,需要了解一下android.provider.ContactsContract这个类(注:在较早的版本中是android.provider.Contacts这个类,不过现在已被废弃,不建议使用),它定义了各种联系人相关的URI和每一种类型信息的属性信息:

下面通过一个项目,来演示一下联系人操作的具体过程。
新建一个名为provider的项目,创建一个名为ContactsReadTest的测试用例,如下:
public class ContactsReadTest extends AndroidTestCase {
//[content://com.android.contacts/contacts]
private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
//[content://com.android.contacts/data/phones]
private static final Uri PHONES_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
//[content://com.android.contacts/data/emails]
private static final Uri EMAIL_URI = ContactsContract.CommonDataKinds.Email.CONTENT_URI;

private static final String _ID = ContactsContract.Contacts._ID;
private static final String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
private static final String HAS_PHONE_NUMBER = ContactsContract. Contacts.HAS_PHONE_NUMBER;
private static final String CONTACT_ID = ContactsContract.Data.CONTACT_ID;
private static final String PHONE_NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private static final String EMAIL_DATA = ContactsContract.CommonDataKinds.Email.DATA;
private static final String EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE;

public void testReadContacts() {
ContentResolver resolver = getContext().getContentResolver();
Cursor c = resolver.query(CONTACTS_URI, null, null, null, null);
while (c.moveToNext()) {
int _id = c.getInt(c.getColumnIndex(_ID));
String displayName = c.getString(c.getColumnIndex(DISPLAY_NAME));
ArrayList< String> phones = new ArrayList< String>();
ArrayList< String> emails = new ArrayList< String>();
String selection = CONTACT_ID + “=” + _id;
//获取手机号
int hasPhoneNumber = c.getInt( c.getColumnIndex(HAS_PHONE_NUMBER));
if (hasPhoneNumber > 0) {
Cursor phc = resolver.query(PHONES_URI, null, selection, null, null);
while (phc.moveToNext()) {
String phoneNumber = phc.getString( phc.getColumnIndex(PHONE_NUMBER));
int phoneType = phc.getInt( phc.getColumnIndex(PHONE_TYPE));
phones.add(getPhoneTypeNameById( phoneType) + " : " + phoneNumber);
}
phc.close();
}
Log.i(TAG, "phones: " + phones);

//获取邮箱
Cursor emc = resolver.query(EMAIL_URI,null, selection, null, null);
while (emc.moveToNext()) {
String emailData = emc.getString( emc.getColumnIndex(EMAIL_DATA));
int emailType = emc.getInt( emc.getColumnIndex(EMAIL_TYPE));
emails.add(getEmailTypeNameById(
emailType) + " : " + emailData);
}
emc.close();

Log.i(TAG, "emails: " + emails);
}
c.close();
}

private String getPhoneTypeNameById(int typeId) {
switch (typeId) {
case ContactsContract.CommonDataKinds .Phone.TYPE_HOME:
return “home”;
case ContactsContract.CommonDataKinds .Phone.TYPE_MOBILE:
return “mobile”;
case ContactsContract.CommonDataKinds .Phone.TYPE_WORK:
return “work”;
default: return “none”;
}
}

private String getEmailTypeNameById(int typeId) {
switch (typeId) {
case ContactsContract.CommonDataKinds .Email.TYPE_HOME:
return “home”;
case ContactsContract.CommonDataKinds .Email.TYPE_WORK:
return “work”;
case ContactsContract.CommonDataKinds .Email.TYPE_OTHER:
return “other”;
default: return “none”;
}
}
}
为了使这个测试用例运行起来,我们需要在AndroidManifest.xml中配置一下测试设备的声明,它与< application>元素处于同一级别位置:

< instrumentation android:name="android.test.InstrumentationTestRunner"                   android:targetPackage="com.scott.provider"/>

然后再配置使用测试类库声明,它与< activity>元素处于同一级别位置:

    < uses-library android:name="android.test.runner"/>

最后,还有一个重要的声明需要配置,就是读取联系人权限,声明如下:

< uses-permission android:name=“android.permission.READ_CONTACTS”/>
经过以上准备工作,这个测试用例就可以运转起来了,运行testReadContacts()方法,打印结果:

联系人里的信息都准确无误的读取出来了。
如果在一个Activity里运行读取联系人的代码,不仅可以使用ContentResolver直接进行读取操作(即查询),还可以使用Activity提供的managedQuery方法方便的实现同样的效果,看一下这个方法的具体代码:
public final Cursor managedQuery(Uri uri,String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
if (c != null) {
startManagingCursor©;
}
return c;
}
其实它还是使用了ContentResolver进行查询操作,但是多了一步startManagingCursor的操作,它会根据Activity的生命周期对Cursor对象进行管理,避免了一些因Cursor是否释放引起的问题,所以非常方便,大大简化了我们的工作量。

接下来我们尝试将一个联系人信息添加到系统联系人的数据源中,实现对联系人的写入操作。
新建一个名为ContactsWriteTest的测试用例:
public class ContactsWriteTest extends AndroidTestCase {
//[content://com.android.contacts/raw_contacts]
private static final Uri RAW_CONTACTS_URI = ContactsContract.RawContacts.CONTENT_URI;
//[content://com.android.contacts/data]
private static final Uri DATA_URI = ContactsContract.Data.CONTENT_URI;

private static final String ACCOUNT_TYPE = ContactsContract.RawContacts.ACCOUNT_TYPE;
private static final String ACCOUNT_NAME = ContactsContract.RawContacts.ACCOUNT_NAME;
private static final String RAW_CONTACT_ID = ContactsContract.Data.RAW_CONTACT_ID;
private static final String MIMETYPE = ContactsContract.Data.MIMETYPE;
private static final String NAME_ITEM_TYPE = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE;
private static final String DISPLAY_NAME = ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME;
private static final String PHONE_ITEM_TYPE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
private static final String PHONE_NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private static final int PHONE_TYPE_HOME = ContactsContract.CommonDataKinds.Phone.TYPE_HOME;
private static final int PHONE_TYPE_MOBILE = ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE;
private static final String EMAIL_ITEM_TYPE = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
private static final String EMAIL_DATA = ContactsContract.CommonDataKinds.Email.DATA;
private static final String EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE;
private static final int EMAIL_TYPE_HOME = ContactsContract.CommonDataKinds.Email.TYPE_HOME;
private static final int EMAIL_TYPE_WORK = ContactsContract.CommonDataKinds.Email.TYPE_WORK;
private static final String AUTHORITY = ContactsContract.AUTHORITY;

public void testWriteContacts() throws Exception {
ArrayList< ContentProviderOperation> operations = new ArrayList< ContentProviderOperation>();
ContentProviderOperation operation = ContentProviderOperation.newInsert(RAW_CONTACTS_URI).withValue(ACCOUNT_TYPE, null)
.withValue(ACCOUNT_NAME, null)
.build();
operations.add(operation);

//添加联系人名称操作
operation = ContentProviderOperation. newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, NAME_ITEM_TYPE)
.withValue(DISPLAY_NAME, “Scott Liu”)
.build();
operations.add(operation);

//添加家庭座机号码
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, PHONE_ITEM_TYPE)
.withValue(PHONE_TYPE, PHONE_TYPE_HOME) .withValue(PHONE_NUMBER, “01034567890”)
.build();
operations.add(operation);

//添加移动手机号码
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, PHONE_ITEM_TYPE)
.withValue(PHONE_TYPE,PHONE_TYPE_MOBILE) .withValue(PHONE_NUMBER, “13034567890”)
.build();
operations.add(operation);

//添加家庭邮箱
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, EMAIL_ITEM_TYPE)
.withValue(EMAIL_TYPE, EMAIL_TYPE_HOME)
.withValue(EMAIL_DATA, “scott@android.com”)
.build();
operations.add(operation);

//添加工作邮箱
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, EMAIL_ITEM_TYPE)
.withValue(EMAIL_TYPE, EMAIL_TYPE_WORK)
.withValue(EMAIL_DATA, “scott@msapple.com”)
.build();
operations.add(operation);

ContentResolver resolver = getContext().getContentResolver();
//批量执行,返回执行结果集
ContentProviderResult[] results = resolver.applyBatch(AUTHORITY, operations);
for (ContentProviderResult result : results) {
Log.i(TAG, result.uri.toString());
}
}
}
在上面的代码中,把整个操作分为几个ContentProviderOperation操作,并将他们做批处理操作,也许注意到,从第二个操作开始,每一项都有一个withValueBackReference(RAW_CONTACT_ID, 0)步骤,它参照了第一项操作新添加的联系人的id,因为是批处理,我们插入数据前并不知道id的值,不过这个不用担心,在进行批处理插入数据时,它会重新引用新的id值,不会影响最终的结果。
当然,这个也不能忘了配置写入联系人的权限声明:

< uses-permission android:name=“android.permission.WRITE_CONTACTS” />
经过以上步骤之后,运行一下testWriteContacts()方法,看看联系人是否添加进去了:

Android 调用系统的ContentProvider相关推荐

  1. android 调用系统播放器

    今天,简单讲讲android如何调用手机自带的播放器. 昨天,从服务器下载一个AVI的视频,下载后需要进行播放,所以想调用系统自带的播放器.但是由于很少用到,所以自己当时不知道怎么写,于是在网上查找资 ...

  2. linux 短信功能,Android调用系统短信功能发送短信

    Android调用系统短信功能发送短信有两种方法: 第一种,设定发送的号码,和内容,界面没有联系人,群组组等按钮,如下图所示: 代码如下: Uri smsToUri = Uri.parse(" ...

  3. android 默认浏览器 视频播放 二维码,Android调用系统默认浏览器访问的方法

    一.启动android默认浏览器 这样子,android就可以调用起手机默认的浏览器访问. 二.指定相应的浏览器访问 1.指定android自带的浏览器访问 ( "com.android.b ...

  4. android系统应用程序,Android调用系统应用程序

    Android调用系统应用程序: 1.直接拨打电话: Intent callIntent = new Intent(Intent.ACTION_CALL, Uri .parse("tel:1 ...

  5. Android调用系统相机拍照并保存到指定位置

    Android调用系统相机拍照并保存到指定位置 @Click(R.id.btn_takePhoto)void onclick() {Intent intent = new Intent(MediaSt ...

  6. android安装自动打开网页,Android调用系统自带浏览器打开网页的实现方法

    Android调用系统自带浏览器打开网页的实现方法 在Android中可以调用自带的浏览器,或者指定一个浏览器来打开一个链接.只需要传入一个uri,可以是链接地址. 启动android默认浏览器 在A ...

  7. Android调用系统分享和指定app分享-微信朋友圈图文分享和qq分享

    Android调用系统分享和指定app分享-微信朋友圈图文分享和qq分享 标签: Android系统分享QQ分享朋友圈图文分享 2016-09-27 22:54 279人阅读 评论(0) 收藏 举报 ...

  8. Qt Android 调用系统文件管理

    1.了解 使用JNI扩展Qt应用 参考<Qt on Android核心编程>15章 源码https://download.csdn.net/download/dinosaurx/10149 ...

  9. Android 调用系统裁剪,适配11和12及手机

    前言:发现Android 调用系统裁剪时,在各Android 版本出现各种问题,踩了一堆坑,做一下总结,免得以后忘记 后面会有完整裁剪代码 1,一加手机报错 intent.putExtra(" ...

最新文章

  1. no no no.不要使用kill -9.
  2. Android中的音乐播放
  3. 单片机常用的几种通信协议
  4. Word 2007怎样生成目录
  5. poj 匈牙利二分匹配算法2239 Selecting Courses
  6. Android系统如何录制屏幕(录制成mp4格式)
  7. 计算机导论以python为舟_计算机科学导论
  8. C4D OCtane渲染器大师之路笔记(四):使用OC灯光
  9. 实现统计二叉树叶子节点个数的算法
  10. windows10安装双系统后,删除linux,开机进入grub怎么办
  11. linux音乐服务器mpd,Arch Linux下使用Mpd+Mpc
  12. ☀️光天化日学C语言☀️(01)- 第一个C语言程序 | 万丈高楼平地起
  13. CTFshow新春欢乐赛--web6--反序列化字符串逃逸
  14. interpro 数据库
  15. PLMN SPN运营商名称显示来源
  16. 使用 Docker 安装 Zabbix,并配置自定义监控项
  17. Python使用asyncio+aiohttp异步爬取猫眼电影专业版
  18. 一起来写个酷炫的水波纹浪啊浪界面
  19. 计算机毕业设计ssm高校心理健康咨询平台vknhv系统+程序+源码+lw+远程部署
  20. 看庭前花开花落,荣辱不惊,望天上…

热门文章

  1. 计算机科技英语文章及翻译,关于电脑的英语作文带翻译
  2. 计算机用老毛桃u盘备份系统,使用U盘备份系统
  3. 贝迪特掌静脉识别,比刷脸更安全的刷手技术
  4. python打字_盲法介绍及python盲打练习系统
  5. Android Studio主题设置
  6. APISpace 全球快递物流地图轨迹查询API
  7. 计算机实验word42,Word2010计算机实验报告--.docx
  8. 入手评测 索尼A7R4A怎么样 值得入手吗
  9. C国演义 [第四章]
  10. Mysql 拼接多个字段作为查询条件查询方法