有没有办法获取最近操作的联系人



最近想做一个功能,能够获取android最近修改的联系人,包括added/updated/deleted。虽然最后还是没有找到很好的办法,但顺便学习了下Android contacts content provider机制。

Android contacts content provider是一个系统级别的content provider。content provider提供了一种机制,使得一个应用可以开放出接口供其他应用跨进程调用。而contact content provider则是系统提供的。其调用者(content resolver)可以通过该provider获取通讯簿数据。

在Android系统中,通讯簿是通过三层表结构来存储数据的,从上到下分别是Contact,RawContact和Data。

先不管Android自己的实现。如果让我们设计数据库来保存通讯簿,会怎么做呢?一种做法是只有一个表,其中包括ID,Name,Phone,Email,Address等项。其问题在于一个用户可以有多个电话,多个邮件地址。关系型数据库处理这种一对多的关系的方式就是分成两个表,第一个表里只有ID和Name;第二个表里每行对应着一个联系方式,例如第一行是Phone1,第二行Phone2,第三行Email1,...。对于每一行,需要有一个指向第一表的索引。所以第二张表一般包含下面几列:ID,ContactID(作为外键),type(类型,例如是邮件还是电话),data(具体的电话号码,邮件地址等)。

在Android contact content provider中,RawContact就是第一张表,Data就是第二张表。那为何还有Contact表呢?

我们先做个实验。现在有一个干净的系统,没有设置帐户,也没有添加过联系人;另外有一个干净的gmail帐号,example1@gmail.com。此时我们点击拨号界面里的新加联系人,系统会提示“本地保存”还是“添加帐户”,这时候如果选择本地保存,那该联系人只是保存的机器本地的。假设联系人为张三。然后我们添加example1@gmail.com为系统帐户。现在系统会进行同步。如果通过web登录gmail,会发现张三已经在gmail的联系人中了。如果在web的gmail中添加李四,那李四也会被同步的手机上。如果从系统移除了example1@gmail.com,那张三和李四就都会从通讯簿中删除。如果在加上example1@gmail.com,那张三和李四就又回来了。

这个实验说明如果有系统帐号,则本地的联系人会和服务端的联系人保持一致,互相更新。一旦有了系统帐号,所有的联系人都会有一个源(source)。对于上面的例子,张三和李四的源就是example1@gmail.com。在没有系统帐号是添加的用户(张三),其开始时没有源的,但一旦添加了系统帐号,张三的源就设置为第一个添加的系统帐号。当系统帐号被移除时,表示contacts的源被移除,所以以该帐号为源的contacts都会被移除。

Android系统可以设置多个系统帐号,所以contacts可以支持多个源。假设现在已经添加了example1@gmail.com,现在我又添加了example2@gmail.com。现在系统中就有两个sources。此时再添加contact,则可以选择是添加到example1@gmail.com还是example2@gmail.com中。每个源对应的contacts独立跟服务器进行同步。

现在问题来了,如果我在example2@gmail.com中也添加了张三。而此张三的电话跟example1@中的电话不同。一种处理方式是被当做完全不同的两个contact;另外一种方式是合并二者。Android采用了第二种方式。所以如果我此时在web上对example2@添加了张三,那同步之后,张三下面会有两个电话号码,分别是example1@和example2@下面添加的。这种归并从现象上看是通过联系人名称来进行的,所以如果重名用户,可能会发生不错误的合并。另外,一个比较诡异的现象是如果对example2@是在手机上添加张三,那联系人中会有两个张三;但之后如果删除两个账户,再添加回来,则会进行归并。估计此归并操作是在sync的时候做的。

归并后的联系人就是Contact表,为支持这种1对多关系,RawContact表中每项又有一个指向Contact表的ID的外键。

这三张表都存放在contacts2.db中,只有系统才能访问。为了让普通进程也能操作,系统提供了contact content provider开发访问接口,三者对应的URI分别是ContactsContract.Contact.CONTENT_URI,ContactsContract.RawContact.CONTENT_URI和ContactsContract.Data.CONTENT_URI。然后我们就可以通过content resolver访问和操作这些数据了。contact contect provider通过两个权限READ_CONTACTS和WRITE_CONTACTS来限制访问,需要的应用可以在manifest中设置。

具体的使用可以参考http://developer.android.com/guide/topics/providers/contacts-provider.html。本文前面也很多是参考它写的。

回到开始的问题,有没有办法获取最近操作的联系人呢?

我们可以注册ContentObserver来监听这Contact表对应的URI的变化。如果手动添加,删除或者修改contact,会触发;如果在web上修改然后同步下来,也会触发;如果删除或添加系统帐户同步下来,同样会触发。这都是我们需要的。同时,如果拨打了某个电话,则同样会触发,因为饿Contact有相关的列。还有其他可能导致触发的情况。

即使这些变化都是我们需要的,那此时我们只能query到所有的contact。

从API18开始,在Contact表中,新加了CONTACT_LAST_UPDATED_TIMESTAMP列,表示该contact被更新的时间。所以我们可以对其进行排序和过滤。我们可以记录每次变化触发的时间,那么当前时间和上次触发时间之间的contacts,就是最近变化的。通过这种方式,可以知道最近inserted/updated的contact。

对于deleted contact,也是从API18开始,新加了DeletedContacts表,其中记录了被删除的contact的日志,有两列:ID和timestamp。我们仍然可以对timestamp进行排序或过滤,找到此次和上次变化之间的contacts。但问题是这里只能拿到ID,而ID此时已经无法查到对应的contact了(因为已经删除了),除非自己保存下历史contacts。

所以很遗憾,还是没有找到合适的办法。

阅读更多
个人分类: android应用开发
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭