联系人信息的存储结构:

从Android 2.0(API Level 5)开始,Android平台提供了一个改进的Contacts API,以适应一个联系人可以有多个帐户的需求,比如说手机通讯录和GMAIL通讯录,两个通讯录中的两条记录可以是同一个人。新的Contacts API主要是由ContactsContract及其相关的类来管理,旧的API(android.provider.Contacts)已不赞成使用,但为了兼容仍可以使用,只不过像以前一样,只能返回第一个帐户的信息。

在新的Contacts API中,联系人数据被放到三张表中:Contacts、RawContacts和Data。这样可以帮助系统更好地存储与管理一个联系人的多个帐户的信息。

Data:

Data表存储了联系人的详细信息,表中的每一行存储一个特定类型的信息,比如Email、Address或Phone。每一行通过一个mimetype_id的字段来表示该行存储的是什么类型的数据,该字段引用了mimetyps表,此表存储了常用的数据类型。Data表的字段主要有:

mimetype_id 表示该行存储的信息的类型
raw_contact_id 表示该行所属的RawContact
is_primary 多个data数据组成一个raw contact,该字段表示此data是否是其所属的raw contact的主data,即其display name会作为raw contact的display name
is_super_primary 该data是否是其所属的contact的主data,如果is_super_primary为1则is_primary一定为1
data1~data15 15个数据字段,对于不同类型的信息,表示不同的含义,ContactsContract.CommomDataKinds类中定义了与常用的数据类型相对应的一些类,这些类中分别定义了相应数据类型中这些字段表示的含义。一般data1表是主信息(如电话,Email地址等),data2表示副信息,data15表示Blob数据。
data_sync1~data_sync4 sync_adapter要用的字段(sync_adapter用于数据的同步,比如你手机中的Gmail帐户与Google服务器的同步)。
data_version 数据的版本,用于数据的同步。

RawContact:

RawContact表中的一行存储Data表中一些数据行的集合及一些其他的信息,表示一个联系人某一特定帐户的信息,比如Facebook或Exchange的一个联系人。

当插入一个raw contact或当一个raw contact所属的一个data改变时,系统会检查这个raw contact跟其他的raw contact是否可以匹配(比如如果两个raw contact的data包含相同的电话号码或名字),如果匹配他们就会被综合到一起,也就是说他们会属于同一个cantact,表现为在RawContact表中他们引用的cantact_id是一样的。

联系人姓名、组织、电话号码、Email或昵称的改变会引发raw contact的重新聚合。有两个方法控制聚合的行为Aggregaton Mode与ContactsContract.AggregationExceptions。

Aggregaton Mode:

RawContact表中有一个字段aggregation_mode,通过向特定raw contact行中插入这个字段可以修改系统对这个raw contact的聚合行为,其允许的值如下:
AGGREGATION_MODE_DEFAULT:正常模式,允许自动聚合;
AGGREGATION_MODE_DISABLE:不允许聚合;
AGGREGATION_MODE_SUSPENDED:当一个raw contact的aggregation mode修改为suspended时,如果其已是一个已聚合的contact的一部分,那么它仍会保持与原来聚合到一起的raw contact的关系,即使它已改变不再跟其他raw contact匹配。

AGGREGATIONEXCEPTIONS:

在数据库中存在一个表:agg_exceptions。通过字段raw_contact_id1、raw_contact_id2、mode存储两个raw contact聚合的方法,系统定义的聚合行为有3个:

TYPE_AUTOMATIC=0  由系统决定聚合行为,默认值。
TYPE_KEEP_SEPARATE=2  不聚合
TYPE_KEEP_TOGETHER=1  聚合

Contact:

Contact表中的一行表示一个联系人,它是RawContact表中的一行或多行的数据的组合,这些RawContact表中的行表示同一个人的不同的帐户信息。Contact中的数据由系统组合RawContact表中的数据自动生成。

不可以直接向这个表中插入数据,当一个raw contact被插入的时候,系统会首先查找Contact表看是否有记录跟插入的raw contact表示同一个人,如果找到了,则把找到的这个contact的_ID插入raw contact记录的CONTACT_ID字段,如果没有找到,则系统自动插入一个Contact记录并把它的_ID插入新插入的raw contact的CONTACT_ID列。

Contact表中只有TIMES_CONTACTED、LAST_TIME_CONTACTED、STARRED、CUSTOM_RINGTONE、SENE_TO_VOICEMAIL列可更改,这些列的更改会导致相应的raw contact被更改。

当删除Contact表中的记录时,会删除一个联系人的所有帐户的信息,也就是说,其对应的所有raw contacts也会被删除,各raw contact对应的data也就被删除了,sync adapter同步时也会删除服务器端的相应记录。

如果需要读取一个联系人的信息用CONTENT_LOOKUP_RUI代替CONTENT_URI(见后面);

如果需要通过电话号码查找一个联系人,用PhoneLookup.CONTENT_FIILTER_URI,这个URI为这个目的进行了优化;

如果需要通过部分名字的匹配查找,用CONTENT_FILTER_URI;

如果需要通过email,address等信息查找,查找表ContactsContract.Data,结果包含contact ID,名字...

android.provider.ontactsContract.Data类

其定义如下:

 public final static class Data implements DataColumnsWithJoins {
private Data() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/data";
//This flag is useful (currently) only for vCard exporter in Contacts app
public static final String FOR_EXPORT_ONLY = "for_export_only";
/*Build a CONTENT_LOOKUP_URI style Uri for the parent ContactsContract.Contacts entry of the given ContactsContract.Data entry.*/
public static Uri getContactLookupUri(ContentResolver resolver, Uri dataUri) {...}
}

Data类定义了CONTENT_URI及CONTENT_TYPE,主要是继承了DataColumnsWithJoins接口中的字段。DataColumnsWithJoins定义如下:

protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns,
RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns,
ContactStatusColumns {
}

该接口只是综合了一些接口,其中:

BaseColumns定义了_ID与_COUNT字段,ContentProvider查询中都要用到的字段。

DataColumns定义了与Data表中字段一一对应的常量

RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns表示RawContact与Contact中的一些字段,定义到此类中以方便访问。

由于联系人的信息都是按类别存储到了这个Data表中,所以我们要查找联系人的信息只需要查这个表。Data类中的Data.CONTACT_ID与Data.RAW_CONTACT_ID分别表示该表项对应的联系人在Contact与RawContract表中的ID,我们只需要知道某一个联系人的contactId或rawContractId,并根据其查找的数据的类型就可以查到相应类型的信息。

Cursor c = getContentResolver().query(Data.CONTENT_URI,
new String[] {Data._ID, Phone.NUMBER, Phone.TYPE, Phone.LABEL},
Data.CONTACT_ID + "=?" + " AND "
+ Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'",
new String[] {String.valueOf(contactId)}, null);

以上代码根据联系人的contractId,并通过设置其MIMETYPE为Phone.CONTENT_ITEM_TYPE查到了该联系人的电话号码,把Data.CONTACT_ID换为Data.RAW_CONTACT_ID即可查找相应rawContactId对应的电话号码。其中用到了Phone.CONTENT_ITEM_TYPE,这是什么呢?

CommonDataKinds类

在前面讲Data表的结构时讲到,Data的data1~data15字段用于存储各类型的数据信息,那么这15个字段分别表示什么信息呢?

前面提到了,Data表中有一个mimetype_id字段,通过这个字段关联mimetypes表表示该行代表的信息类型,因为Data表中的每一行可以表示如Phone或Address等不同类型的信息,所以对于不同类型的信息,data1~data15这15列表示不同的含义,如果要靠记忆记住这15列对于特定的类型分别表示什么意义自然不行,于是Google就预定义了一些类,每一个类对应一些预先定义好的数据类型,在每个类中定义了一些语义地、方便记忆的常量,用来对应这15个字段,比如在CommonDataKinds.Email类中有如下定义

public static final String ADDRESS = DATA1;
public static final String DISPLAY_NAME = DATA4;

DATA1与DATA4为继承自DataColumns中的常量,在DataColumns中是这样定义的:

public static final String DATA1 = "data1";
public static final String DATA4 = "data4";

这样,当于们要查找Email地址时,只需要通过ContactsContract.CommonDataKinds.Email.ADDRESS引用,而不需要知道它是存储在Data表中的data1列中。

回到上一个问题,上面查询电话号码的例子中用到了Phone.CONTENT_ITEM_TYPE,即是表示CommonDataKinds.Phone.CONTENT_ITEM_TYPE,在Phone类中有如下定义

public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone_v2";

vnd.android.cursor.item/phone_v2 就是表示Data表中相应的行存储的是电话号码的信息,通过相应类型(如Phone、Email)的CONTENT_ITEM_TYPE,我们就可以指定要查询的MIMETYPE,即要查询的数据类型。

其实在上面的例子中还有更简单的方法:

Cursor phone = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[] { CommonDataKinds.Phone.NUMBER }, CommonDataKinds.Phone.CONTACT_ID + " =? ",
new String[] { String.valueOf(contactId) }, null);

只需把query的第一个参数处传递想查找的数据类型的相应的类中的CONTENT_URI就可以了。

Lookup Key

在新的Contact API中,为contact引入了lookup key的概念,当你的程序需要保存对联系人的引用时,用lookup key而别用row id,lookup key是contacts表中的一列,当你有一个lookup key时,可以这样构造一个Uri:

Uri lookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);

然后用这个Uri查询:

Cursor c = getContentResolver().query(lookupUri, new String[]{Contacts.DISPLAY_NAME}, ...);
try {
c.moveToFirst();
String displayName = c.getString(0);
} finally {
c.close();
}

用lookup key 而不用row id的原因是因为row id容易改变,用户把原先两个联系人合并成一个或因为同步的问题都会导致row id的改变。lookup key是一个字符串,它由raw contact的标识连接组成。

使用row id会比使用lookup key效率高,你可以同时保存二者,然后联合二者生成一个lookup uri:

Uri lookupUri = getLookupUri(contactId, lookupKey)

当同时有row id跟lookup key时,系统会优先以row id查询,如果无查找结果或找到的结果跟lookup key不匹配,则再用lookup key查找。


初学android,连android代码都没写过几行,想学习一下Contact API,发现说2.0后API改了,2.0以前的机子基本上也被淘汰了吧,就只看一下2.0以后的API吧,在网上找一点有用的资料都没有,找来找去就那几个帖子,都可以说是SDK上的例子,本想找点参考的,最后发现还是只有SDK,看的晕头转行的,只有了一些最基本的理解,像跟IM有关的StatusColumns之类的还有Directory等就只粗略看了一眼就过去了。毕竟刚接触android,SDK的文档即使每一句话都能看懂也不太了解意思,不知道在哪用,先把看懂的记一下吧,免得以后又忘了。只是把SDK中分散到各页面的东西综合了一下。有错的方希望别误导了人。


更多相关文章

  1. Android:Logcat日志猫
  2. 匹配联系人采用的是7位还是11位匹配
  3. Android读取手机通讯录联系人到自己项目
  4. android 系统数据操作说明
  5. android安卓APP获取手机设备信息和手机号码的代码示例
  6. 关于 unity5.3.1 录制 animation 带有 rotation 信息打包 Androi
  7. Android开发 VideoView视频播放详解
  8. Android(安卓)ContentProvider query参数
  9. Android(安卓)程序崩溃后的处理

随机推荐

  1. Android应用程序进程启动过程的源代码分
  2. Android(安卓)AsyncTask完全解析
  3. Blog-06-《一周快速上手Kotlin For Andro
  4. Android屏幕完美适配最全攻略(最权威的官
  5. 【外刊IT评论】如何发布你的Android应用
  6. 谷歌Android操作系统有望主导移动市场
  7. android体系结构
  8. android对html支持接口总结
  9. GridView或ListView 中android:drawSelec
  10. Android主题定制及修改