安卓高效开发:联系人数据存储与操作基本
2013-12-05 12:04
411 查看
前段时间项目中使用到了与联系人模块的相关操作,研究了一点皮毛,抽空整理了一下,把相关操作写成了个库项目,主要为了复用,由于有base项目做参考,没有使用数据库批量操作的方法,闲言少叙,进正题~
RawContact表
每行代表一个联系人,每个联系人都有唯一的rawContactId, 这个是联系人操作的主要API,如新添/删除一个联系人时,都是对该表进行操作,与这个联系人相关联的其它表信息,系统会自行建立/清除。
Contact表
类似于家族表,通常情况下每个联系人的rawContactId和ContactId相同,但遇到系统认为是同一家族的联系人时会合并,这样不同rawContactID的联系人可能会共享相同的contactID,本文不涉及这种情况。
MIME表
存储着大类的类型及对应ID,一个典型的MIME表如下:
[align=left] [/align]
这个表中存储的是联系人细节信息的类型,包括一个15个大类,其中通常会使用的大类类型如下:
可以看出,联系人中常用的大类(MIME)信息有14种,包括姓名、电话、电子邮件等,每个大类信息又包括很多子类,如电话(Phone)中有移动、家庭、工作等小类,这些信息都是保存在下面要讲到的Data表中。
Data表
每行代表联系人的一条信息,如移动电话、工作电话、家庭住址信息等,一个联系人在该表中可能有多行数据,这些数据都有id,谷歌建议保持这些id,也就是在编辑联系人使其内容变化的时候,使用update而不是delete/insert的操作进行更新。
对于Data表中的一行,是包含的大类信息还是小类信息呢,这个不同的大类有所不同,对于姓名、组织、头像等这样较为唯一的数据,Data表中通常使用一行存储整个大类,而对于其他如电话、电子邮件、地址等可能含有较多种类的,则Data表中每行存储一个小类信息。
下面简要分析下Data表的列构成及存储结构,Data表中的重要数据列如下表:
Data表中包含这些数据列,其中比较重要的列Data1/Data2/Data3、MIMETYPE、RAW_CONTACT_ID
RAW_CONTACT_ID:该行内容属于哪个联系人,一个联系人仅有一个rawContactID.
MIMETYPE : 决定该行存储的数据属于是哪个大类,是电话、电子邮件还是其他?
MIMETYPE决定的类型不同,DATA1~DATA15表示的值类型也就不同。以PHONE为例:
[align=left] [/align]
Data1 : 官方解释Data1列存储的是核心内容,就是比如电话类的号码、邮件类的邮件地址、 即时通讯类的号码等,也就是通常在编辑联系人时填入的内容。
Data2:存入的是子Type,以上图为例,MIME决定了该行是Phone大类数据,而Data2中则决定该行是Phone中哪个小类
的数据,比如是移动、家庭、还是其他,其它大类类似。
Data3:标签,表示的是当Data2定义不同的子类时,应该显示的标签内容,就是我们在联系人编辑模块上看到的标签字符串,系统已经定义好常用的,如果想使用自定义的标签字符串,需将Data2设定为BaseTypes.TYPE_CUSTOM,Data3的标签字符串才会被使用。
在Data表中的每一行数据,根据MIME标识的不同,不同的大类,DATA1~DATA15列有着不同的存储内容,我自己整理了一下,其中绿色标注的表示常用的数据项,
宽度不够,data7~data10如下
图中对于每种大类,data1~data15所表示的内容,注意:如前文所述,在Data表中,这些联系人的一个大类不一定只占据1行,像电话、邮件、邮寄地址等大类类可能含有很多小类,每个小类占据一行信息。而姓名和组织比较特殊,这种大类对于每个联系人都是固定的,只占据一行信息。
在每个表中都有哪些信息呢?可以查看下,
RawContact表:
[align=left] [/align]
当用户增加一个联系人时,需要向RawContact表中插入一行数据,得到的_id就是rawContactId,对于rawContact表的每次内容改动,系统都会自动在其它表建立关联信息,也就是说,对于添加和删除联系人这样的操作,上层应用仅需要对该表进行操作即可。
从上表看出,rawContactId是25,这个是我们进行数据库操作的基础,每增加一个联系人时,其rawContactId的值会第加,此时系统会在contact表中寻找,如果系统觉得新添加的rawContactId对应的联系人没有”老乡“,就会把新加联系人的contactId也第加存储,否则,两个不同的rawContactId会共用一个contactId,在联系人界面只显示一个人。
关于contactID和rawContactID的关系在此不详述,有兴趣的朋友可通过代码多次添加相同内容的联系人复现此类现象,联系人界面上的每个人,都有1个ContactId,当用户重复插入相同的数据组时,系统认为都属于这个家族的成员,因此会把部分大类信息如注释信息等叠加显示,但并不增加新的联系人,查看Contact表可以看到,每个Contact表的lookup内容都包含该家族所有的rawContactid,每一次的插入数据组都有一唯一的rawContactID与之对应。这种实现方式为系统默认,不在本文中进行讨论。
Contact表
[align=left] [/align]
[align=left] [/align]
宽度原因分成两个图,实际只有1条数据,需关注lookup项,通过contactId和lookup内容确定的Uri是是进行联系人查询的钥匙。
大头在...Data表,裁剪、拼凑后的Data表新鲜出炉
_id : Data表中每行数据的id
mimeType : 前文已述,每行数据的大类类型,数字含义请参与mime那张表。
raw_contact_id : 每个联系人的id,可以看出都是25,和上面两表吻合,表示这些数据是来自同一联系人。
data1 :放置核心数据,其实就是编辑内容。
data2 :子类型,具体数字含义,参阅每个ContactsContract.CommonDataKinds里的内部类。
data3 : label内容,系统会在data2为TYPE_CUTSOM(0)时,使用data3.
在进行任何操作前,别忘了在注册文件中的联系人读写权限加上。
联系人的删/查都包含两处意思,
1)删/查一个联系人的所有信息,主要操作RawContact表
2)删/查Data表中的某个或某些数据信息,主要操作Data表。
结合上述的数据库存储情况,直接上代码,见注释。
测试例按照顺序执行增加查询和删除菜单项,得到的log信息如下,可以看到rawContactId是27不是之前的25,是因为我删除了之前的重测了两次。
至于联系人更新主要是应用逻辑的问题,比如需要在添加前判断,如果此项数据存在,则使用更新,不存在则使用添加,具体的使用场景及逻辑判断,请结合前文联系人添加相关自行扩展。注:不需要再写更新联系人工具函数,只要在添加工具函数里加分支即可。
联系人数据存储的四张表:
RawContact表每行代表一个联系人,每个联系人都有唯一的rawContactId, 这个是联系人操作的主要API,如新添/删除一个联系人时,都是对该表进行操作,与这个联系人相关联的其它表信息,系统会自行建立/清除。
Contact表
类似于家族表,通常情况下每个联系人的rawContactId和ContactId相同,但遇到系统认为是同一家族的联系人时会合并,这样不同rawContactID的联系人可能会共享相同的contactID,本文不涉及这种情况。
MIME表
存储着大类的类型及对应ID,一个典型的MIME表如下:
[align=left] [/align]
这个表中存储的是联系人细节信息的类型,包括一个15个大类,其中通常会使用的大类类型如下:
可以看出,联系人中常用的大类(MIME)信息有14种,包括姓名、电话、电子邮件等,每个大类信息又包括很多子类,如电话(Phone)中有移动、家庭、工作等小类,这些信息都是保存在下面要讲到的Data表中。
Data表
每行代表联系人的一条信息,如移动电话、工作电话、家庭住址信息等,一个联系人在该表中可能有多行数据,这些数据都有id,谷歌建议保持这些id,也就是在编辑联系人使其内容变化的时候,使用update而不是delete/insert的操作进行更新。
对于Data表中的一行,是包含的大类信息还是小类信息呢,这个不同的大类有所不同,对于姓名、组织、头像等这样较为唯一的数据,Data表中通常使用一行存储整个大类,而对于其他如电话、电子邮件、地址等可能含有较多种类的,则Data表中每行存储一个小类信息。
下面简要分析下Data表的列构成及存储结构,Data表中的重要数据列如下表:
Data表中包含这些数据列,其中比较重要的列Data1/Data2/Data3、MIMETYPE、RAW_CONTACT_ID
RAW_CONTACT_ID:该行内容属于哪个联系人,一个联系人仅有一个rawContactID.
MIMETYPE : 决定该行存储的数据属于是哪个大类,是电话、电子邮件还是其他?
MIMETYPE决定的类型不同,DATA1~DATA15表示的值类型也就不同。以PHONE为例:
[align=left] [/align]
Data1 : 官方解释Data1列存储的是核心内容,就是比如电话类的号码、邮件类的邮件地址、 即时通讯类的号码等,也就是通常在编辑联系人时填入的内容。
Data2:存入的是子Type,以上图为例,MIME决定了该行是Phone大类数据,而Data2中则决定该行是Phone中哪个小类
的数据,比如是移动、家庭、还是其他,其它大类类似。
Data3:标签,表示的是当Data2定义不同的子类时,应该显示的标签内容,就是我们在联系人编辑模块上看到的标签字符串,系统已经定义好常用的,如果想使用自定义的标签字符串,需将Data2设定为BaseTypes.TYPE_CUSTOM,Data3的标签字符串才会被使用。
在Data表中的每一行数据,根据MIME标识的不同,不同的大类,DATA1~DATA15列有着不同的存储内容,我自己整理了一下,其中绿色标注的表示常用的数据项,
宽度不够,data7~data10如下
图中对于每种大类,data1~data15所表示的内容,注意:如前文所述,在Data表中,这些联系人的一个大类不一定只占据1行,像电话、邮件、邮寄地址等大类类可能含有很多小类,每个小类占据一行信息。而姓名和组织比较特殊,这种大类对于每个联系人都是固定的,只占据一行信息。
举例说明
增加联系人,比如对于如下的联系人信息
在每个表中都有哪些信息呢?可以查看下,
RawContact表:
[align=left] [/align]
当用户增加一个联系人时,需要向RawContact表中插入一行数据,得到的_id就是rawContactId,对于rawContact表的每次内容改动,系统都会自动在其它表建立关联信息,也就是说,对于添加和删除联系人这样的操作,上层应用仅需要对该表进行操作即可。
从上表看出,rawContactId是25,这个是我们进行数据库操作的基础,每增加一个联系人时,其rawContactId的值会第加,此时系统会在contact表中寻找,如果系统觉得新添加的rawContactId对应的联系人没有”老乡“,就会把新加联系人的contactId也第加存储,否则,两个不同的rawContactId会共用一个contactId,在联系人界面只显示一个人。
关于contactID和rawContactID的关系在此不详述,有兴趣的朋友可通过代码多次添加相同内容的联系人复现此类现象,联系人界面上的每个人,都有1个ContactId,当用户重复插入相同的数据组时,系统认为都属于这个家族的成员,因此会把部分大类信息如注释信息等叠加显示,但并不增加新的联系人,查看Contact表可以看到,每个Contact表的lookup内容都包含该家族所有的rawContactid,每一次的插入数据组都有一唯一的rawContactID与之对应。这种实现方式为系统默认,不在本文中进行讨论。
Contact表
[align=left] [/align]
[align=left] [/align]
宽度原因分成两个图,实际只有1条数据,需关注lookup项,通过contactId和lookup内容确定的Uri是是进行联系人查询的钥匙。
大头在...Data表,裁剪、拼凑后的Data表新鲜出炉
_id : Data表中每行数据的id
mimeType : 前文已述,每行数据的大类类型,数字含义请参与mime那张表。
raw_contact_id : 每个联系人的id,可以看出都是25,和上面两表吻合,表示这些数据是来自同一联系人。
data1 :放置核心数据,其实就是编辑内容。
data2 :子类型,具体数字含义,参阅每个ContactsContract.CommonDataKinds里的内部类。
data3 : label内容,系统会在data2为TYPE_CUTSOM(0)时,使用data3.
在进行任何操作前,别忘了在注册文件中的联系人读写权限加上。
添加联系人代码
/************************************添加联系人工具函数********************************/ public static final int INFO_TYPE_NONE = -1; public static final int HEADINFO_TYPE_STRUCTUREDNAME = INFO_TYPE_NONE + 1; public static final int HEADINFO_TYPE_ORGANIZATION = INFO_TYPE_NONE + 2; public static final int BODYINFO_TYPE_PHONE = INFO_TYPE_NONE + 3; public static final int BODYINFO_TYPE_EMAIL = INFO_TYPE_NONE + 4; public static final int BODYINFO_TYPE_NICKNAME = INFO_TYPE_NONE + 5; public static final int BODYINFO_TYPE_WEBSITE = INFO_TYPE_NONE + 6; public static final int BODYINFO_TYPE_EVENT = INFO_TYPE_NONE + 7; public static final int BODYINFO_TYPE_RELATION = INFO_TYPE_NONE + 8; public static final int BODYINFO_TYPE_SIPADDRESS = INFO_TYPE_NONE + 9; public static final int BODYINFO_TYPE_STRUCTUREDPOSTAL = INFO_TYPE_NONE + 10; public static final int BODYINFO_TYPE_NOTE = INFO_TYPE_NONE + 11; /** * Header信息包含StructuredName和Organization两个大类信息,这两个大类信息均只占据Data表中一行 * @param cr 客户内容解析器 * @param rawContactid 待插入的联系人id * @param content 待插入的StructuredName数据内容,需客户提供 * @param HeaderInfoType 待插入的大类信息类型:1 : StructuredName , 2:Organization * a)插入StructuredName,则对于Data表中的数据列状况如下(data1列中存的是DISPLAY_NAME,依次类推): * 1:DISPLAY_NAME 2:GIVEN_NAME 3:FAMILY_NAME * 4:PREFIX 5:MIDDLE_NAME 6:SUFFIX * 7:PHONETIC_GIVEN_NAME 8:PHONETIC_MIDDLE_NAME 9:PHONETIC_FAMILY_NAME * b)插入Organization,则对于Data表中数据列的状况如下: * 1:COMPANY 2: TYPE 3:LABEL * 4:TITLE 5:DEPARTMENT 6:JOB_DESCRIPTION * 7:SYMBOL 8:PHONETIC_NAME 9:OFFIC_LOCATION */ public static void importHeaderInfoToData(Context context,int rawContactid,HashMap<String, String> content,int HeaderInfoType) { if (context == null || rawContactid < 0 || content == null) { Log.e(TAG, "importHeaderInfoToData param check fail"); return; } ContentValues value = new ContentValues(); value.put(Data.RAW_CONTACT_ID, rawContactid); switch (HeaderInfoType) { case HEADINFO_TYPE_STRUCTUREDNAME: value.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); break; case HEADINFO_TYPE_ORGANIZATION: value.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); break; default: break; } /** * 将content中非空数据项导入CV中 */ for (int i = 1; i < 10; i++) { String keyName = "data" + i; if (content.containsKey(keyName)) { value.put(keyName, content.get(keyName)); } } Uri uri = null; ContentResolver cr = context.getContentResolver(); if (cr != null) { uri = cr.insert(android.provider.ContactsContract.Data.CONTENT_URI, value); } if (DEBUG) Log.d(TAG, "importHeaderInfoToData result = " + uri); value.clear(); } /** * CommonBod部分包括电话、电子邮件、昵称、网站、联系事件、关系、sip地址、地址、注释 * 在Data表中,这八个大类信息存储有着共同特点。 * data1 : 有效信息,如电话号码、邮件地址、即时通讯号等,就是事件用户输入的数据内容。 * data2 : 信息类型,如Phone大类包含很多小类,如移动、家庭、工作等,其它大类也是如此。 * data3 : 标签,当信息类型时用户自定义时,该项纪录自定义的信息类型,如用户自定义一个标签:亲人号码。 * 把对这八类信息的插入组合到一个方法中, */ public static void importCommonBodyToData(Context context,int rawContactid,int bodytype,String content,int type,String label){ if (context == null || rawContactid < 0 || bodytype < 0 || content == null || type < 0 || label == null) { Log.e(TAG, "importCommonBodyToData param check fail"); return; } ContentValues value = new ContentValues(); switch (bodytype) { case BODYINFO_TYPE_PHONE: value.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_EMAIL: value.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_NICKNAME: value.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_WEBSITE: value.put(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_EVENT: value.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_RELATION: value.put(Data.MIMETYPE, Relation.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_SIPADDRESS: value.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_STRUCTUREDPOSTAL: value.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); break; case BODYINFO_TYPE_NOTE: value.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); break; default: break; } value.put(Data.RAW_CONTACT_ID,rawContactid); value.put(Data.DATA1, content); //对于注释类型信息,无有效type值 if (bodytype != BODYINFO_TYPE_NOTE) { value.put(Data.DATA2, type); } //标签仅对于自定义的信息类型有效 if (type == BaseTypes.TYPE_CUSTOM && bodytype != BODYINFO_TYPE_NOTE) { value.put(Data.DATA3, label); } Uri uri = null; ContentResolver cr = context.getContentResolver(); if (cr != null) { uri = cr.insert(android.provider.ContactsContract.Data.CONTENT_URI, value); } if (DEBUG) Log.d(TAG, "importCommonBodyToData : bodytype is " + bodytype +" result = " + uri); value.clear(); } //头像。 public static void importPhotoToData(Context context,int rawContactid,int drawableId){ if (context == null || rawContactid < 0 || drawableId < 0) { Log.e(TAG, "importPhotoToData param check fail"); return; } Bitmap sourceBitmap = BitmapFactory.decodeResource(context.getResources(),drawableId); final ByteArrayOutputStream os = new ByteArrayOutputStream(); sourceBitmap.compress(Bitmap.CompressFormat.PNG, 100, os); byte[] avatar = os.toByteArray(); ContentValues value = new ContentValues(); value.put(Data.RAW_CONTACT_ID, rawContactid); value.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); value.put(Photo.PHOTO, avatar); Uri uri = null; ContentResolver cr = context.getContentResolver(); if (cr != null) { uri = cr.insert(ContactsContract.Data.CONTENT_URI,value); } if (DEBUG) Log.d(TAG, "importPhotoToData result = " + uri); value.clear(); } //即时通信 public static void importIMToData(Context context,int rawContactid,int protocol,String content,int type,String label){ if (context == null || rawContactid < 0 || protocol < 0 || content == null || type < 0 || label == null) { Log.e(TAG, "importIMToData param check fail"); return; } ContentValues value = new ContentValues(); value.put(Data.RAW_CONTACT_ID, rawContactid); value.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); value.put(Im.PROTOCOL, protocol); value.put(Im.DATA,content); value.put(Im.TYPE, type); if (type == Im.TYPE_CUSTOM) { value.put(Im.LABEL, label); } Uri uri = null; ContentResolver cr = context.getContentResolver(); if (cr != null) { uri = cr.insert(android.provider.ContactsContract.Data.CONTENT_URI, value); } if (DEBUG) Log.d(TAG, "importIMToData result = " + uri); value.clear(); } //组成员关系随情况扩展。
删除查询更新联系人
上述是联系人添加相关的工具类代码,关于删除和查询,由于实现过程类似,放到一起。联系人的删/查都包含两处意思,
1)删/查一个联系人的所有信息,主要操作RawContact表
2)删/查Data表中的某个或某些数据信息,主要操作Data表。
结合上述的数据库存储情况,直接上代码,见注释。
/************************************查询联系人工具函数********************************/ /** * 查询Contact表,获取当前所有联系人的游标集,Contact表中每一行代表一个联系人 * @param context 上下文 * @param uri 查询表的URI,默认是查询Contact表 * @param projection 查询结果列,考虑性能优化,谷歌建议只查询需要的列 * @param sortOrder 查询结果排序,默认是安装显示名升序排列 * @return 返回查询到的Contact游标集 */ public static Cursor QueryContactTable(Context context,Uri uri,String[] projection,String sortOrder) { Cursor result = null; Uri wrapUri = uri == null ? Contacts.CONTENT_URI : uri; String[] WrapProjection = projection == null ? new String[] { Contacts._ID, // 0 Contacts.DISPLAY_NAME, // 1 Contacts.STARRED, // 2 Contacts.TIMES_CONTACTED, // 3 Contacts.CONTACT_PRESENCE, // 4 Contacts.PHOTO_ID, // 5 Contacts.LOOKUP_KEY, // 6 Contacts.HAS_PHONE_NUMBER, // 7 Contacts.IN_VISIBLE_GROUP, // 8 } : projection; String WrapSortOrder = sortOrder == null ? Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC " : sortOrder; result = context.getContentResolver().query(wrapUri, WrapProjection, null, null,WrapSortOrder); return result; } /** * @param cursor 目标游标集 * @param position 查询在目标游标集里第position位置的数据行 * @param isCloseCursor 是否在查询完关闭游标 * @return 得到目标联系人的lookupUri,这个对于查询联系人信息非常关键。 */ public static Uri getContactUri(Cursor cursor,int position,boolean isCloseCursor) { if (null == cursor || cursor.getCount() <= 0 || cursor.getCount() <= position) { if (DEBUG) Log.e(TAG, "getContactUri fail"); return null; } cursor.moveToPosition(position); final long contactId = cursor.getLong(cursor.getColumnIndex(Contacts._ID)); if (DEBUG) Log.d(TAG, "getContactUri [" + position + "] contactId is " + contactId); final String lookupKey = cursor.getString(cursor .getColumnIndex(Contacts.LOOKUP_KEY)); if (DEBUG) Log.d(TAG, "getContactUri [" + position + "] lookupKey is " + lookupKey); if (null != cursor && isCloseCursor) { cursor.close(); } return Contacts.getLookupUri(contactId, lookupKey); } /** * 通过上文得到的lookupUri,查询Data表获取需要的细节信息。 * @param context 上下文 * @param lookupUri 具体联系人的lookupUri,由在Contact表中查询的contactId和lookup数据获得。 * @return 结果游标集 */ public static final Cursor QueryDataTable(Context context, Uri lookupUri) { if (context == null || lookupUri == null) { return null; } final List<String> segments = lookupUri.getPathSegments(); if (segments.size() != 4) { return null; } final long uriContactId = Long.parseLong(segments.get(3)); final String uriLookupKey = Uri.encode(segments.get(2)); final Uri dataUri = Uri.withAppendedPath( ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId), Contacts.Data.CONTENT_DIRECTORY); if (DEBUG) Log.d(TAG, "QueryDataTable : get dataUri is " + dataUri); Cursor cursor = context.getContentResolver().query(dataUri, new String[] { Contacts.Data._ID,Contacts.Data.RAW_CONTACT_ID,Contacts.Data.MIMETYPE, Contacts.Data.DATA1,Contacts.Data.DATA2,Contacts.Data.DATA3,Contacts.LOOKUP_KEY}, null, null, null); if (cursor.moveToFirst()) { String lookupKey = cursor.getString(cursor .getColumnIndex(Contacts.LOOKUP_KEY)); if (!lookupKey.equals(uriLookupKey)) { // ID and lookup key do not match cursor.close(); return null; } if (DEBUG) { Log.d(TAG, "QueryDataTable has " + cursor.getCount() + "'s item," + "DataTable is\n" + "_ID | RAW_CONTACT_ID | " + " MIMETYPE | DATA1 | DATA2 | DATA3 "); do { String lineContent = ""; for (int i = 0; i < 6; i++) { lineContent += (cursor.getString(i) + " | "); } Log.i(TAG, lineContent); } while (cursor.moveToNext()); } return cursor; } else { cursor.close(); return null; } } /************************************删除联系人工具函数********************************/ /** * 删除一个联系人的全部信息,依然是通过lookupUri * @param context * @param uri */ public static void deleteContactInAll(Context context,Uri uri) { if (context == null || uri == null) { if (DEBUG) Log.e(TAG, "deleteContactInAll fail"); return; } ContentResolver cr = context.getContentResolver(); int deletedNum = 0; if (cr != null) { deletedNum = context.getContentResolver().delete(uri, null, null); } if (DEBUG) Log.d(TAG, "deleteContactInAll uri is = " + uri + "deletedNum is " + deletedNum); } /** * 删除一个联系人的某些信息,主要针对Data表中的一些数据项。 * @param context * @param where * @param selectionArgs */ public static void deleteContactInData(Context context,String where,String[] selectionArgs) { if (context == null || where == null || selectionArgs == null) { if (DEBUG) Log.e(TAG, "deleteContactInOne fail"); return; } ContentResolver cr = context.getContentResolver(); int deletedNum = 0; if (cr != null) { deletedNum = cr.delete(android.provider.ContactsContract.Data.CONTENT_URI,where,selectionArgs); if (DEBUG) Log.d(TAG, "deleteContactInOne The number of deleted item : " + deletedNum); } } }
为了验证这些工具函数有效性,需要写个测试程序,如下:
package com.klp.androidklpcontactbase; import java.util.HashMap; import com.klp.contactbase.ContactUtils; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.Website; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.util.Log; import android.view.Menu; import android.view.MenuItem; public class TestActivity extends Activity { public static final String TAG = "Contact::TestActivity"; ContentResolver cr = null; int AddedRawContactid = -1; Context context; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cr = getContentResolver(); } private void addContactTest() { ContentValues values = new ContentValues(); Uri rawContentUri = cr.insert(RawContacts.CONTENT_URI, values); AddedRawContactid = (int) ContentUris.parseId(rawContentUri); Log.i(TAG, "addContactTest : AddedRawContactid is " + AddedRawContactid); //Header ContactUtils.importHeaderInfoToData(getBaseContext(), AddedRawContactid, prepareNameInfo(), ContactUtils.HEADINFO_TYPE_STRUCTUREDNAME); ContactUtils.importHeaderInfoToData(getBaseContext(), AddedRawContactid, prepareOrgInfo(), ContactUtils.HEADINFO_TYPE_ORGANIZATION); //Phone ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_PHONE, "13911111111", Phone.TYPE_HOME, "label"); ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_PHONE, "13922222222", Phone.TYPE_MOBILE, "label"); //Email ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_EMAIL, "abc@sina.com", Email.TYPE_HOME, "label"); //NickName ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_NICKNAME, "nick_default", Nickname.TYPE_DEFAULT, "label"); //WebSite ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_WEBSITE, "www.csdn.net", Website.TYPE_BLOG, "csdn blog"); //Event ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_EVENT, "1949/10/1", Event.TYPE_BIRTHDAY, "label"); ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_EVENT, "2013/12/2", Event.TYPE_CUSTOM, "嫦娥3号发射"); //Relationship ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_RELATION, "football club player", Relation.TYPE_FRIEND, "label"); //SipAddr ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_SIPADDRESS, "sip:22444032@phonesystem.3cx.com", SipAddress.TYPE_HOME, "label"); //STRUCTUREDPOSTAL ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_STRUCTUREDPOSTAL, "ShangHai XH district donghai Street 13A", SipAddress.TYPE_HOME, "label"); //Note 仅仅在 Data1列中存储注释内容,1和label参数都是无效的。 ContactUtils.importCommonBodyToData(getApplicationContext(), AddedRawContactid, ContactUtils.BODYINFO_TYPE_NOTE, "随便写两句注释", 1, "label"); //Photo ContactUtils.importPhotoToData(getApplicationContext(), AddedRawContactid, R.drawable.ic_launcher); //IM ContactUtils.importIMToData(getApplicationContext(), AddedRawContactid, Im.PROTOCOL_QQ, "987654321", Im.TYPE_WORK, "label"); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub switch (item.getItemId()) { case R.id.action_add: addContactTest(); break; case R.id.action_query: queryContactTest(); break; case R.id.action_delete: // deleteContactInAllTest(); deleteContactInOneTest(); break; default: break; } return super.onOptionsItemSelected(item); } /** * 客户需要重写函数,根据实际情况填写非空项,内容为空的可以不填。 * @return */ public static HashMap<String, String> prepareNameInfo() { HashMap<String, String> content = new HashMap<String, String>(); content.put(StructuredName.DISPLAY_NAME, "John"); content.put(StructuredName.GIVEN_NAME, "Choi");//确保联系人首字母排序 /* content.put(StructuredName.FAMILY_NAME, "test_FAMILY_NAME"); content.put(StructuredName.MIDDLE_NAME, "test_MIDDLE_NAME"); content.put(StructuredName.PREFIX, "test_PREFIX"); content.put(StructuredName.SUFFIX, "test_SUFFIX"); content.put(StructuredName.PHONETIC_GIVEN_NAME, "test_PHONETIC_GIVEN_NAME"); content.put(StructuredName.PHONETIC_MIDDLE_NAME, "test_PHONETIC_MIDDLE_NAME"); content.put(StructuredName.PHONETIC_FAMILY_NAME, "test_PHONETIC_FAMILY_NAME"); */ return content; } /** * 客户需要重写函数,根据实际情况填写非空项,内容为空的可以不填。 * @return */ public static HashMap<String, String> prepareOrgInfo() { HashMap<String, String> content = new HashMap<String, String>(); content.put(Organization.COMPANY, "Microsoft"); /* content.put(Organization.TYPE, "test_TYPE"); content.put(Organization.LABEL, "test_LABEL");*/ content.put(Organization.TITLE, "Engineer"); /* content.put(Organization.DEPARTMENT, "test_DEPARTMENT"); content.put(Organization.JOB_DESCRIPTION, "test_JOB_DESCRIPTION"); content.put(Organization.SYMBOL, "test_SYMBOL"); content.put(Organization.PHONETIC_NAME, "test_PHONETIC_NAME"); content.put(Organization.OFFICE_LOCATION, "test_OFFICE_LOCATION");*/ return content; } private void queryContactTest(){ //搜索Contact表获取游标结果集 Cursor contactCursor = ContactUtils.QueryContactTable(getApplicationContext(), null, null, null); //从Contact表结果集中得到第1位置(下标为0)的URI Uri lookupurl = ContactUtils.getContactUri(contactCursor, 0, false); Log.i(TAG, "the first lookupurl is " + lookupurl); //查询data表中的数据 ContactUtils.QueryDataTable(getApplicationContext(), lookupurl); } private void deleteContactInAllTest(){ //搜索Contact表获取游标结果集 Cursor contactCursor = ContactUtils.QueryContactTable(getApplicationContext(), null, null, null); //从Contact表结果集中得到第1位置(下标为0)的URI,并将其删除。 ContactUtils.deleteContactInAll(getApplicationContext(), ContactUtils.getContactUri(contactCursor, 0, true)); } private void deleteContactInOneTest(){ ContactUtils.deleteContactInData(getApplicationContext(), Data.RAW_CONTACT_ID + "=?", new String[]{String.valueOf(AddedRawContactid)}); } }
测试例按照顺序执行增加查询和删除菜单项,得到的log信息如下,可以看到rawContactId是27不是之前的25,是因为我删除了之前的重测了两次。
至于联系人更新主要是应用逻辑的问题,比如需要在添加前判断,如果此项数据存在,则使用更新,不存在则使用添加,具体的使用场景及逻辑判断,请结合前文联系人添加相关自行扩展。注:不需要再写更新联系人工具函数,只要在添加工具函数里加分支即可。
小结
上面的代码都是工具类代码,自己写了个测试例文件已验证可用,联系人更新部分没有给出具体代码,是因为需要联系具体的使用逻辑,至于性能优化(数据库批量操作)、UI适配、业务逻辑,如需要请自行解决,这个可以作为库项目编成jar包,供以后在开发和联系人基本操作相关的应用时使用,友情分享~毫无技术支持~相关文章推荐
- 【Android 开发】:数据存储之 SQLite 数据库操作(三)
- Oracle11高性能开发--(6)高效数据插入与并行操作
- 14天学会安卓开发(第七天)数据存储之SharedPreferences与文件
- 基本数据类型,String、Integer等封装类,以及Class,三者在内存中是怎么存储的?对它们的操作机制又是什么样的?
- ios开发使用CoreData存储数据时,快速写下FetchRequest语句操作
- Delphi7学习基本数据格式和开发操作记录
- 安卓高效开发:数据库基本
- 数据结构(一)顺序表1:顺序存储的基本操作
- iOS开发:数据存储之plist文件操作
- 数据结构(二)链表1:链式存储的基本操作
- Android开发7:简单的数据存储(使用SharedPreferences)和文件操作
- 14天学会安卓开发(第七天)数据存储之SharedPreferences与文件
- Android软件开发之获取通讯录联系人信息 + android联系人信息的存储结构 + Android联系人读取操作笔记
- 安卓开发_数据存储技术_SharedPreferences类
- 数据存储—文件的基本操作
- Android软件开发之获取通讯录联系人信息 + android联系人信息的存储结构 + Android联系人读取操作笔记
- 安卓开发_数据存储技术_sqlite
- 最全面的Java字节byte操作,处理Java基本数据的转换及进制转换操作工具,流媒体及java底层开发项目常用工具类
- 安卓开发_数据存储技术_内部存储
- [置顶] Android开发之数据存储——SharedPreferences基础知识详解,饿补学会基本知识,开发者必会它的用法。