安卓高效开发:联系人数据存储与操作基本

原创 2013年12月05日 12:04:15

         前段时间项目中使用到了与联系人模块的相关操作,研究了一点皮毛,抽空整理了一下,把相关操作写成了个库项目,主要为了复用,由于有base项目做参考,没有使用数据库批量操作的方法,闲言少叙,进正题~


联系人数据存储的四张表:

      RawContact表
      每行代表一个联系人,每个联系人都有唯一的rawContactId, 这个是联系人操作的主要API,如新添/删除一个联系人时,都是对该表进行操作,与这个联系人相关联的其它表信息,系统会自行建立/清除。
 
      Contact表
      类似于家族表,通常情况下每个联系人的rawContactId和ContactId相同,但遇到系统认为是同一家族的联系人时会合并,这样不同rawContactID的联系人可能会共享相同的contactID,本文不涉及这种情况。
 
      MIME表
     存储着大类的类型及对应ID,一个典型的MIME表如下:  

 
     这个表中存储的是联系人细节信息的类型,包括一个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为例:
       

 
      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表:

 
       当用户增加一个联系人时,需要向RawContact表中插入一行数据,得到的_id就是rawContactId,对于rawContact表的每次内容改动,系统都会自动在其它表建立关联信息,也就是说,对于添加和删除联系人这样的操作,上层应用仅需要对该表进行操作即可。
     从上表看出,rawContactId是25,这个是我们进行数据库操作的基础,每增加一个联系人时,其rawContactId的值会第加,此时系统会在contact表中寻找,如果系统觉得新添加的rawContactId对应的联系人没有”老乡“,就会把新加联系人的contactId也第加存储,否则,两个不同的rawContactId会共用一个contactId,在联系人界面只显示一个人。
     关于contactID和rawContactID的关系在此不详述,有兴趣的朋友可通过代码多次添加相同内容的联系人复现此类现象,联系人界面上的每个人,都有1个ContactId,当用户重复插入相同的数据组时,系统认为都属于这个家族的成员,因此会把部分大类信息如注释信息等叠加显示,但并不增加新的联系人,查看Contact表可以看到,每个Contact表的lookup内容都包含该家族所有的rawContactid,每一次的插入数据组都有一唯一的rawContactID与之对应。这种实现方式为系统默认,不在本文中进行讨论。   
    Contact表

 

 

 

 

      宽度原因分成两个图,实际只有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包,供以后在开发和联系人基本操作相关的应用时使用,友情分享~毫无技术支持~

安卓数据存储-使用LitePal操作数据库

简介:开源库-LitePal是一款开源的数据库的开源的Android数据库框架,它采用对象关系映射(OPM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以...

安卓开发之数据存储类

安卓开发过程中,数据存储应该算的上一个重要部分,其实由原本新建一个bean类,保存属性信息,set get方法,在自定义Application中声明,也是不错的方法,后来开发过程中,发现没有多少参数...

14天学会安卓开发(第七天)数据存储之SharedPreferences与文件

【原文:http://blog.csdn.net/corder_raine/article/details/8310130】 14天学会安卓开发   作者:神秘的N (英文名 ...

14天学会安卓开发(第七天)数据存储之SharedPreferences与文件

14天学会安卓开发   作者:神秘的N (英文名  corder_raine) 联系方式:369428455(反馈) 交流群:284552167(示例,原文档下载) 版权为作者...

安卓简单计算器含数据存储

  • 2015年12月29日 11:49
  • 1.64MB
  • 下载

iOS开发简单高效的数据存储

学习交流讨论请关注新浪微博:极客James在iOS开发过程中,不管是做什么应用,都会碰到数据保存的问题,你是用什么方法来持久保存数据的?这是在几乎每一次关于iOS技术的交流或讨论都会被提到的问题,而且...

android笔记-android基本操作和数据存储

一、测试相关:     测试方法:         白盒测试         黑盒测试             adb shell                 monkey 5000(点...
  • Cs1275
  • Cs1275
  • 2014年10月31日 22:52
  • 581

安卓笔记1之数据存储的方式

欢迎使用Markdown编辑器写博客本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和...

安卓数据存储方式之SQLite

1.知识图谱 数据库的增删查改的功能实现模板 连接数据库,首先得在java下新建一个.db包,在新建的db包下新建一个.db文件 DbHelper.java的代码如下: ...

安卓速记2--数据存储方式

Android提供以下四种存储方式: SharePreference SQLite File ContentProvider 如果要实现数据共享,正确的方式是使用ContentProvider ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:安卓高效开发:联系人数据存储与操作基本
举报原因:
原因补充:

(最多只允许输入30个字)