很多事情,絕大多數(shù)人都會在開始的時候滿懷熱情,而能堅持到底的卻是寥寥無幾。對待自己的目標(biāo),虎頭蛇尾絕不可取,半途而廢只會一無所成,我們必須持之以恒的做下去,堅持到底才能摘取勝利的果實。最近也忙了起來,忙著給自己充電,深知這項任務(wù)的艱巨,不是一天兩天的事,所以也借用這句警言來告誡自己,堅持不懈的走下去。
今天我們來講解一下如何利用ContentProvider機制讀寫聯(lián)系人信息。
在Android中,ContentProvider是一種數(shù)據(jù)包裝器,適合在不同進程間實現(xiàn)信息的共享。例如,在Android中SQLite數(shù)據(jù)庫是一個典型的數(shù)據(jù)源,我們可以把它封裝到ContentProvider中,這樣就可以很好的為其他應(yīng)用提供信息共享服務(wù)。其他應(yīng)用在訪問ContentProvider時,可以使用一組類似REST的URI的方式進行數(shù)據(jù)操作,大大簡化了讀寫信息的復(fù)雜度。例如,如果要從封裝圖書數(shù)據(jù)庫的ContentProvider獲取一組圖書,需要使用類似以下形式的URI:
content://com.scott.book.BookProvider/books
而要從圖書數(shù)據(jù)庫中獲取指定圖書(比如23號圖書),需要使用類似以下形式的URI:
content://com.scott.book.BookProvider/books/23
注:ContentProvider是一個抽象類,定義了一系列操作數(shù)據(jù)的方法模板,BookProvider需要實現(xiàn)這些方法,實現(xiàn)圖書信息的各種操作。
那么,現(xiàn)在知道了具體的URI之后,我們又如何操作進而取得數(shù)據(jù)呢?
此時,我們就要了解ContentResolver這個類,它跟ContentProvider是對應(yīng)的關(guān)系,我們正是通過它來與ContentProvider進行數(shù)據(jù)交換的。android.content.Context類為我們定義了getContentResolver()方法,用于獲取一個ContentResolver對象,如果我們在運行期可以通過getContext()獲取當(dāng)前Context實例對象,就可以通過這個實例對象所提供的getContentResolver()方法獲取到ContentResolver類型的實例對象,進而可以操作對應(yīng)的數(shù)據(jù)。
下面我們就通過聯(lián)系人實例對這種機制進行演示。
在Android中,聯(lián)系人的操作都是通過一個統(tǒng)一的途徑來讀寫數(shù)據(jù)的,我們打開/data/data/com.android.providers.contacts可以看到聯(lián)系人的數(shù)據(jù)源:
有興趣的朋友可以導(dǎo)出這個文件,用專業(yè)的工具軟件打開看一下表結(jié)構(gòu)。
對這個SQLite類型的數(shù)據(jù)源的封裝后,聯(lián)系人就以ContentProvider的形式為其他應(yīng)用進程提供聯(lián)系人的讀寫服務(wù),我們就可以順利成章的操作自己的聯(lián)系人信息了。
為了方便測試,我們先添加兩個聯(lián)系人到數(shù)據(jù)源中,如圖所示:
我們看到,每個聯(lián)系人都有兩個電話號碼和兩個郵箱賬號,分別為家庭座機號碼、移動手機號碼、家庭郵箱賬號和工作郵箱賬號。當(dāng)然在添加聯(lián)系人時有很多其他信息,我們這里都沒有填寫,只選擇了最常用的電話和郵箱,主要是方便演示這個過程。
在演示代碼之前,我們需要了解一下android.provider.ContactsContract這個類(注:在較早的版本中是android.provider.Contacts這個類,不過現(xiàn)在已被廢棄,不建議使用),它定義了各種聯(lián)系人相關(guān)的URI和每一種類型信息的屬性信息:
有興趣的朋友還可以讀一下源代碼,不過比較多,而且內(nèi)部類使用的特別多,讀起來有一定的困難,還是要做好心理準(zhǔn)備。
下面我們通過一個項目,來演示一下聯(lián)系人操作的具體過程。新建一個名為provider的項目,創(chuàng)建一個名為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";
-
-
- private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
-
- private static final Uri PHONES_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
-
- 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;
-
-
- 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中配置一下測試設(shè)備的聲明,它與<application>元素處于同一級別位置:
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.scott.provider"/>
然后再配置使用測試類庫聲明,它與<activity>元素處于同一級別位置:
-
- <uses-library android:name="android.test.runner"/>
最后,還有一個重要的聲明需要配置,就是讀取聯(lián)系人權(quán)限,聲明如下:
-
- <uses-permission android:name="android.permission.READ_CONTACTS"/>
經(jīng)過以上準(zhǔn)備工作,這個測試用例就可以運轉(zhuǎn)起來了,我們運行一下testReadContacts()方法,打印結(jié)果如下:
看來聯(lián)系人里的信息都被我們準(zhǔn)確無誤的讀取出來了。
如果我們在一個Activity里運行讀取聯(lián)系人的代碼,不僅可以使用ContentResolver直接進行讀取操作(即查詢),還可以使用Activity提供的managedQuery方法方便的實現(xiàn)同樣的效果,我們來看一下這個方法的具體代碼:
- 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;
- }
我們發(fā)現(xiàn),其實它還是使用了ContentResolver進行查詢操作,但是多了一步startManagingCursor的操作,它會根據(jù)Activity的生命周期對Cursor對象進行管理,避免了一些因Cursor是否釋放引起的問題,所以非常方便,大大簡化了我們的工作量。
接下來我們將要嘗試將一個聯(lián)系人信息添加到系統(tǒng)聯(lián)系人的數(shù)據(jù)源中,實現(xiàn)對聯(lián)系人的寫入操作。我們新建一個名為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";
-
-
- private static final Uri RAW_CONTACTS_URI = ContactsContract.RawContacts.CONTENT_URI;
-
- 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)步驟,它參照了第一項操作新添加的聯(lián)系人的id,因為是批處理,我們插入數(shù)據(jù)前并不知道id的值,不過這個不用擔(dān)心,在進行批處理插入數(shù)據(jù)時,它會重新引用新的id值,不會影響最終的結(jié)果。
當(dāng)然,這個也不能忘了配置寫入聯(lián)系人的權(quán)限聲明:
-
- <uses-permission android:name="android.permission.WRITE_CONTACTS" />
經(jīng)過以上步驟之后,我們運行一下testWriteContacts()方法,看看聯(lián)系人是否添加進去了: