基礎總結篇之六: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()方法,看看聯繫人是否添加進去了:

相關文章
相關標籤/搜索