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

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

今天我们来讲解一下如何利用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的测试用例,如下:

[java] view plaincopyprint?
  1. package com.scott.provider;
  2. import java.util.ArrayList;
  3. import android.content.ContentResolver;
  4. import android.database.Cursor;
  5. import android.net.Uri;
  6. import android.provider.ContactsContract;
  7. import android.test.AndroidTestCase;
  8. import android.util.Log;
  9. public class ContactsReadTest extends AndroidTestCase {
  10. private static final String TAG = "ContactsReadTest";
  11. //[content://com.android.contacts/contacts]
  12. private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
  13. //[content://com.android.contacts/data/phones]
  14. private static final Uri PHONES_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
  15. //[content://com.android.contacts/data/emails]
  16. private static final Uri EMAIL_URI = ContactsContract.CommonDataKinds.Email.CONTENT_URI;
  17. private static final String _ID = ContactsContract.Contacts._ID;
  18. private static final String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
  19. private static final String HAS_PHONE_NUMBER = ContactsContract.Contacts.HAS_PHONE_NUMBER;
  20. private static final String CONTACT_ID = ContactsContract.Data.CONTACT_ID;
  21. private static final String PHONE_NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
  22. private static final String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
  23. private static final String EMAIL_DATA = ContactsContract.CommonDataKinds.Email.DATA;
  24. private static final String EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE;
  25. public void testReadContacts() {
  26. ContentResolver resolver = getContext().getContentResolver();
  27. Cursor c = resolver.query(CONTACTS_URI, null, null, null, null);
  28. while (c.moveToNext()) {
  29. int _id = c.getInt(c.getColumnIndex(_ID));
  30. String displayName = c.getString(c.getColumnIndex(DISPLAY_NAME));
  31. Log.i(TAG, displayName);
  32. ArrayList<String> phones = new ArrayList<String>();
  33. ArrayList<String> emails = new ArrayList<String>();
  34. String selection = CONTACT_ID + "=" + _id;  //the 'where' clause
  35. //获取手机号
  36. int hasPhoneNumber = c.getInt(c.getColumnIndex(HAS_PHONE_NUMBER));
  37. if (hasPhoneNumber > 0) {
  38. Cursor  phc = resolver.query(PHONES_URI, null, selection, null, null);
  39. while (phc.moveToNext()) {
  40. String phoneNumber = phc.getString(phc.getColumnIndex(PHONE_NUMBER));
  41. int phoneType = phc.getInt(phc.getColumnIndex(PHONE_TYPE));
  42. phones.add(getPhoneTypeNameById(phoneType) + " : " + phoneNumber);
  43. }
  44. phc.close();
  45. }
  46. Log.i(TAG, "phones: " + phones);
  47. //获取邮箱
  48. Cursor emc = resolver.query(EMAIL_URI,null, selection, null, null);
  49. while (emc.moveToNext()) {
  50. String emailData = emc.getString(emc.getColumnIndex(EMAIL_DATA));
  51. int emailType = emc.getInt(emc.getColumnIndex(EMAIL_TYPE));
  52. emails.add(getEmailTypeNameById(emailType) + " : " + emailData);
  53. }
  54. emc.close();
  55. Log.i(TAG, "emails: " + emails);
  56. }
  57. c.close();
  58. }
  59. private String getPhoneTypeNameById(int typeId) {
  60. switch (typeId) {
  61. case ContactsContract.CommonDataKinds.Phone.TYPE_HOME: return "home";
  62. case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: return "mobile";
  63. case ContactsContract.CommonDataKinds.Phone.TYPE_WORK: return "work";
  64. default: return "none";
  65. }
  66. }
  67. private String getEmailTypeNameById(int typeId) {
  68. switch (typeId) {
  69. case ContactsContract.CommonDataKinds.Email.TYPE_HOME: return "home";
  70. case ContactsContract.CommonDataKinds.Email.TYPE_WORK: return "work";
  71. case ContactsContract.CommonDataKinds.Email.TYPE_OTHER: return "other";
  72. default: return "none";
  73. }
  74. }
  75. }
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>元素处于同一级别位置:

[html] view plaincopyprint?
  1. <!-- 配置测试设备的主类和目标包 -->
  2. <instrumentation android:name="android.test.InstrumentationTestRunner"
  3. android:targetPackage="com.scott.provider"/>
<!-- 配置测试设备的主类和目标包 --><instrumentation android:name="android.test.InstrumentationTestRunner"android:targetPackage="com.scott.provider"/>

然后再配置使用测试类库声明,它与<activity>元素处于同一级别位置:

[html] view plaincopyprint?
  1. <!-- 配置测试要使用的类库 -->
  2. <uses-library android:name="android.test.runner"/>
        <!-- 配置测试要使用的类库 --><uses-library android:name="android.test.runner"/>

最后,还有一个重要的声明需要配置,就是读取联系人权限,声明如下:

[html] view plaincopyprint?
  1. <!-- 读取联系人 -->
  2. <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <!-- 读取联系人 --><uses-permission android:name="android.permission.READ_CONTACTS"/>

经过以上准备工作,这个测试用例就可以运转起来了,我们运行一下testReadContacts()方法,打印结果如下:

看来联系人里的信息都被我们准确无误的读取出来了。

如果我们在一个Activity里运行读取联系人的代码,不仅可以使用ContentResolver直接进行读取操作(即查询),还可以使用Activity提供的managedQuery方法方便的实现同样的效果,我们来看一下这个方法的具体代码:

[java] view plaincopyprint?
  1. public final Cursor managedQuery(Uri uri,
  2. String[] projection,
  3. String selection,
  4. String[] selectionArgs,
  5. String sortOrder)
  6. {
  7. Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
  8. if (c != null) {
  9. startManagingCursor(c);
  10. }
  11. return c;
  12. }
    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的测试用例,如下:

[java] view plaincopyprint?
  1. package com.scott.provider;
  2. import java.util.ArrayList;
  3. import android.content.ContentProviderOperation;
  4. import android.content.ContentProviderResult;
  5. import android.content.ContentResolver;
  6. import android.net.Uri;
  7. import android.provider.ContactsContract;
  8. import android.test.AndroidTestCase;
  9. import android.util.Log;
  10. public class ContactsWriteTest extends AndroidTestCase {
  11. private static final String TAG = "ContactsWriteTest";
  12. //[content://com.android.contacts/raw_contacts]
  13. private static final Uri RAW_CONTACTS_URI = ContactsContract.RawContacts.CONTENT_URI;
  14. //[content://com.android.contacts/data]
  15. private static final Uri DATA_URI = ContactsContract.Data.CONTENT_URI;
  16. private static final String ACCOUNT_TYPE = ContactsContract.RawContacts.ACCOUNT_TYPE;
  17. private static final String ACCOUNT_NAME = ContactsContract.RawContacts.ACCOUNT_NAME;
  18. private static final String RAW_CONTACT_ID = ContactsContract.Data.RAW_CONTACT_ID;
  19. private static final String MIMETYPE = ContactsContract.Data.MIMETYPE;
  20. private static final String NAME_ITEM_TYPE = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE;
  21. private static final String DISPLAY_NAME = ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME;
  22. private static final String PHONE_ITEM_TYPE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
  23. private static final String PHONE_NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
  24. private static final String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
  25. private static final int PHONE_TYPE_HOME = ContactsContract.CommonDataKinds.Phone.TYPE_HOME;
  26. private static final int PHONE_TYPE_MOBILE = ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE;
  27. private static final String EMAIL_ITEM_TYPE = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
  28. private static final String EMAIL_DATA = ContactsContract.CommonDataKinds.Email.DATA;
  29. private static final String EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE;
  30. private static final int EMAIL_TYPE_HOME = ContactsContract.CommonDataKinds.Email.TYPE_HOME;
  31. private static final int EMAIL_TYPE_WORK = ContactsContract.CommonDataKinds.Email.TYPE_WORK;
  32. private static final String AUTHORITY = ContactsContract.AUTHORITY;
  33. public void testWriteContacts() throws Exception {
  34. ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
  35. ContentProviderOperation operation = ContentProviderOperation.newInsert(RAW_CONTACTS_URI)
  36. .withValue(ACCOUNT_TYPE, null)
  37. .withValue(ACCOUNT_NAME, null)
  38. .build();
  39. operations.add(operation);
  40. //添加联系人名称操作
  41. operation = ContentProviderOperation.newInsert(DATA_URI)
  42. .withValueBackReference(RAW_CONTACT_ID, 0)
  43. .withValue(MIMETYPE, NAME_ITEM_TYPE)
  44. .withValue(DISPLAY_NAME, "Scott Liu")
  45. .build();
  46. operations.add(operation);
  47. //添加家庭座机号码
  48. operation = ContentProviderOperation.newInsert(DATA_URI)
  49. .withValueBackReference(RAW_CONTACT_ID, 0)
  50. .withValue(MIMETYPE, PHONE_ITEM_TYPE)
  51. .withValue(PHONE_TYPE, PHONE_TYPE_HOME)
  52. .withValue(PHONE_NUMBER, "01034567890")
  53. .build();
  54. operations.add(operation);
  55. //添加移动手机号码
  56. operation = ContentProviderOperation.newInsert(DATA_URI)
  57. .withValueBackReference(RAW_CONTACT_ID, 0)
  58. .withValue(MIMETYPE, PHONE_ITEM_TYPE)
  59. .withValue(PHONE_TYPE, PHONE_TYPE_MOBILE)
  60. .withValue(PHONE_NUMBER, "13034567890")
  61. .build();
  62. operations.add(operation);
  63. //添加家庭邮箱
  64. operation = ContentProviderOperation.newInsert(DATA_URI)
  65. .withValueBackReference(RAW_CONTACT_ID, 0)
  66. .withValue(MIMETYPE, EMAIL_ITEM_TYPE)
  67. .withValue(EMAIL_TYPE, EMAIL_TYPE_HOME)
  68. .withValue(EMAIL_DATA, "scott@android.com")
  69. .build();
  70. operations.add(operation);
  71. //添加工作邮箱
  72. operation = ContentProviderOperation.newInsert(DATA_URI)
  73. .withValueBackReference(RAW_CONTACT_ID, 0)
  74. .withValue(MIMETYPE, EMAIL_ITEM_TYPE)
  75. .withValue(EMAIL_TYPE, EMAIL_TYPE_WORK)
  76. .withValue(EMAIL_DATA, "scott@msapple.com")
  77. .build();
  78. operations.add(operation);
  79. ContentResolver resolver = getContext().getContentResolver();
  80. //批量执行,返回执行结果集
  81. ContentProviderResult[] results = resolver.applyBatch(AUTHORITY, operations);
  82. for (ContentProviderResult result : results) {
  83. Log.i(TAG, result.uri.toString());
  84. }
  85. }
  86. }
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值,不会影响最终的结果。

当然,这个也不能忘了配置写入联系人的权限声明:

[html] view plaincopyprint?
  1. <!-- 写入联系人 -->
  2. <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <!-- 写入联系人 --><uses-permission android:name="android.permission.WRITE_CONTACTS" />

经过以上步骤之后,我们运行一下testWriteContacts()方法,看看联系人是否添加进去了:

<!-- Baidu Button BEGIN -->

基础总结篇之六:ContentProvider之读写联系人相关推荐

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

    古之成大事者,不惟有超世之才,亦有堅韌不拔之志.北宋.蘇軾<晁錯論> 我们的前辈中那些成就大事的人,不单单有过人的智慧和才能,也须有坚韧不拔的意志.试问没有坚韧的意志,如何写得出复杂的系统 ...

  2. View 5.1 重装上阵(4—基础架构篇)

    从底层架构方面来看,VIEW 5.1最大的改进在于引入了Content-Based Read Cache(CBRC) 技术,大大降低了部署桌面虚拟化所带来的IOPS风暴,同时大大降低了对于存储的压力和 ...

  3. SP 短信开发-基础知识篇

    SP 短信开发-基础知识篇 很土的话题,但是最近帮朋友做这个东西,所以写点东西出来给初学者参考. 一.准备资料 SP开发资料网站上有很多,但是主要是以下几个文档: 1.MISC1.6 SP订购通知接口 ...

  4. 计算机基础知识题库选择题,计算机基础知识篇选择题库

    计算机基础知识篇选择题库 (6页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 15.9 积分 计算机基础知识篇选择题库1.微型计算机的性能指标不包括___ ...

  5. Hadoop学习笔记—15.HBase框架学习(基础知识篇)

    Hadoop学习笔记-15.HBase框架学习(基础知识篇) HBase是Apache Hadoop的数据库,能够对大型数据提供随机.实时的读写访问.HBase的目标是存储并处理大型的数据.HBase ...

  6. 超详细的Java面试题总结(二)之Java基础知识篇

    系列文章: 超详细的Java面试题总结(一)之Java基本知识 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java面试题总 ...

  7. 鸟哥的LINUX私房菜 基础学习篇 读书笔记 -- 第零章 计算机概论 (一)

    鸟哥的LINUX私房菜 基础学习篇 读书笔记 -- 第零章 计算机概论 (一) 第零章 计算机概论 0.1 电脑:辅助人脑的好工具 0.1.1 计算机硬件五大组成部分 0.1.2 一切设计的起点: C ...

  8. 基金投资从入门到精通之一:基础知识篇

    第一篇 基础知识篇 第一节      认识基金 基金投资入门系列--基础知识 1.什么是证券投资基金? 通俗地说,证券投资基金是通过汇集众多投资者的资金,交给银行保管,由专业的基金管理公司负责投资于股 ...

  9. Web基础配置篇(七): Svn与Git的基本使用

    Web基础配置篇(七): Svn与Git的基本使用 一.概述 svn和git分别是目前使用最广泛的两种版本控制器. SVN(Subversion)是集中式管理的版本控制器,而Git是分布式管理的版本控 ...

最新文章

  1. 连续举办了十七年的韩国大学生智能车竞赛谢幕了
  2. Flutter开发之数据存储-1-SharedPreferences(32)
  3. Nginx静态资源压缩实战内容介绍
  4. SAP UI5 Gateway后台ETAG校验逻辑
  5. 全国计算机等级考试题库二级C操作题100套(第68套)
  6. MySQL备份恢复与日志
  7. spring cloud 搭建问题记录
  8. zk框架实现zul的js代码调用服务器java命令
  9. 未来教育题库 ***Java二级试题第27套***
  10. 生意场上,这些社交暗语不可不懂
  11. (一)机器学习入门与经典算法之KNN算法(第一期)
  12. ASIC 芯片 PVT
  13. 手把手带你入门 Spring Security
  14. SDUT—Python程序设计实验四(字符串)
  15. Jacob 操作 outlook 发邮件 遍历文件夹邮件 移动邮件
  16. x570主板怎么样 x570主板支持的cpu
  17. 树(2021.7.11晚)
  18. 用谷歌还是火狐?手机端还是另外选择其他浏览器吧
  19. 一封华为离职IT总监的告别信
  20. 吴佳怡最新街拍曝光  潮酷girl演绎初秋时尚

热门文章

  1. 菜鸟网工工作中对Linux系统的一点体会
  2. 《云计算:原理与范式》一3.10 企业对企业集成服务
  3. 《jQuery Mobile快速入门》—— 1.6 可主题化的设计
  4. jQuery基础部分
  5. PHP代码重用与函数编写
  6. iOS - Swift 与 Objective-C 互相操作
  7. 猫都能学会的Unity3D Shader入门指南(一)
  8. 著作权法(1分)18年5月考1分的概率大
  9. 国产SSD市场机遇与挑战并存
  10. TextView实现歌词同步《IT蓝豹》