Symbian OS应用开发学习笔记之通讯录(电话薄Contacts)
来源:第三只眼的专栏
Symbian OS通讯录模型
Symbian OS手机的通讯录采用文件方式存储,用symbian自己的说法就是通讯录数据库。每个Symbian OS手机都有一个默认的通讯录数据库,这个通讯录数据库在2nd和3rd两个版本手机中的位置是不同的,前者是c:\ system\data\Contacts.cdb,后者是c:\private\100012a5\DBS_100065FF_Contacts.cdb。不管怎么说两者都在内部闪存中,也就是跟优盘差不多的介质,由于symbian OS的文件系统暂时还没有仔细拜读过,所以具体差别暂不知,总之掉电不会失去就是了。
Symbian OS的手机通讯录在开发上的操作依靠Symbian OS通讯录模型(Contacts Model)来实现。通讯录模型由通讯录数据库、通讯录条目(项)和通信录域三者组成,他们之间的关系是:一个手机除了系统自带的默认通讯录数据库外还可以带多个通讯录数据库;一个通讯录数据库有多个通讯录条目组成,这里每个条目就是每个联系人,具体数量限制各个手机应该不一样;而一个通讯录条目又有多个通讯录域组成,好比有姓名、工作手机号码、家庭手机号码等等,每个项就是一个域。
在这里Symbian为了统一通讯录格式,所以将通讯录条目采用vCard格式MIME规范(RFC 1521)所定义的明码文本来定义域,具体的vCard简单介绍见附录。
Symbian OS通讯录操作API类
知道了Contacts Model的概念,Symbian OS将很多系统API操作封装为几个类:
CContactDatabase(数据库类):负责新建、打开、关闭等基本数据库操作外,还负责数据库更新(通讯录条目的新建、修改、删除需要通过CContactDatabase类的操作才能实现)、排序和查找,另外还有一些建立快速拨号之类的操作也是通过它来实现。
CContactItem(通讯录条目类):由唯一的一个TContactItemId(一个TInt32类型的宏定义)标识,负责具体一个通讯录条目的创建、修改,其直接管理每一个通讯录域
CContactItemField(域类):每一个域就是一个真实单一的数据,该数据的类型具有存储类型(TStorageType)和域类型(TFieldType)同时决定,具体的四种存储类型和多种域类型定义见系统头文件cntdef.h内的定义。
当然还有很多其它的类,比如CContactItemFieldSet(域集类)、CContactFieldStorage(与存储基类)、CContactTextFields(文本存储域类)、 MContactDbObserver(通讯数据库观察类)等等,涉及面太大,具体也不能凭空说清楚,代码中出现就知道了。
Symbian OS通讯录操作实例
例1、 打开和关闭数据库
CContactDatabase::OpenL()函数有两个重载函数。如果该函数没有给出一个参数,就打开默认的数据库。另一种情况是,应用软件设计师也可以传递一个有关数据库的路径和文件名,规定打开一个指定数据库。
//打开默认数据库 CContactDatabase* contactsDb = CContactDatabase::OpenL(); CleanupStack::PushL(contactsDb); //取得当前数据库所有通讯条目数 TInt numberOfContacts = contactsDb->CountL(); //释放数据库 CleanupStack::PopAndDestroy(contactsDb);
要注意的是:某个通信录数据库并不具有Close()函数或类似的函数,否则我们压入清除栈时就得用CleanupClosePushL()函数了。
例2、 创建数据库
CContactDatabase::CreateL()函数与CContactDatabase::ReplaceL()函数之间的唯一差别就是:如果该数据库已经存在,前者会以KErrAlreadyExists退出。如前所述,如果没有定义参数,这些函数将创建一个默认的数据库。CContactDatabase::FindContactFile()函数给出了一个描述符,如果不存在默认数据库的话,该描述符就会返回该默认数据库的位置。
// If one is found, replace it with a new empty default database. // If no default database is found, create a new one. TFileName contactDbFilePath; CContactDatabase* newDefaultContactDb; //是否存在默认数据库 if(CContactDatabase::FindContactFile(contactDbFilePath)) { newDefaultContactDb = CContactDatabase::ReplaceL(); } else { newDefaultContactDb = CContactDatabase::CreateL(); } CleanupStack::PushL(newDefaultContactDb); // 添加自己功能代码 CleanupStack::PopAndDestroy(newDefaultContactDb); 注:以上代码负责创建一个空的默认数据库。
例3、 读取(遍历)通讯录条目
可以用TContactIter类(该类起到数据库操作中类似游标的作用)来遍历一个通信录数据库。这个类提供了一整套的函数,用于遍历所有的通信录项。所有的函数都用通信录项ID (TContactItemId) 进行操作,该ID 用于访问某个特定的通信录项。
// Open the default contacts database: CContactDatabase* contactsDb = CContactDatabase::OpenL(); CleanupStack::PushL(contactsDb); TContactIter iter(*contactsDb); TContactItemId cardId; //循环遍历 while( ( cardId = iter.NextL() ) != KNullContactId ) { //读取相应项,这里之所以称其card,就是其实际相当于读一个完整的vCard条目 CContactItem* card = contactsDb->ReadContactL(cardId); CleanupStack::PushL(card); //添加自己功能代码 //…… contactsDb->CloseContactL(card->Id()); CleanupStack::PopAndDestroy(); // card } CleanupStack::PopAndDestroy(); // contactsDb 例4、 新建通讯录条目
// 字符串声明 _LIT(KForenameLabel,"Forename");//中文“名” _LIT(KSurnameLabel,"Surname"); //中文“姓” _LIT(KWorkPhoneLabel,"Work Phone"); _LIT(KForename,"Steve"); _LIT(KOtherForename,"Bob"); _LIT(KSurname,"Wilkinson"); _LIT(KWorkPhone,"+441617779700"); //以上定义的字符串,在以后例子中将直接使用,不再重新进行定义了 //打开默认数据库 CContactDatabase* contactsDb = CContactDatabase::OpenL(); CleanupStack::PushL(contactsDb); // 建立一个新条目 CContactItem* contact = CContactCard::NewLC(); //创建一个新的文本存储类型的姓域 CContactItemField* field = CContactItemField::NewLC(KStorageTypeText, KUidContactFieldFamilyName); //将姓域建立与vCard的映射 field->SetMapping(KUidContactFieldVCardMapUnusedN); //设置域标签 field->SetLabelL(KSurnameLabel); //设置域值 field->TextStorage()->SetTextL(KSurname); //把该域加入到新建的条目中 contact->AddFieldL(*field); CleanupStack::Pop(); //添加文本存储类型的名域 field=CContactItemField::NewLC(KStorageTypeText, KUidContactFieldGivenName); field->SetMapping(KUidContactFieldVCardMapUnusedN); field->SetLabelL(KForenameLabel); field->TextStorage()->SetTextL(KForename); contact->AddFieldL(*field); CleanupStack::Pop(); //添加文本存储类型的手机号码域 field=CContactItemField::NewLC(KStorageTypeText, KUidContactFieldPhoneNumber); field->SetMapping(KUidContactFieldVCardMapTEL); field->SetLabelL(KWorkPhoneLabel); field->TextStorage()->SetTextL(KWorkPhone); contact->AddFieldL(*field); CleanupStack::Pop(); //把建立的新记录添加到数据库中 contactsDb->AddNewContactL(*contact); contactsDb->SetOwnCardL(*contact); CleanupStack::PopAndDestroy(2); // contact contactsDb
例5、 查找并更新通讯录条目
这个例子比较复杂,涉及的查找函数为FindAsyncL,该类函数实例有:
CContactIdArray * CContactDatabase::FindLC(const TDesC &aText, const CContactItemFieldDef *aFieldDef); CIdleFinder * CContactDatabase::FindAsyncL(const TDesC &aText, const CContactItemFieldDef *aFieldDef, MIdleFindObserver *aObserver); 还有对应的FindInTextDefLC()和FindInTextDefAsyncL()各两组,具体参见sdk 下面是具体代码实例: CContactDatabase* iContactsDb = CContactDatabase::OpenL(); CleanupStack::PushL(iContactsDb); CContactItemFieldDef* iFieldDef = new (ELeave)CContactItemFieldDef(); CleanupStack::PushL(iFieldDef); iFieldDef->AppendL(KUidContactFieldGivenName); iFieldDef->AppendL(KUidContactFieldFamilyName); _LIT(KFindToken, "Bond"); CIdleFinder * iFinder = iContactsDb->FindAsyncL( KFindToken, iFieldDef, this); CleanupStack::PushL(iFinder); if(iFinder->IsComplete()) { if(iFinder->Error() == KErrNone) { CContactIdArray* result = iFinder->TakeContactIds(); CleanupStack:: PushL(result); for(TInt i=0; i<result->Count(); i++) { TContactItemId cardId = (*result)[i]; CContactItem* ownCard = iContactsDb ->OpenContactL(cardId); CleanupStack::PushL(ownCard); TInt index = ownCard->CardFields().Find(KUidContactFieldGivenName); ownCard->CardFields()[index].TextStorage()->SetTextL(KOtherForename); //提交所做的修改,如果这里不做更改可以调用CloseContactL直接关闭 //但是一旦用OpenContactL或OpenContactLX打开就必须调用两者之一关闭 iContactsDb ->CommitContactL(*ownCard); CleanupStack::PopAndDestroy();// ownCard } CleanupStack::PopAndDestroy();//result; } } CleanupStack::PopAndDestroy(3);// iContactsDb、iFieldDef、iFinder 例6、 导出所选通讯录条目到文件(vCard)
在这里,主要使用CContactDatabase类中ExportSelectedContactsL函数,关于该函数的定义可以查看SDK文档;而且在这里与前次遍历不一样的是,加了一个过滤器CCntFilter类,虽然取法仍然是所有通讯条目,但做法不一样,具体例程如下:
RFs fileSession; //连接文件服务器 User::LeaveIfError(fileSession.Connect()); CleanupClosePushL(fileSession); //1 //打开默认数据库 CContactDatabase* contactDb = CContactDatabase::OpenL(); CleanupStack::PushL(contactDb); //2 //新建过滤器 CCntFilter* filter = CCntFilter::NewLC(); //3 filter->SetContactFilterTypeALL(EFalse); //按vCard格式导出 filter->SetContactFilterTypeCard(ETrue); //安装filter contactDb->FilterDatabaseL(*filter); //取出满足条件的记录数据项数组 CContactIdArray* exportContact = CContactIdArray::NewL(filter->iIds); CleanupStack::PushL(exportContact); //4 RFile file; //新建文件,aFileName是文件名字 file.Replace(fileSession,aFileName,EFileWrite); CleanupClosePushL(file); //5 //声明文件流 RFileWriteStream outputStream(file); CleanupClosePushL(outputStream); //6 TUid id; id.iUid = KVersitEntityUidVCard; //导出到文件 contactDb->ExportSelectedContactsL(id,*exportContact, aWriteStream, CContactDatabase::EExcludeUid); CleanupStack::PopAndDestroy(6,contactDb);
Symbian S60独有通讯录操作API引擎
以上是适用于任何Symbian OS通讯录操作的方法,在S60平台SDK中nokia专门为我们建立了一个操作通讯录的引擎,以及相对应的产生了一些封装的类:
CPbkContactEngine(通讯录引擎类):如果已经存在一个缺省数据库,CPbkContactEngine::NewL() 就连接到该数据库,否则创建该数据库。当然也可以传入文件名,打开一个指定的通讯录数据库,根据头文件cpbkcontactengine.h,他就是对 CContactDatabase和观察器类MContactDbObserver封装了下并进行了一些优化,简便了我们操作时的一些代码,为此操作起来比较方便。
CPbkContactItem(通讯录条目类):该类头文件是CPbkContactItem.h,主要对通讯录条目类CContactItem的封装和优化,可以看出很多导出函数都是一致的。
TPbkContactItemField(域类):不用想也知道这个类是怎么来的了,该类的头文件tpbkcontactitemfield.h。有兴趣的可以去研究比照一下。
当然也有其他一些封装的类,只不过离通讯录模型比较远的,我们就不多展开了,在例子中看其使用。
例7、 新建通讯录条目
_LIT(KFName,"King"); _LIT(KLName,"Chai"); _LIT(KNumber,"13777777777"); //运用引擎打开默认通讯录 CPbkContactEngine* iPbkContactEngine = CPbkContactEngine::NewL(); CleanupStack::PushL(PbkContactEngine);//1 //新建一空通信录项 CPbkContactItem* contact = iPbkContactEngine->CreateEmptyContactL(); CleanupStack::PushL(contact); //2 //设置first name 域 TPbkContactItemField* field = contact->FindField(EPbkFieldIdFirstName); CleanupStack::PushL(contact); //3 field->TextStorage()->SetTextL(KFName); //设置last name 域 field = contact->FindField(EPbkFieldIdLastName); field->TextStorage()->SetTextL(KLName); //设置手机号码域 field = contact->FindField(EPbkFieldIdPhoneNumberMobile); field->TextStorage()->SetTextL(KNumber); //可以添加其他值域 //... //修改后结果添加到数据库中,并返回这个通信录项的id,该id可以以后使用 TContactItemId Id = iPbkContactEngine->AddNewContactL(*contact); CleanupStack::PopAndDestroy(3); 以上代码是否比例4的代码相对来说更简单些啊? 例8、 修改通讯录条目 实现修改和新建的代码类似,不同是你需要找到你要修改的通讯录条目aContactId,然后找到要修改的域进行修改,最后导入数据库。 _LIT(number,"13500000000"); TBuf<11> phonenumber(number); CPbkContactEngine* iPbkContactEngine = CPbkContactEngine::NewL(); CleanupStack::PushL(PbkContactEngine);//1 //这里打开条目后加锁,以防其它客户端打开 CPbkContactItem* contact = iPbkContactEngine->OpenContactLCX(aContactId); CleanupStack::PushL(contact); //2 //找到需要修改的field TPbkContactItemField* field = contact->FindField(EPbkFieldIdPhoneNumberMobile); CleanupStack::PushL(field); //3 //设置并确认修改 field->TextStorage()->SetTextL(phonenumber); iPbkContactEngine ->CommitContactL(*contact); CleanupStack::PopAndDestroy(2)
附录vCard:
手机应用开发中经常会遇到有关OBEX协议的问题,其实在通信录开发中也遵循这个协议,通信录中的数据是存在一个名叫vCard的载体里。vCard是一类电子名片,得到许多电子设备(如PDA和移动电话等)的支持。vCard的目的是:在这些设备之间用某些协议实现方便的通信录数据传递。可以将vCard编码成MIME规范(RFC 1521)所定义的明码文本。这种编码确保了各种vCard与限制为7位字符集(如在SMS消息中使用的编码)的传递编码的完全兼容。
一张vCard被格式化如下(说实话,下面这个vCard我也没看懂,有看懂的帮忙解释下):
通信录模型中的许
BEGIN:VCARD VERSION:2.1 N:Wilkinson;Steve FN:Steve Wilkinson ORG:EMCC Software Ltd. TEL;WORK;VOICE:01617779700 ADR;WORK;ENCODING=QUOTED-PRINTABLE:;;108 Manchester d.=0D=0ACarrington;Manchester;UK;M31 4BD;United Kingdom LABEL;WORK;ENCODING=QUOTED-PRINTABLE:108 Manchester Rd.=0D=0ACarrington=0D=0AManchester, UK M31 4BD=0D=0AUnited K= ingdom EMAIL;PREF;INTERNET:steve.wilkinson@emccsoft.com REV:20030909T164330Z END:VCARD
多功能都与vCard的处理有关,以保证Symbian应用开发伙伴们能方便地编制符合电子名片及通讯录交换方面的工业标准的代码。