基础总结篇之六:ContentProvider之读写联系人

靡不有初,鮮克有終。《詩經》

很多事情,绝大多数人都会在开始的时候满怀热情,而能坚持到底的却是寥寥无几。对待自己的目标,虎头蛇尾绝不可取,半途而废只会一无所成,我们必须持之以恒的做下去,坚持到底才能摘取胜利的果实。最近也忙了起来,忙着给自己充电,深知这项任务的艰巨,不是一天两天的事,所以也借用这句警言来告诫自己,坚持不懈的走下去。

今天我们来讲解一下如何利用ContentProvider机制读写联系人信息。

在Android中,ContentProvider是一种数据包装器,适合在不同进程间实现信息的共享。例如,在Android中SQLite数据库是一个典型的数据源,我们可以把它封装到ContentProvider中,这样就可以很好的为其他应用提供信息共享服务。其他应用在访问ContentProvider时,可以使用一组类似REST的URI的方式进行数据操作,大大简化了读写信息的复杂度。例如,如果要从封装图书数据库的ContentProvider获取一组图书,需要使用类似以下形式的URI:

content://com.scott.book.BookProvider/books

而要从图书数据库中获取指定图书(比如23号图书),需要使用类似以下形式的URI:

content://com.scott.book.BookProvider/books/23

注:ContentProvider是一个抽象类,定义了一系列操作数据的方法模板,BookProvider需要实现这些方法,实现图书信息的各种操作。

那么,现在知道了具体的URI之后,我们又如何操作进而取得数据呢?

此时,我们就要了解ContentResolver这个类,它跟ContentProvider是对应的关系,我们正是通过它来与ContentProvider进行数据交换的。android.content.Context类为我们定义了getContentResolver()方法,用于获取一个ContentResolver对象,如果我们在运行期可以通过getContext()获取当前Context实例对象,就可以通过这个实例对象所提供的getContentResolver()方法获取到ContentResolver类型的实例对象,进而可以操作对应的数据。

下面我们就通过联系人实例对这种机制进行演示。

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

有兴趣的朋友可以导出这个文件,用专业的工具软件打开看一下表结构。

对这个SQLite类型的数据源的封装后,联系人就以ContentProvider的形式为其他应用进程提供联系人的读写服务,我们就可以顺利成章的操作自己的联系人信息了。

为了方便测试,我们先添加两个联系人到数据源中,如图所示:

我们看到,每个联系人都有两个电话号码和两个邮箱账号,分别为家庭座机号码、移动手机号码、家庭邮箱账号和工作邮箱账号。当然在添加联系人时有很多其他信息,我们这里都没有填写,只选择了最常用的电话和邮箱,主要是方便演示这个过程。

在演示代码之前,我们需要了解一下android.provider.ContactsContract这个类(注:在较早的版本中是android.provider.Contacts这个类,不过现在已被废弃,不建议使用),它定义了各种联系人相关的URI和每一种类型信息的属性信息:

有兴趣的朋友还可以读一下源代码,不过比较多,而且内部类使用的特别多,读起来有一定的困难,还是要做好心理准备。

下面我们通过一个项目,来演示一下联系人操作的具体过程。新建一个名为provider的项目,创建一个名为ContactsReadTest的测试用例,如下:

package com.scott.provider;
import java.util.ArrayList;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.test.AndroidTestCase;
import android.util.Log;
public class ContactsReadTest extends AndroidTestCase {

	private static final String TAG = "ContactsReadTest";

	//[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));

			Log.i(TAG, displayName);

			ArrayList<String> phones = new ArrayList<String>();
			ArrayList<String> emails = new ArrayList<String>();

			String selection = CONTACT_ID + "=" + _id;	//the ‘where‘ clause

			//获取手机号
			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(c);  
    }  
    return c;  
}

我们发现,其实它还是使用了ContentResolver进行查询操作,但是多了一步startManagingCursor的操作,它会根据Activity的生命周期对Cursor对象进行管理,避免了一些因Cursor是否释放引起的问题,所以非常方便,大大简化了我们的工作量。

接下来我们将要尝试将一个联系人信息添加到系统联系人的数据源中,实现对联系人的写入操作。我们新建一个名为ContactsWriteTest的测试用例,如下:

package com.scott.provider;
import java.util.ArrayList;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.ContactsContract;
import android.test.AndroidTestCase;
import android.util.Log;
public class ContactsWriteTest extends AndroidTestCase {
	private static final String TAG = "ContactsWriteTest";
	//[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, "[email protected]")
									.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, "[email protected]")
									.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()方法,看看联系人是否添加进去了:

时间: 2024-10-08 03:50:27

基础总结篇之六:ContentProvider之读写联系人的相关文章

基础总结篇之七:ContentProvider之读写短消息

古之成大事者,不惟有超世之才,亦有堅韌不拔之志.北宋.蘇軾<晁錯論> 我们的前辈中那些成就大事的人,不单单有过人的智慧和才能,也须有坚韧不拔的意志.试问没有坚韧的意志,如何写得出复杂的系统,如何创造出伟大的产品?作为程序员的我们,智慧和才能似乎不太欠缺,我们欠缺的也许是正是坚韧的意志,所以从今天起,锻炼自己的意志吧,在坚持理想的道路上,让这种意志给自己力量. 今天我们来讲一下如何利用ContentProvider读写短消息. 上次我们讲了如何通过ContentProvider机制读写联系人,通

【Android基础】内容提供者ContentProvider的使用详解

1.什么是ContentProvider 首先,ContentProvider(内容提供者)是android中的四大组件之一,但是在一般的开发中,可能使用的比较少. ContentProvider为不同的软件之间数据共享,提供统一的接口.也就是说,如果我们想让其他的应用使用我们自己程序内的数据,就可以使用ContentProvider定义一个对外开放的接口,从而使得其他的应用可以使用咱们应用的文件.数据库内存储的信息.当然,自己开发的应用需要给其他应用共享信息的需求可能比较少见,但是在Andro

【Linux】鸟哥的Linux私房菜基础学习篇整理(二)

1. dumpe2fs [-bh] devicename:查询superblock信息.参数:-b:列出保留为坏道的部分:-h:列出superblock的数据,不会列出其他的区段内容. 2. df [-ahikHTm] 目录或文件名:列出文件系统的整理磁盘使用量.参数:-a:列出所有的文件系统,包括系统特有的/proc等文件系统:-k:以KB的容量显示各文件系统:-m:以MB的容量显示各文件系统:-h:以人们易阅读的GB.MB.KB等格式自行显示:-H:以M=1000K替代M=1024K的进位方

【Linux】鸟哥的Linux私房菜基础学习篇整理(九)

1. quotacheck [-avugfM] [/mount_point]:扫描文件系统并创建Quota配置文件.参数:-a:扫描所有在/etc/mtab内,含有quota支持的文件系统,加上此参数后,不必写/mount_point:-u:针对用户扫描文件与目录的使用情况,会新建aquota.user:-g:针对用户组扫描文件与目录的使用情况,会新建aquota.group:-v:显示扫描过程的信息:-f:强制扫描文件系统,并写入新的quota配置文件(危险):-M:强制以读写的方式扫描文件系

【Linux】鸟哥的Linux私房菜基础学习篇整理(十一)

1. 直接将命令丢到后台中执行“&”,在命令最后加“&”.    将目前的工作丢到后台中暂停:[Ctrl]+z 2. jobs [-lrs]:查看目前的后台工作状态.参数:-l:除了列出job number与命令串外,同时列出PID号码:-r:列出正在后台run的工作:-s:列出正在后台stop的工作. 3. fg %jobnumber:把后台的工作拿到前台执行.参数:无参数:默认去除含+的工作:%jobnumber:jobnumber是工作号码,%可省略. 4. bg %jobnumbe

C# Xamarin移动开发基础进修篇

一.课程介绍 英文原文:C# is the best language for mobile app development. Anything you can do in Objective-C, Swift or Java, you can do in C#. 中文译意:C#是移动应用程序开发的最佳语言. 在Objective-C,Swift或Java中你可以做的任何事情,你都可以在C#中完成. 1).本次分享课程适合人群如下: 1. 热爱Xamarin跨平台移动开发. 2.进一步了解和学习

Linux 初探 (基础认知篇)

linux 初探 (基础认知篇) 什么是linux? Linux是开源的类Unix系统,单纯的术语Linux来说其实仅仅指由林纳斯.托瓦兹开发并于1991年发布的一款存在于内核空间的操作系统内核(kernel). 为什么会开发要开发内核呢? 要从应用程序是如何在计算机上跑起来谈起,早期的应用程序需要了解CPU指令集利用汇编等语言进行开发.CPU的常见指令集架构:x86.x64(早期称其为amd64)兼容x86.m68000(m68k).arm.power.power pc(ppc,桌面级powe

Linux新手入门书籍推荐 鸟哥的linux私房菜-基础学习篇

这本书写的不错.赞~\(≧▽≦)/~ 2017-02-24 下午,我开始在Linux下写第一个.c程序,在终端打印hello world.gcc 源代码文件之后,输出可执行文件,但是 当我输入文件名执行它的时候,却提示我 无法找到命令.于是我找百度,查资料,花了半个小时,终于找到解决方法了, 输入"./filename"即可.... 2017-02-25 我看<基础学习篇>这本书,在P158页下面的例题讲解中找到了昨天下午异常的解析.我就很是感慨,心想:要是早看这本书,半个

《nodejs+gulp+webpack基础实战篇》课程笔记(四)-- 实战演练

一.用gulp 构建前端页面(1)---静态构建 npm install gulp-template --save-dev 通过这个插件,我们可以像写后台模板(譬如PHP)一样写前端页面.我们首先学习一下写法. 现在我们创建一个新任务:创建一个裸的index.html文件,然后在body里面写上 ,我的年龄是:<%= age %> 下载好gulp-template,我们引用并配置 var gulp_tpl = require("gulp-template"); gp.tas