说到Symbian,确实让人头痛。不仅开发平台和SDK版本众多,难以选择,而且对程序员确实要求很高,光是Symbian C++的熟悉就要花上很长时间,更麻烦的是测试和调试。模拟器只能提供一部分功能,和电话通信有关的全部要在真机上测试。很多时候,在模拟器上能跑的代码,放到真机上就不行了,这其中的心酸想必开发过得朋友深有体会。

小弟我因为工程实践项目的要求,和几位嵌入式的高手一起搞了Symbian来电通项目。其实来电通项目已经有很多人做了,比较有名的是CallMaster和柳丁,但是这方面的关键技术和源码至今没有人公开,这在很大程度上增加了这个项目的难度,我们只好白手起家,也算是一次真正的项目历练(因为很多项目的关键技术和源码一般都不会给你,只有自己研究,^_^)。我在项目中负责两个核心模块的实现。一个是监听模块,另一个就是归属地查询模块。我把这一个多月的成果全部总结了一下,写成技术文章,提供给大家参考。希望想从事这方面开发的朋友能有一些借鉴作用,少走一些弯路,我就感到非常欣慰了。

以下是最核心的部分,归属地数据库的设计和实现,文中可能存在不少问题,欢迎高手们指正,向Symbian高手学习。

 

说明,我们的开发环境是Carbide C++

SDK s60 FP2 CW

 

 

归属地查询模块

归属地模块的主要功能可以划分为两个部分:一是当监听模块获取号码后,自动查找归属地数据库;二是由用户在本地自由查找,可自由选择组合(手机,固话等归属地)。但是两个功能的核心和难点都在于归属地数据库的建立,下面结合图例重点阐述。

整个数据库的建立大致分为三个步骤,见图示:

 

 

 手机来电通核心模块——归属地数据库设计(Winsym原创)-编程知识网

 

一、原始数据的采集,整理。这一步骤主要从网上下载我国目前手机号码的归属地数据库原始数据,我们选用的是access格式的原始数据,但其只有一张表且存在大量冗余信息,原始数据截图如下:

 手机来电通核心模块——归属地数据库设计(Winsym原创)-编程知识网

 二、对原始数据进行分析和总结不难看出,citycardtype字段中存在大量冗余信息。例如:北京市和北京联通GSM卡被存取了很多次,造成了大量的重复。所以,我们必须要设计出一种优秀的表结构格式(在满足功能的前提下,最大限度地消除冗余信息,采用数据库设计相关理论,达到3范式设计要求)。共设计四张表:

1 Phone

2 CityName

3 CardName

4 Zone

 

具体字段如下:

  

 

表名

字段名

类型

长度

说明

Phone

number

     int

 

手机号码段,主码唯一标示

 

cityid

  int

 

归属城市编号

 

cardid

  int

 

卡类型编号

CityName

cityid

int

 

归属城市编号,主码

 

cityname

text

50

归属城市名称

CardName

cardid

int

 

卡类型编号,主码

 

cardname

text

50

卡类型名称

Zone

zonecode

text

10

二级城市归属地区号 联合主码

 

zonename

text

50

二级城市归属地名称 联合主码

 

 

在实际应用过程中,查找效率和安全问题是我们必须要考虑的两个主要问题,为此我们采取了以下解决方案:

对于查找效率问题,我们采用了建立索引机制。由于Phone表中的记录的规模(大约有15万条记录),为了提高查找效率,对表Phone中的字段numberCityNamecityid字段,CardNamecardid字段分别建立索引。相应Sql语句可参考以下:

CREATE unique INDEX numIndex ON Phone(number)

CREATE unique INDEX cardidIndex ON CardName(cardid)

CREATE unique INDEX cityidIndex ON CityName(cityid)

 

对于安全问题,我们使用了S60 2nd DBMS专有的权限加密机制,相当于SQL Server数据库的登录密码,最大限度的保护数据库访问的安全和数据库设计专利。具体实现可参考以下代码片段:

// Open the contacts database using the Encrypted format

    _LIT(KPassword, "I Fu le you");

    //useing decrypted database

    CSecurityBase* securityBase = Security::NewL ();

    securityBase->SetL (TPtrC (), KPassword); //Set the password for decryption

    //Get the key

    CSecurityDecryptBase* decryptBase = securityBase->NewDecryptL (TPtrC8 ());

    CleanupStack::PushL (securityBase);

    User::LeaveIfError ( myDatabase.Open (myDbs, KFile, TPtrC (), decryptBase,myDatabase.EReadOnly));

 

为了方便Sql脚本的提取,以上设计均在SqlServer2000数据库中完全实现。四张表之间的关系可参考以下截图:

 

 手机来电通核心模块——归属地数据库设计(Winsym原创)-编程知识网

 

三、最后一步是将SqlServer2000平台上的归属地数据库顺利地导入到Symbian专用的 DBMS中。这里存在两个难点,一是Symbian数据库的建立,API的使用;二是海量数据的导入。

对于第一个问题,Symbian为我们提供了两组API
1. RDbStoreDatabase 提供了专有的创建和打开数据库的接口,这样的数据库是不能共享的,数据库以文件的形式存在,所以它又称为客户端访问。
2. RDbNamedDatabase 提供了用名字和格式标识的创建和打开数据库的接口,这个类允许客户端(专有)和服务的共享数据库访问。

考虑到程序的兼容和开发的方便,我们使用的是RDbNamedDatabase,他可以轻松地创建我们想要的数据库,并利用Symbian支持的SQL子集,进行相应的建表,建立索引,查询等操作,完成相应功能需求。代码片段如下:

RFs myDbs;

    // handle for our database

    RDbNamedDatabase myDatabase;

    // we have to connect to the DBMS server first

    User::LeaveIfError( myDbs.Connect());

    myDbs.MkDirAll(KDirName);

    //handles use the cleanup stack

    CleanupClosePushL(myDbs);

    _LIT(KPassword, "I Fu le you");

    //useing encrypted database

    CSecurityBase* securityBase = Security::NewL();

    securityBase->SetL(TPtrC(), KPassword); //Set the password for encryption

 

    // Get the key

    CSecurityEncryptBase* encryptBase = securityBase->NewEncryptL(TPtrC8());

   

    User::LeaveIfError( myDatabase.Create(myDbs,KFile,TPtrC(),encryptBase));

    //handles use the cleanup stack

    CleanupClosePushL(myDatabase);

   

    _LIT(KSQLCreatePhone, "CREATE TABLE Phone(number  integer ,cityid  unsigned smallint,cardid  unsigned smallint)");

    _LIT(KSQLIndex,"CREATE unique INDEX numIndex ON Phone(number) ");

    _LIT(KSQLIndex1,"CREATE unique INDEX cardidIndex ON CardName(cardid) ");

    _LIT(KSQLIndex2,"CREATE unique INDEX cityidIndex ON CityName(cityid) ");

   

    _LIT(KSQLCreateCardName,"Create Table CardName(cardid unsigned smallint,cardtype varchar)");

    _LIT(KSQLCreateCityName,"Create Table CityName(cityid unsigned smallint,cityname varchar)");

    _LIT(KSQLCreateZone,"Create Table Zone(zonecode varchar,zonename varchar)");

   

    //create Master tables

    User::LeaveIfError(myDatabase.Execute(KSQLCreatePhone));

    User::LeaveIfError(myDatabase.Execute(KSQLCreateCardName));

    User::LeaveIfError(myDatabase.Execute(KSQLCreateCityName));

    User::LeaveIfError(myDatabase.Execute(KSQLCreateZone));

 

    User::LeaveIfError(myDatabase.Execute(KSQLIndex));

    User::LeaveIfError(myDatabase.Execute(KSQLIndex1));

    User::LeaveIfError(myDatabase.Execute(KSQLIndex2));

 

对于第二个问题,我们采取的方法是,将所有Sql Server2000数据库各表格的数据,全部转成sql脚本,并利用Symbian提供的文件系统相关的API,读取每一行sql语句并执行,最终在电脑模拟器上生成数据库文件dbms.db。经实际操作,这种方法比在手机上生成可以大大节省数据库生成时间,效果显著。相应的sql脚本截图如下:

 

 手机来电通核心模块——归属地数据库设计(Winsym原创)-编程知识网

 

读取的代码片段如下:

RFile myfile;

    //open file with RFile

    User::LeaveIfError(myfile.Open(myDbs,KFileName1,EFileRead));

    CleanupClosePushL(myfile);

   

    //Reads  single lines of text to or from a file

    txt.Set(myfile);

    TBuf16<60> textsql;

    //TDesC16 sqlRow;

    while(txt.Read(textsql) != KErrEof  )

    {

       //console->Printf(textsql);

       User::LeaveIfError(myDatabase.Execute(textsql));

       //console->Printf(_L("/n"));

       textsql.Delete(0,textsql.Length());

    }

全部完成后,就可以利用SQL语句查询了,哈哈,好爽啊!