Android 调用系统的ContentProvider

  • Post author:
  • Post category:其他


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()方法,看看联系人是否添加进去了:

在这里插入图片描述

在这里插入图片描述



版权声明:本文为zenmela2011原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。