contiki系统提供了一系列的时钟库,可以供contiki系统或者用户态的程序调用.
时钟库包括时钟到期检查.在调度时钟时低功耗的模块被唤醒,实时的任务调度.
定时器也可以让执行具体的事情过程中进入休眼状态.
contiki包抱一个时钟模块,但是有多个时钟模型:timer, stimer, ctimer, etimer, rtimer.不同的时钟有不同的作用.有的定时器运行时间长,但是间隔时间短,有的间隔时间长,但是运行时间短.有些能用于中断的上下文中(rtimer),但是有些不行.
定时器模块提供系统时钟,并且可以短时间的阻塞CPU.整个时钟库就是基于定时器来做的.
timer和stimer是提供了最简单的时钟操作,即检查时钟周期是否已经结束.应用程序需要从timer中读出状态,判断时钟是否过期.两种时钟最大的不同在于,tmiers是使用的系统时钟的ticks,而stimers是使用的秒,也就是stimers的一个时钟周期要长一些.和其它的时钟不同,这两个时钟能够在中断中安全使用.可以用到低层的驱动代码上.
etimer库主要提供的是事件时钟(event timer),在一个时钟周期后,contiki系统可以使用这个时钟做事件调度.主要用在contiki进程当系统的其它功能工作或休眼时,这个进程在等待一个时钟周期.
ctimer库主要用于回调时钟(callback timers),主要用一个时钟周期完了之后,去调度回调函数.当其它进程进入工作或者休眼状态时,这个进程仍然可以等待ctimer.由于ctimer本来就是当一个时钟周期结束时,去调用一个函数并且执行.ctimer运用的场合一般在没有明显的进程的地方,比如协议的执行时可以用到ctimer.ctimer可以通过rime协议栈去管理通讯是否超时的.
rtimer库主要是用来调度实时任务的.可以用到任何运行的contiki进程中,用时钟调度的方式去让实时任务运行.rtimer可用在对时间要求极严的的场合,比如无线电模块要在不延时的情况下开启或者关闭.
timer模块实现文件core/sys/timer.c,对应的头文件是用来外部调用的.
contiki的timer是用来提供定时器的基本设置重启,清零.或者是检查时间到期.应用程序必须去主动检查定时器是否过期,不能自动的的获得过期的消息.
timer模块用clock_time()来获得当前的系统时间.但是对于不同的MCU,读取定时器的方法会有所有同,所以这个函数对不同的MCU有一套不同的定义.
比如CC2530芯片,clock_time()的实现在cpu/cc253x/dev/clock.c中,是contiki的系统时钟.
timer结构体定义在core/sys/timer.h中,同样,其它的不同类弄的模块都有符合自身功能的一个结构体的定义.
struct timer { clock_time_t start; clock_time_t interval; };
其中clocl_time_t是一个unsigned short型的变量,定义在cpu/cc253x/8051def.h中.这里有一个8051的文件,是由于sdcc是基于8051的编译器,而CC2530也是一个增强型的8051.
timer结构体中包含两个变量,start,开始计数点,interval,过期时问.
timer库的API如下所示
void timer_set(struct timer *t, clock_time_t interval) : 开始定时器
void timer_reset(struct timer *t) : 以过期的时间间隔(interval)为起始点,重启timer
void timer_restart(struct timer *t) : 以当前时间,即clock_time()为起始点,重启tiemr
int timer_expired(struct timer *t) : 检查定时器是否到期
clock_time_t timer_remaining(struct timer *t) : 定时器计时结束的话,返回值是不可预料的.反回正数表示定时器还差多长时间(只是一个估计值)
值得注意的是,初始化timer必须要第一步调用timer_set(),它会初始化start和interval的值.
timer库可以安全的用在中断中.下面有个例子,就是说明在中断中如何去探测定时器结束.
static struct timer rxtimer; void init(void) { timer_set(&rxtimer, CLOCK_SECOND / 2); } interrupt(UART1RX_VECTOR) uart1_rx_interrupt(void) { if(timer_expired(&rxtimer)) { /* Timeout */ /* ... */ } timer_restart(&rxtimer); /* ... */ }
上面的这个例子中的interrupt函数有许多局限性,是根据CPU的特点定义的,所以需要看MCU层支持的代码.
另外,关于clock_time()这个函数的实现,也牵涉到MCU,现在以2530为例来分析一下.
在cpu/cc253x/dev/clock.c这个文件中,有两个函数.
/*---------------------------------------*/ CCIF clock_time_t clock_time(void) { return count; } /*---------------------------------------*/ CCIF unsigned long clock_seconds(void) { return seconds; }里面实现了clock_time和clock_seconds,同时也引出了两个全局变量count和seconds.这两个变量正是timer和stimer实现的关键.前面提到过,timer是以tick为单位来计数的,但是stimer是以seconds为单位计数的.我们看一下这两个全局变量的定义cpu/cc253x/dev/clock.c:
/* Sleep timer runs on the 32k RC osc. */ /* One clock tick is 7.8 ms */ #define TICK_VAL (32768/128) /* 256 */ /*---------------------------------------*/ #if CLOCK_CONF_STACK_FRIENDLY volatile __bit sleep_flag; #else #endif /*---------------------------------------*/ /* Do NOT remove the absolute address and do NOT remove the initialiser here */ __xdata __at(0x0000) static unsigned long timer_value = 0; static volatile __data clock_time_t count = 0; /* Uptime in ticks */ static volatile __data clock_time_t seconds = 0; /* Uptime in secs */
对于CC2530的CPU来说,总共包含了两个高频振荡器(32MHz的晶振和16MHz的RC振荡器)和两个低频振荡器(32KHz的晶振和低频振荡器).
休眼时钟是运行在32KHz的RC振荡器上的.精确的振荡周期为32.768KHz,
而系统的一个tick就是#define TICK_VAL (32768/128) /* 256 */来规定的.
这里做一下简单的计算,1/(32768/256)也就是7.8ms.
在CC2530的clock实现中,每一次增加count就加1.
然后在cpu/cc253x/8051def.h中间有
/* Defines tick counts for a second. */ #define CLOCK_CONF_SECOND 128
这个定义是7.8msx128=1s,也就是说,seconds的值与count的值是128倍的关系.
这样cc2530同时保留了timer和stimer的增量.
stimer模块实际上stimer这个模块跟timer的用法,及API的功能完全一致,只不过是以seconds为单位计时的.而且在timer中也提到了如何实现的.
API如下
void stimer_set(struct stimer *t, unsigned long interval)
void stimer_reset(struct stimer *t)
void stimer_restart(struct stimer *t)
int stimer_expired(struct stimer *t)
unsigned long stimer_remaining(struct stimer *t)
contiki中的etimer是提供了一个时钟来产生计时事件.当结束计时时,会给进程发送一个PROCESS_EVENT_TIMER类型的事件.etimer使用的时钟是clock_time(),即系统时钟.
etimer时钟最核心的内容就是etimer这个结构体.
struct etimer { struct timer timer; struct etimer *next; struct process *p; };
单看这个结构体,不看其它内容.可以知道etimer是用链表形式管理的,每一个etimer对应于一个进程.
void etimer_set(struct etimer *t, clock_time_t interval) : 开始时钟
void etimer_reset(struct etimer *t) : 以前的时间间隔重新没置时钟
void etimer_restart(struct etimer *t) : 以当前的时间为间隔设置时钟
void etimer_stop(struct etimer *t) : 停止时钟
int etimer_expired(struct etimer *t) : 检查时钟是否终止
int etimer_pending() : 检查还有没有时钟在工作
clock_time_t etimer_next_expiration_time() :得到下一个事件时钟的终止时间.
void etimer_request_poll() :这一个接口在后面再详细描述。
要注意,由于时钟事件本身就是用用contiki系统的事件时钟调度的。当一个事件时钟需要从另一个contiki的process里面回调一个函数时。可以用PROCESS_CONTEXT_BEGIN() 和PROCESS_CONTEXT_END()
来临时的改变一下进程的上下文。
还要注意的是,etimer不能用在中断中。因为它是进程间的调度。
下面是一个例子,使用了一个事件定时器,让一个进程每分钟执行一次。
PROCESS_THREAD(example_process, ev, data) { static struct etimer et; PROCESS_BEGIN(); /* Delay 1 second */ etimer_set(&et, CLOCK_SECOND); while(1) { PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); /* Reset the etimer to trig again in 1 second */ etimer_reset(&et); /* ... */ } PROCESS_END(); }
对于etimer的实现core/sys/etimer.c,还有一部分内容需要说明。
timerlist是把整个的etimer做为了个链表来维护的。然后在add_timer中采用前序插入法,把最新的etimer放到最前面。然后供etimer_set等API去做内部调用。
next_expiration变量。主要是用于获得下一次的etimer的时间间隔。用来实现etimer时钟的校准。遍历了链表上所有的etimer,如果哪个etimer的间隔时间最短,就把它做为next_expiration。即最小值。
static struct etimer *timerlist; static clock_time_t next_expiration; .... /*---------------------------------------*/ static void update_time(void) { clock_time_t tdist; clock_time_t now; struct etimer *t; if (timerlist == NULL) { next_expiration = 0; } else { now = clock_time(); t = timerlist; /* Must calculate distance to next time into account due to wraps */ tdist = t->timer.start + t->timer.interval - now; for(t = t->next; t != NULL; t = t->next) { if(t->timer.start + t->timer.interval - now < tdist) { tdist = t->timer.start + t->timer.interval - now; } } next_expiration = now + tdist; } }
其中在etimer_stop实现中
void etimer_stop(struct etimer *et) { struct etimer *t; /* First check if et is the first event timer on the list. */ if(et == timerlist) { timerlist = timerlist->next; update_time(); } else { /* Else walk through the list and try to find the item before the et timer. */ for(t = timerlist; t != NULL && t->next != et; t = t->next); if(t != NULL) { /* We've found the item before the event timer that we are about to remove. We point the items next pointer to the event after the removed item. */ t->next = et->next; update_time(); } } /* Remove the next pointer from the item to be removed. */ et->next = NULL; /* Set the timer as expired */ et->p = PROCESS_NONE; }
注意一下中间的for循环,它是用于找出下一个etimer是传入参数et的情况。然后再把et这个链表的指向销毁。然后把进程标记为空,即PROCESS_NONE。
这儿有一个疑问。为什么在创建,销毁时钟时,没有使用malloc,free之类的操作。其根本原因还是由于contiki本身适用于内存受限的操作系统,而且在编译时,把程序的各个段己经规定好了,不使用HEAP区域。etimer的内存空间最多也只能放在data或者是bss段中。所以才有了只清除内部的变量,而不释放本身的内存的做法。实际上,个人建议,把etimer的分配放到前面内存分配讨论过的mmem方式在初始化系统时去动态分配内存比较好。
etimer是依赖于MCU平台的。并且它需要依靠etimer_request_poll() 去回调进程,用来管理事件定时器。这也意味着,etimer可以让系统从休眼状态中唤醒,关于MCU的休眼和唤醒,在硬件上有特定的定时器。比如在cc2530中,就有专门的sleep timer来实现。etimer库提供了3个接口来实现操作。
etimer_pending() 检查是否有还没有过期的etimer
etimer_next_expiration_time() 得到下一个etimer的过期时间。
etimer_request_poll() 用来通知etimer库系统时钟要改变了或者是etimer要过期了,一般会周期性的调这个函数,然后检查系统时钟中否改变了。这个函数可以用在中断中。
由于etimer_repuest_poll()是用来调用etimer_process进程.我们把这个进程的代码再分析一遍.
/*---------------------------------------*/ PROCESS_THREAD(etimer_process, ev, data) { struct etimer *t, *u; PROCESS_BEGIN(); timerlist = NULL; while(1) { PROCESS_YIELD(); if(ev == PROCESS_EVENT_EXITED) { struct process *p = data; while(timerlist != NULL && timerlist->p == p) { timerlist = timerlist->next; } if(timerlist != NULL) { t = timerlist; while(t->next != NULL) { if(t->next->p == p) { t->next = t->next->next; } else t = t->next; } } continue; } else if(ev != PROCESS_EVENT_POLL) { continue; } again: u = NULL; for(t = timerlist; t != NULL; t = t->next) { if(timer_expired(&t->timer)) { if(process_post(t->p, PROCESS_EVENT_TIMER, t) == PROCESS_ERR_OK) { /* Reset the process ID of the event timer, to signal that the etimer has expired. This is later checked in the etimer_expired() function. */ t->p = PROCESS_NONE; if(u != NULL) { u->next = t->next; } else { timerlist = t->next; } t->next = NULL; update_time(); goto again; } else { etimer_request_poll(); } } u = t; } } PROCESS_END(); }
在这个函数的实现中,我们可以看到:
timerlist是整个系统统护的etimer的链表.由于protothread的作用,当前进程可能随时改变.那么,timerlist的值也会改变.
etimer_process主要是收集要结束的etimer,并且给其它进程发出event timer类型的消息.
接受PROCESS_EVENT_EXIT事件,找出要退出的etimer;
接受PROCESS_EVENT_TIMER事件,然后再从事件队列里删除已经到期的etimer,并且标记为PROCESS_NONE。
PROCESS_EVENT_POLL的事件没有做任何处理,大概属于后期处理的代码。
实际上,etimer_process这个进程运行的目的也就是不断的销毁即将退出的etimer.
ctimer模块contiki系统中的回调的计时器,底层照例用的clock_time()这个系统时钟.
首先关心的是ctimer的结构体:
struct ctimer { struct ctimer *next; struct etimer etimer; struct process *p; void (*f)(void *); void *ptr; };其中ctimer->f即回调函数的接口.ctimer->ptr表示回调函数的参数.
列出了ctimer的API,由于使用方法与上面的timer相同,不做过多的解释.
void ctimer_set(struct ctimer *c, clock_time_t t, void(*f)(void *), void *ptr)
void ctimer_reset(struct ctimer *t)
void ctimer_restart(struct ctimer *t)
void ctimer_stop(struct ctimer *t)
int ctimer_expired(struct ctimer *t)
示例:
static void callback(void *ptr) { ctimer_reset(&timer); /* ... */ } void init(void) { ctimer_set(&timer, CLOCK_SECOND, callback, NULL); }
我们再来分析一下ctimer的启动和设置情况.涉及到两个函数
void ctimer_init(void);
void ctimer_set(struct ctimer *c, clock_time_t t,void (*f)(void *), void *ptr);
init函数必需在contiki开始启动时调用,一般放在初始化的变量中.
set函数有4个传入参数
*c表示需要使用的ctimer的地址
t表示使用的时钟,有tick和second之分
f是回调的函数
*ptr是回调函数的参数,一般可以写个结构体.如果没有,可以把这个参数设为NULL.
set函数执行的时机,一般在这个ctimer过期时,就开始执行回调函数.
我们再看看ctimer_process这个进程的实现.
PROCESS(ctimer_process, "Ctimer process"); PROCESS_THREAD(ctimer_process, ev, data) { struct ctimer *c; PROCESS_BEGIN(); for(c = list_head(ctimer_list); c != NULL; c = c->next) { etimer_set(&c->etimer, c->etimer.timer.interval); } initialized = 1; while(1) { PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_TIMER); for(c = list_head(ctimer_list); c != NULL; c = c->next) { if(&c->etimer == data) { list_remove(ctimer_list, c); PROCESS_CONTEXT_BEGIN(c->p); if(c->f != NULL) { c->f(c->ptr); } PROCESS_CONTEXT_END(c->p); break; } } } PROCESS_END(); }
由于ctimer同样也是event timer,只不过多了一个回调的功能,所以在初始化ctimer时,用etimer_set来初始化系统启动时需要的ctimer.然后标记初始化完成.在ctimer_process中,主要是去轮询过期的ctimer,然后再进行回调.请注意,由于回调函数在另一个进程中,所以根据protothread的设计,需要调用PROCESS_CONTEXT_BEGIN()和PROCESS_CONTEXT_END()来临时的开辟其它进程中的上下文,最后关闭. rtimer模块
第一步还是看一下rtimer的结构体
struct rtimer { rtimer_clock_t time; rtimer_callback_t func; void *ptr; };
时钟类型,回调函数名,然后回调函数的参数.
函数指针定义
typedef void (* rtimer_callback_t)(struct rtimer *t, void *ptr);
这个timer不再是一个链表,只是一个timer,去提供实时性的需求.
rtimer本来就是用做contiki的实时任务的.有自己的时钟模块.利用rtimer_time来得到当前的系统时钟.然后用RTIMER_SECOND表示每一分钟的tick数.这儿的时钟跟timer模块用的晶振不一样.这已经是CPU上的时钟了.在CC2530上,利用CLKCONCMD寄存器,设置成500KHz的时钟.由于分频置TICKSPD[2:0]设的32MHz,也就是说RTIMER_SECOND设置成 500KHz/32Hz 为15625U,定义在cpu/cc253x/rtimer-arch.h中.是属于体系结构相关的代码.
和contiki中的其它时钟库不一样.rtimer要保证实时任务的优先级,并且立即执行.主要是大部分的函数都不具备优先执行权,所以用rtimer去优先执行是可取的.如果放在中断安全的一些函数中例如process_poll()中执行的话,与其它非优先级的进程一起执行的话,容易产生冲突,造成非优线程只能异步去执行.
有两种类型的实时任务.一种是硬实时任务,一种是软实时任务.其中硬实时任务的优先级要高点,
rtimer与其它的timer不一样的是,它在平台这一层只是提供了三个接口.
rtimer_init, rtimer_set, rtimer_run_next这三个接口.但是这三个接口的实现是严重依赖于MCU的特性的.
其中主要调用了rtimer_arch_schedule,在cc2530中是把时钟的模式先捕获,捕获完了做输出比较.
rtimer的具体实现现在还是分析的相当明白.需要对cc2530的定时器模式和工作流程做好分析之后,才能回过头来看rtimer 具体作用.
从一个技术出身的程序员到做产品,现在感觉到来自各方面的不足,这种不足需要通过各种办法和手段去弥补,当然自学和看书是一种方法,但它并不能给你更广的眼界,不能给你不一样的思考。其实从产品的角度看问题,可能你需要的是更多的沟通,包括和业界一流的公司去学习和交流。
这周三有幸和数字天空的 fan做了一个深入的交流,他谈了很多,讲到了他们团队的合作是在弱制度下实现了自觉、高效的工作方式。这也是我很好奇的地方,没有严格的制度约束,没有领袖性的人物每天灌输和洗脑,如何保证一个30-40人的团队有效分工,积极合作的呢?我挖掘这个问题的本质挖掘了很久,和fan聊了很多,最后的一刹那我终于找到了原因,因为他们公司通过在移动互联网行业的摸爬滚打凝聚了自觉的魂!那这个魂又是什么?切听我下面分析:
Why is each of us here?这是每一个职场员工都需要去思考的问题,我们为什么每天疲于奔命与两点一线之间?我们为什么要面对顾客、甚至老板的指责和谩骂?我们无数天的加班和熬夜把大好的青春留在了看似繁华的IT岁月中,我们甚至于和互相讨厌和憎恶的人共事,我们到底是为了什么?有的人最直接的想法认为我们都是为了钱。真的是钱是我们坚持下去的动力吗?
“相信自己的选择是最好的选择”,这是从感情上最能解释的通的一句话,在招受一次又一次失败的时候,在招受不公平的待遇的时候,在招受别人的谴责甚至辱骂的时候,我们迷茫过,对信心动摇过,但我们从未改变自己的方向和选择,这就是我认为这句话就是所谓企业灵魂最标准的答案。
但是最近听到的很多人对于自己从事的工作没有太多的信心,或者说你认为现在状况不是你最好的选择,那么我觉得“choose your marker”,找到那个让你兴奋的专业领域去吧。然后投身进去,等待自己不断的蜕变。
话题有点扯远了,我们在很好掌握自己专业的同时我们更需要倾听和理解你的领域或者产业目前的状况,这是一种最好的学习方法,也是帮助你蜕变最快的方式之一。至少我是这么认为的。
那么如何对于自己领域有个了解呢?我觉得需要对同行业公司进行了解,需要对竞争对手进行了解,虽然你可能是一个程序员,一个产品经理,一个测试人员,或者一位普通的员工,你要学会倾听各种的声音。
只要你敢于走出你的隔间,你的公司,别被你的领导、你的上司或者你的周围的人所束缚。请相信:”你可以做的更优秀!“这就好比学语言,最快的学习方法是首先你必须忘记你的母语,这和道家所说的有失才有得 有异曲同工之妙啊!
就像《内向的人如何建立人脉》所描述的是找到产业中的各种各样的圈子、小组、俱乐部,主动参与进去,倾听他们的声音,告诉他们你的理解。不要仅仅因为你是一个产品经理,就只和产品经理圈子混,错,请保持和你的产业链条上的各个环节的人接触,这样才能打通你的任督二脉,知道你那些所从来不知道的,思考那些你本来无缘思考的。
所以以上这些心得是我和数字天空的fan,Tap4fun的li,gameloft的cai沟通的一些感想。
请相信:
走出公司,进入产业,建立自己的圈子。我做到了,你呢?
直接上代码:
管理通讯录的类:ManagerContact.java
package com.example.contactdemo; import java.util.ArrayList; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; import android.util.Log; /** * 管理通讯录的类 * @author Willen * */ public class ManagerContact { // 根据姓名删除联系人 public void delContact(Context context, String name) { Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, new String[] { Data.RAW_CONTACT_ID }, ContactsContract.Contacts.DISPLAY_NAME + "=?", new String[] { name }, null); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); if (cursor.moveToFirst()) { do { long Id = cursor.getLong(cursor .getColumnIndex(Data.RAW_CONTACT_ID)); ops.add(ContentProviderOperation .newDelete( ContentUris.withAppendedId( RawContacts.CONTENT_URI, Id)).build()); try { context.getContentResolver().applyBatch( ContactsContract.AUTHORITY, ops); } catch (Exception e) { } } while (cursor.moveToNext()); cursor.close(); } } // 根据号码删除联系人 public void delContactByNumber(Context context, String number) { Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, new String[] { Data.RAW_CONTACT_ID }, ContactsContract.Contacts.Data.DATA1 + "=?", new String[] { number }, null); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); if (cursor.moveToFirst()) { do { long Id = cursor.getLong(cursor .getColumnIndex(Data.RAW_CONTACT_ID)); ops.add(ContentProviderOperation .newDelete( ContentUris.withAppendedId( RawContacts.CONTENT_URI, Id)).build()); try { context.getContentResolver().applyBatch( ContactsContract.AUTHORITY, ops); } catch (Exception e) { } } while (cursor.moveToNext()); cursor.close(); } } // 根据姓名更新联系人 public boolean updateContact(Context context, String oldname, String name, String phone, String email, String website, String organization, String note) { Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, new String[] { Data.RAW_CONTACT_ID }, ContactsContract.Contacts.DISPLAY_NAME + "=?", new String[] { oldname }, null); if (!cursor.moveToFirst()) { return false; } String id = cursor .getString(cursor.getColumnIndex(Data.RAW_CONTACT_ID)); cursor.close(); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?" + " AND " + Phone.TYPE + "=?", new String[] { String.valueOf(id), Phone.CONTENT_ITEM_TYPE, String.valueOf(Phone.TYPE_HOME) }) .withValue(Phone.NUMBER, phone).build()); // 更新email ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?" + " AND " + Email.TYPE + "=?", new String[] { String.valueOf(id), Email.CONTENT_ITEM_TYPE, String.valueOf(Email.TYPE_HOME) }) .withValue(Email.DATA, email).build()); // 更新姓名 ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), StructuredName.CONTENT_ITEM_TYPE }) .withValue(StructuredName.DISPLAY_NAME, name).build()); // 更新网站 ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), Website.CONTENT_ITEM_TYPE }) .withValue(Website.URL, website).build()); // 更新公司 ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), Organization.CONTENT_ITEM_TYPE }) .withValue(Organization.COMPANY, organization).build()); // 更新note ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), Note.CONTENT_ITEM_TYPE }) .withValue(Note.NOTE, note).build()); try { context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); return true; } catch (Exception e) { return false; } } // 根据号码更新联系人 public boolean updateContactByNumber(Context context, String number, String name, String phone, String email, String website, String organization, String note) { Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, new String[] { Data.RAW_CONTACT_ID }, ContactsContract.Contacts.Data.DATA1 + "=?", new String[] { number }, null); if (!cursor.moveToFirst()) { Log.e("InsertContact", "updateContactByNumber"); InsertContact(context, name, number); return false; } String id = cursor .getString(cursor.getColumnIndex(Data.RAW_CONTACT_ID)); cursor.close(); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?" + " AND " + Phone.TYPE + "=?", new String[] { String.valueOf(id), Phone.CONTENT_ITEM_TYPE, String.valueOf(Phone.TYPE_HOME) }) .withValue(Phone.NUMBER, phone).build()); // 更新email ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?" + " AND " + Email.TYPE + "=?", new String[] { String.valueOf(id), Email.CONTENT_ITEM_TYPE, String.valueOf(Email.TYPE_HOME) }) .withValue(Email.DATA, email).build()); // 更新姓名 ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), StructuredName.CONTENT_ITEM_TYPE }) .withValue(StructuredName.DISPLAY_NAME, name).build()); // 更新网站 ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), Website.CONTENT_ITEM_TYPE }) .withValue(Website.URL, website).build()); // 更新公司 ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), Organization.CONTENT_ITEM_TYPE }) .withValue(Organization.COMPANY, organization).build()); // 更新note ops.add(ContentProviderOperation .newUpdate(ContactsContract.Data.CONTENT_URI) .withSelection( Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { String.valueOf(id), Note.CONTENT_ITEM_TYPE }) .withValue(Note.NOTE, note).build()); try { context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); return true; } catch (Exception e) { return false; } } // 添加联系人 public void InsertContact(Context context, String name, String phone) { ContentResolver resolver = context.getContentResolver(); Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); ContentValues values = new ContentValues(); // 向raw_contacts插入一条除了ID之外, 其他全部为NULL的记录, ID是自动生成的 long id = ContentUris.parseId(resolver.insert(uri, values)); // 添加联系人姓名 uri = Uri.parse("content://com.android.contacts/data"); values.put("raw_contact_id", id); values.put("data2", name); values.put("mimetype", "vnd.android.cursor.item/name"); resolver.insert(uri, values); // 添加联系人电话 values.clear(); // 清空上次的数据 values.put("raw_contact_id", id); values.put("data1", phone); values.put("data2", "2"); values.put("mimetype", "vnd.android.cursor.item/phone_v2"); resolver.insert(uri, values); } //判断联系人是否在通讯录内 public boolean getContactPeople(Context context,String incomingNumber) { ContentResolver contentResolver = context.getContentResolver(); Cursor cursor = null; String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER }; cursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, ContactsContract.CommonDataKinds.Phone.NUMBER + "=?", new String[] { incomingNumber }, ""); if(cursor.getCount() == 0) { return true; } return false; } }
MainActivity.java
package com.example.contactdemo; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; public class MainActivity extends Activity { Context context; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ManagerContact managerContact = new ManagerContact(); String phone = "15992470000"; if (!managerContact.getContactPeople(context, phone)) { Toast.makeText(context, "本地通讯录已包此人信息", Toast.LENGTH_SHORT).show(); }else { managerContact.InsertContact(context, "aa", phone); Toast.makeText(context, "添加成功!", Toast.LENGTH_SHORT).show(); } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
布局文件只有一个button。资源:http://download.csdn.net/detail/viviwen123/5194623