symbian 短信

 一、消息存储基本知识: SymbianOS提供的消息传送架构也是基于Client/Server机制,Server端负责管理手机上的各种消息,在进行消息相关操作之前我们需要了解SymbianOS是如何组织和存储消息的。 手机中的各种消息都是以数据项(Entry)形式供程序操作,数据项有4种类型,SymbianOS为每种数据项提供了相应的常量标识UID,这些UID保存在msvuids.h文件中: 1、文件夹类型,对应常量UID为KUidMsvFolderEntry,和PC上的文件夹系统一样,每个文件夹可以包含其它数据项也可能是其它数据项的子数据项。 2、消息类型,对应常量UID为KUidMsvMessageEntry,它表示数据项是一条消息。 3、附件类型,对应常量UID为KUidMsvAttachmentEntry,它表示某条消息的附件。 4、服务类型,对应常量UID为KUidMsvServiceEntry,服务数据项包含某个消息服务的配置信息,在一般情况还拥有通过该服务收发的消息数据项。 除了上面提到的四种类型UID还有常用到的UID是KUidMsvRootEntry(msvids.h),它指的是根数据项,根数据项包含了4个标准文件夹数据项,分别是收件箱(KMsvGloballnBoxIndexEntryld),发件箱(KMsvgGlobalOutBoxIndexEntryld)、草稿箱(KMsvDraftEntryld)和已发送项(KMsvSentEntryld),另外根数据项下面还包含有各种消息服务项. SymbianOS中的消息服务器负责保存各种类型的数据项,这里有两个基本概念需要了解:消息存储和消息索引。消息存储保存了数据项的数据,保存的数据格式取决于消息服务,服务数据项使用消息存储保存服务配置信息,文件夹数据项不使用消息存储(即存储为空),Symbian提供了CMsvStore类来访问数据项的消息存储;为了节省内存和快速检索消息,消息服务器把数据项的一些概要信息(标题,日期,类型,ID等)写到消息索引中,当消息服务器启动时索引装载到RAM中直到消息服务器关闭,Symbian提供了TMsvEntry类表示数据项索引。 操作消息常用的类和数据类型: CMsvSession:该类代表客户端(客户端MTM、用户接口MTM或者客户端消息应用程序)与消息服务器端的通讯通道即C/S架构中客户端与消息服务器的回话。每一个客户端线程对应一个该类的实例,使用它获得下面将要提到的CMsvEntry上下文对象。一个消息客户端应用必须在正常使用任何MTM或CMsvEntry对象前,使用OpenSyncL()或者OpenASyncL()来新建立一个Session对象。 CClientMtmRegistry:Registry掌握了客户端所有目前可用的MTM有关的细节,消息客户端可以使用该类获得CBaseMtm继承的对象。 CBaseMtm:这个类主要用来操作消息条目,比如可以新建、修改、发送消息条目。 TMsvId:它只是一个TInt32的Typedef,消息服务器为每个数据项分配一个唯一的数据作为标识,除了上面提到的几个固定标识,其它标识都是动态分配的。想要对某个消息进行操作必须先得到他的ID,Symbian中消息相关的大部分函数都会用到TMsvId。 CMsvEntrySelection,是一个可以存储TMsvId的数组,在使用CMsvEntry(CMsvServerEntry)的许多操作中都会作为参数或者返回对象。 TMsvEntry:上面提到过它表示数据项的索引,只包含消息的一些概要信息,主要会用到Id()成员函数得到数据项的标识ID和公有数据成员iDetails、iDesciption和iDate,前面两个成员可以用来获取和设置索引的概要信息,iDate成员可以获取和设置数据项的日期及时间。 CMsvEntry和CMsvServerEntry:可以理解为数据项的上下文(context),这两个类非常类似,只不过CMsvEntry用户客户端,CMsvServerEntry用户实现消息的服务端。它包含两个部分的功能:一是可以允许访问与这个Entry关联的,不同类型的数据(比如可以根据指定ID定位数据项、获取消息存储和消息索引);二是运行访问它的子entry和父entry(当然对新的条目又可以进行一的功能)。 CMsvStore:上面提到过表示数据项的存储,可以通过CMsvEntry(CMsvServerEntry)的EditStoreL(),ReadStoreL()函数取得可编辑存储或只读存储 二、数据项常用操作 因为消息处理建立在C/S架构上,所以在消息操作之前,先要进行一些预处理,大致步骤如下: 1、通过消息会话类CMsvSession连接到消息服务器,建立会话。因为通常连接都采用异步方式,所以为了或许连接的事件通知,实例化CMsvSession对象的类需要继承自MMsvSessionObserver。 2、构造客户端MTM注册对象(通过CClientMtmRegistry::NewL(CMsvSession &aMsvSession)来实现)。 3、构造具体的客户端MTM对象(通过CClientMtmRegistry::NewMtmL(TUid aMtmTypeUid)这里的MtmTypeUID和TMsvEntry内的消息类型ID是一致的)。 4、接下来的操作就是根据具体需求创建、编辑、验证、发送消息条目(如果只是创建和编辑消息条目,则不用如上这么复杂,可省略MTM对象构造)。 下面的消息操作使用了一个CMsvEntry或CMsvServerEntry的指针对象,这两个类提供的基本功能一样,但有一部分函数名会不一样,可以查一下SDK。 1.获得当前数据项索引和ID TMsvEntry oldEntry = iServerEntry->Entry(); //这里iServerEntry应该是CMsvServerEntry TMsvId oldContext = oldEntry.Id(); //如果使用CMsvEntry可以直接使用Id() 2.定位到指定数据项 在更换当前数据项之前通常先保存当前数据项索引ID,更换数据项并完成相关操作后再更换回原来的数据项,这可以避免影响其它函数,是一个很好习惯。 TMsvId oldContext = iServerEntry->Entry().Id(); //使用SetEntry()更换当前数据项到root iServerEntry->SetEntry(KMsvRootIndexEntryId); //具体操作后更换回原来数据项 iServerEntry->SetEntry(oldContext); 3.查找数据项 下面的三个CMsvEntry成员函数都能完成在当前数据项下进行查找的功能: CMsvEntrySelection* ChildrenWithMtmL(TUid aMtm) const; 根据消息服务(MTM)进行查找,查找消息索引对象(TMsvEntry)的成员iMtm等于aMtm的数据项ID。 CMsvEntrySelection* ChildrenWithServiceL(TMsvId aId) const; 根据消息服务ID进行查找,查找消息索引对象(TMsvEntry)的成员iServiceId等于aId的数据项ID。 CMsvEntrySelection* ChildrenWithTypeL(TUid aEntryType) const; 根据数据项类型进行查找,查找消息索引对角的(TMsvEntry)的成员iType等于aEntryType的数据项ID。 CMsvServerEntry与之相对应的三个函数为GetChildrenWithMtm(), GetChildrenWithService(), GetChildrenWithType(),注意的一点是CMsvEntry的三个函数都返回一个CMsvEntrySelection对象的指针,使用完之后我们要负责释放,使用CMsvServerEntry的三个函数需要事先构造一个CMsvEntrySelection对象,用完之后也需要释放。 找出POP3邮箱个数的代码: iMsvEntry->SetEntryL( KUidMsgTypePop3 ); CMsvEntrySelection* sel = NULL; sel = entry->ChildrenWithMtmL( KPkiSmtpTechnologyTypeUid ); TInt cnt = sel->Count(); //获得集合中数据项的个数 delete sel; 4.更改消息索引 TMsvEntry entry = iMsvEntry->Entry(); entry.iDetails.Set( _L( “New details” ) ); iMsvEntry->ChangeL( entry ); //把更改后的数据项索引写回消息索引中去(相当于commit提交) 这里最后的ChangeL很重要,如果不进行该步调用,那么未将修改的消息索引写回到消息存储中。 5.数据项的读写 在进行数据项的读写之前需要使用EditStoreL(),ReadStoreL()函数得到相应的存储CMsvStore通过它提供的接口进行操作。 void CMessageView::ViewMessageL(TMsvId aId) { // Construct the CMsvEntry CMsvEntry* entry = iSession->GetEntryL(aId); CleanupStack::PushL(entry); // Get the messaging store CMsvStore* store = entry->ReadStoreL(); CleanupStack::PushL(store); // Construct the CRichText and restore the body text CParaFormatLayer* paraLayer = CParaFormatLayer::NewL(); CleanupStack::PushL(paraLayer); CCharFormatLayer* charLayer = CCharFormatLayer::NewL(); CleanupStack::PushL(charLayer); CRichText* body = CRichText::NewL(paraLayer, charLayer); CleanupStack::PushL(body); store->RestoreBodyTextL(*body); // Extract body text from CRichText TInt len = body->DocumentLength(); //get length HBufC *buf = HBufC::NewL( len ); TPtr ptrBuf = buf->Des(); body->Extract( ptrBuf, 0, len ); //get data //因为不同的消息的存储格式不同,还可能需要对ptrBuf进行相应的解码才能正常 //显示 delete buf; buf = NULL; CleanupStack::PopAndDestroy(5, entry); } 三、关于SMS的一些特殊操作 1.读取消息条目 //读取发件箱消息条目索引 TMsvSelectionOrdering sort; //全部内容排序,包括隐藏 sort.SetShowInvisibleEntries(ETrue); //设置入口为outbox,也就是发信箱 CMsvEntry* entry = CMsvEntry::NewL(*iSession,KMsvGlobalOutBoxIndexEntryId,sort); CleanupStack::PushL(entry); //选择全部内容 CMsvEntrySelection* entries = entry->ChildrenL(); CleanupStack::PushL(entries); //读取消息索引信息,At(0)代表首信息,取其他的可以给出相应的index TMsvId aEntryId = entries->At(0); //得到消息索引的时间 TTime time; time = entry->ChildDataL(aEntryId).iDate; iSmsMtm->SwitchCurrentEntryL(aEntryId);//iSmsMtm是CSmsClientMtm类型的指针变量,它已经初始化 iSmsMtm->LoadMessageL(); // load the message CRichText& body = iSmsMtm->Body(); //sms的内容存到CRichText控件对象中 TPtrC msg(body.Read(0)); //弹出对话框,提示短信内容 CAknInformationNote* informationNote = new (ELeave) CAknInformationNote; informationNote->ExecuteLD(msg); CleanupStack::PopAndDestroy(); 2.删除消息条目 //删除草稿箱中的消息条目 TMsvSelectionOrdering sort; sort.SetShowInvisibleEntries(ETrue); CMsvEntry* entry = CMsvEntry::NewL(*iSession,KMsvDraftEntryId,sort); CleanupStack::PushL(entry); CMsvEntrySelection* entries = entry->ChildrenL(); CleanupStack::PushL(entries); TInt i = entries->Count(); for(TInt ncount=0;ncount<i;ncount++) { entry->DeleteL(entries->At(ncount)); } // information to the user iEikonEnv->InfoMsg(_L("DeleteAll Done!")); CleanupStack::PopAndDestroy(2); 3.创建消息条目 创建消息条目部分在收件箱和草稿箱两个地方是不一样的,具体代码如下: //在草稿箱中创建短信 const TInt LEN = 12; //转入收件箱 iSmsMtm->SwitchCurrentEntryL(KMsvDraftEntryId); //构建消息索引 TMsvEntry newIndexEntry; newIndexEntry.iDate.HomeTime(); newIndexEntry.iMtm = KUidMsgTypeSMS; newIndexEntry.iType = KUidMsvMessageEntry; //in 3rd edition crashes here if capabilities are wrong newIndexEntry.iServiceId = iSmsMtm->ServiceId(); newIndexEntry.iDetails.Set(aName);//aName为收件人名称 newIndexEntry.iDescription.Set(aContent.Left(LEN)); newIndexEntry.SetUnread(ETrue); iSmsMtm->Entry().CreateL(newIndexEntry); TMsvId smsId = newIndexEntry.Id(); iSmsMtm->SwitchCurrentEntryL(smsId); //填充消息存储区 iSmsMtm->AddAddresseeL(aAddr);//aAddr为收件人号码,信息位于消息头中 CRichText& body = iSmsMtm->Body();//消息正文 body.Reset(); body.InsertL(0, *iSmsContext); //提交保存 iSmsMtm->SaveMessageL(); 注:当然如果要发送的短信,还需要进行具体的设定,这部分将在发送消息里面详述,这里只是做为普通的创建消息,与收件箱中消息进行比较,具体见下面代码示例。 //在收件箱中创建短信 const TInt LEN = 12; //转入收件箱 iSmsMtm->SwitchCurrentEntryL(KMsvGlobalInBoxIndexEntryId); //构建消息索引 TMsvEntry newIndexEntry; //newIndexEntry.iDate.HomeTime();//收件箱短信索引头在CSmsHeader中设置 newIndexEntry.iMtm = KUidMsgTypeSMS; newIndexEntry.iType = KUidMsvMessageEntry; //in 3rd edition crashes here if capabilities are wrong newIndexEntry.iServiceId = iSmsMtm->ServiceId(); newIndexEntry.iDetails.Set(aAddr); newIndexEntry.iDescription.Set(aContent.Left(LEN)); newIndexEntry.SetUnread(ETrue); iSmsMtm->Entry().CreateL(newIndexEntry); TMsvId smsId = newIndexEntry.Id(); iSmsMtm->SwitchCurrentEntryL(smsId); //填充消息存储区 //CSmsHeader& header = iSmsMtm->SmsHeader(); //header.SetFromAddressL(aAddr);//这里我采用了两种方法,均不能正确设置发件人号码 //iSmsMtm->AddAddresseeL(aAddr);//后来才知道这里为收件人号码,所以必须在以后修改 CRichText& body = iSmsMtm->Body(); body.Reset(); body.InsertL(0, *iSmsContext); //提交保存 iSmsMtm->SaveMessageL(); //完善消息头,设置发件人号码和发送时间 CMsvStore* messageStore = iSmsMtm->Entry().EditStoreL(); CleanupStack::PushL( messageStore ); CSmsHeader* hdr = CSmsHeader::NewL( CSmsPDU::ESmsDeliver, body ); CleanupStack::PushL( hdr ); hdr->SetFromAddressL(iNumber); TTime nowTime; nowTime.HomeTime(); hdr->Deliver().SetServiceCenterTimeStamp(nowTime); hdr->StoreL(*messageStore); messageStore->CommitL(); CleanupStack::PopAndDestroy(hdr); CleanupStack::PopAndDestroy(messageStore); // 修改当前消息索引为只读,这样收件箱列表处浏览会有回复选项 //但是如果在之前就设置ReadOnly就会导致SaveMessageL出错 newIndexEntry.SetReadOnly(ETrue); //消息索引提交更改 iSmsMtm->Entry().ChangeL(newIndexEntry); 4.发生消息条目 其实发送消息可以使用客户端MTM方法,但是一般都是在活动对象中实现,或者实现Send-As API、CSendAppUi类来实现。以下代码简单给出客户端Mtm的方法: //发送消息 //iMtm是在新建sms阶段设定 TMsvEntry msvEntry = iMtm->Entry().Entry(); //重新设定TMsvEntry msvEntry.iDetails.Set(iRecipient->Des()); // set recipient info in details msvEntry.SetInPreparation(EFalse); // set inPreparation to false msvEntry.SetSendingState(KMsvSendStateWaiting); // set the sending state (immediately) msvEntry.iDate.HomeTime(); // set time to Home Time // 得到sms内容 CRichText& mtmBody = iMtm->Body(); mtmBody.Reset(); mtmBody.InsertL(0, KGDSMSTag); //插入我们的短信内容 // 使用CSmsClientMtm类处理sms CSmsClientMtm* smsMtm = STATIC_CAST(CSmsClientMtm*, iMtm); smsMtm->RestoreServiceAndSettingsL(); //CSmsHeader封装sms消息的参数,像服务中心号码和发送设定 CSmsHeader& header = smsMtm->SmsHeader(); //CSmsSettings类用来详细设定sms Header CSmsSettings* sendOptions = CSmsSettings::NewL(); CleanupStack::PushL(sendOptions); sendOptions->CopyL(smsMtm->ServiceSettings()); sendOptions->SetDelivery(ESmsDeliveryImmediately);//设定立即发送 header.SetSmsSettingsL(*sendOptions); //检查服务中心号码有效性 if(header.Message().ServiceCenterAddress().Length() == 0) { // 如果没有设定,则查找默认中心号码 CSmsSettings* serviceSettings = &(smsMtm->ServiceSettings()); //中心号码列表为空 if(!serviceSettings->NumSCAddresses()) { // 错误消息 iEikonEnv->InfoWinL(_L("No service center number"),_L("cannot send this one.")); } else { // 设定为默认服务中心号码 CSmsNumber* sc = 0; sc = &(serviceSettings->SCAddress(serviceSettings->DefaultSC())); header.Message().SetServiceCenterAddressL(sc->Address()); } } CleanupStack::PopAndDestroy(); ... ... CMsvEntrySelection* selection = new (ELeave) CMsvEntrySelection; CleanupStack::PushL(selection); selection->AppendL(movedId); // 添加我们要发送的sms,movedId在省略部分有定义,是TMsvId型变量 // 调用的这个函数是用于发送的,具体的代码后面介绍 SetScheduledSendingStateL(selection); CleanupStack::PopAndDestroy(); // selection return ETrue; // 到这里sms已被发送 --------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------- void SetScheduledSendingStateL(CMsvEntrySelection* aSelection) { CBaseMtm* smsMtm = iMtm; // 添加entry到任务列表 TBuf8<1> dummyParams; CCommandAbsorbingControl::NewLC(); CMsvOperationWait* waiter = CMsvOperationWait::NewLC(); waiter->Start(); // 这个函数是关键 CMsvOperation* op= smsMtm->InvokeAsyncFunctionL(ESmsMtmCommandScheduleCopy, *aSelection, dummyParams, waiter->iStatus); CleanupStack::PushL(op); CActiveScheduler::Start(); //开始时间表中任务 CleanupStack::PopAndDestroy(3); // waiter, op, CCommandAbsorbingControl } 6:22 | 添加评论 | 阅读评论 (1) | 固定链接 | 写入日志 | 计算机与 Internet3月1日活动对象框架原理一、概述: Symbian OS是一个多任务的操作系统,那么为了实现多任务,同时使系统能够快速响应,高效的进行事件处理,并减轻应用程序员的工作负担(申请大多数耗时的操作(例如文件系统)由服务提供器来完成,服务提供器完成程序员提交的请求后,将会返回给程序员一个成功或失败的信号。),Symbian OS特意引入了活动对象的概念。 服务提供器API具有函数的异步和同步版本,供客户应用程序使用。所谓同步是指,客户提交请求后,处于等待状态,等待服务提供器返回成功或失败的信号后,然后在进行其他操作;所谓异步是指,请求完成,即返回信号之前,调用者也许会继续执行其他的处理,或者只是简单的等待。在这里的等待,也可以称为“阻塞”,信号就是一个事件,我们的代码就是事件驱动的。为了实现多任务,一般我们使用异步API。 一般操作系统为了实现多任务,往往使用多线程实现,当然,Symbian也是支持多线程的。但是,在同一个线程中运行的活动对象之间进行切换的代价要比线程上下文的切换代价低,这使得对于各种资源比较紧张的Symbian OS来说,使得活动对象更适合事件驱动多任务。 注意: (1)线程间上下文切换和同一线程的活动对象之间传递控制权,在速度上的差别可能会有10倍之差,另外,一个线程大约在内核中有4KB的空间开销,在用户空间上有8KB的用于程序栈的空间开销,而一个活动对象的开销可能只有几百字节,甚至更小。 (2)虽然在一个线程内的活动对象是非抢占式地协同运行的,但在它们所在的线程却是抢占式调度的。 二、概念:一个活动对象必须派生自基类CActive class CActive : public CBase { public: enum Tpriority { EPriorityIdle = -100; EPriorityLow = -20; EPriorityStandard = 0; EPriorityUserInput = 10; EPriorityHigh = 20; } public: IMPORT_C ~CActive (); IMPORT_C void Cancel ();//删除未完成请求的函数 …… IMPORT_C void SetPriority (TInt aPriority); Inline TBool IsActive () const; …… protected: IMPORT_C CActive (TInt aPriority); IMPORT_C void SetActive (); virtual void DoCancel () = 0;//两个纯虚函数,继承类必须实现它们 virtual void RunL () = 0;//处理函数 IMPORT_C virtual TInt RunError (TInt aError); public: TrequestStatus iStatus;//代表请求状态 …… private: TBool iActive; …… } 通过上面的CActive声明可以看出:活动对象和线程类似,构造时也会有一个优先级值来决定它们如何被调度,通常为活动对象提供一个标准优先级EPriorityStandard。当活动对象响应的异步服务完成时,就会产生一个事件。活动调度器会侦测到事件,并决定每个事件对应的是哪个活动对象,然后调用恰当的活动对象去处理事件。当活动对象处理事件时,直到事件处理函数返回到活动调度器,该对象都是无法被抢占的,也就是说,RunL()事件处理函数是一个原子操作。 在Symbian OS中,活动对象相互协作并顺序的实现多任务,也不需要对共享的资源进行同步保护。另外,因为活动对象在同一个线程中运行,所以可以更容易地共享内存和对象,尽管活动对象存在于同一线程,但它们仍然是各自独立运行的,这就好像同一个进程中的线程是独立运行的一样。 三、关于活动对象基类CActive的几点说明:参照上面CActive的声明 1、必须在发布异步请求后调用SetActive(),否则活动对象规划器在搜索已完成的活动对象时忽略它,从而导致错误。需要说明的是,在CActive这个基类中,并没有任何实际的函数用来发布异步请求,我们自己必须编写这种函数,通常取名为StartL()。 2、DoCancel()是个纯虚函数,必须实现该函数以提供未完成请求所需的功能。但是,需要注意:绝对不应该直接调用该函数,应该总是使用Cancel(),该函数调用DoCancel(),同时确保设置必须的标志,从而表明请求已完成。 3、RunL()是原子操作,当它被活动规划器调度后,相同线程里,其他任何RunL()都不可以运行,直到这一RunL()完成并返回,因此该方法必须简短,否则,用户就会感到等待事件较长,手机好像死机了一样。 4、如果RunL()异常退出,则调用RunError()(由活动规划器调用),它为活动对象提供处理自身错误的机会。如果能够处理错误,RunError()就应该返回KErrNone;否则,它应该只是返回作为参数传递的错误码,在这种情况下,将错误传递到活动规划器的Error()函数,默认行为是导致严重错误。 5、iStatus实际上只是一个封装的整数,用于表示异步服务提供器返回的状态或错误码。活动对象发出请求后,服务提供器的第一个任务是将iStatus设为KrequestPending,当请求的服务完成时,服务提供器将iStatus的值设为KErrNone(如果请求成功完成)或错误码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值