天天看点

android最新联系人信息的存储结构

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

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

android最新联系人信息的存储结构

data:

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

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:

aggregationexceptions:

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

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类

其定义如下:

data类定义了content_uri及content_type,主要是继承了datacolumnswithjoins接口中的字段。datacolumnswithjoins定义如下:

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

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,并根据其查找的数据的类型就可以查到相应类型的信息。

以上代码根据联系人的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类中有如下定义

data1与data4为继承自datacolumns中的常量,在datacolumns中是这样定义的:

这样,当于们要查找email地址时,只需要通过contactscontract.commondatakinds.email.address引用,而不需要知道它是存储在data表中的data1列中。

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

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

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

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

 lookup key

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

然后用这个uri查询:

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

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

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

在网上看到的读取所有联系人姓名与电话的代码都是这样的:

如果有n个联系人且每个联系人都存有电话号码的话,就得查询n+1次。

在园子里看到一个帖子说可以通过

取得所有联系人的信息,我在android 4.0模拟器跟2.3.7的真机上测试都不成功。

联系人的各种类型的信息都存储在data表中,所以查询data表并限制其mimetype为phone.content_item_type即可以查到所有姓名与电话

上述代码可以查到所有联系人的姓名与电话,但是如果直接挨个输出的话会有问题,如果一个人存储了两个电话号码的话,在data表中会有两条记录,比如一个叫张三的人,存储了他两个电话:11111,22222。那么输出结果中会有两条关于张三的记录,并不会合并到一起,所以我想到先把cursor查询到的所有数据存储到map里,以display_name为键,以number组成的list为值,即

于是有了如下代码:

这样就可以解决一个姓名对应多个号码的问题,但还有问题,可能是两个联系人同名,但他们属于不同的联系人,在数据库中表现为有不同的contact_id,那么可以将上述代码修改一下,将projection参数处添加上contactscontract.commondatakinds.phone.contact_id,然后把map改为以contact_id为建,以display_name与number组成的list为值,把display_name统一存储为list的第一项。当然也可以定义一个类,包含姓名字段及电话号码组成的list字段,电话号码的list中的元素还可以是map,以号码的type为键。