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

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

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

content://com.scott.book.BookProvider/books
数据库

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

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

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

那么,如今知道了具体的URI以后,咱们又如何操做进而取得数据呢?测试

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

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

在Android中,联系人的操做都是经过一个统一的途径来读写数据的,咱们打开/data/data/com.android.providers.contacts能够看到联系人的数据源:.net


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

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

相关文章
相关标签/搜索