• 纪念一个曾经的软件产品(七)——天气,短信,邮件


    [回目录]

    又断开了很长一段时间,我觉得再不动手写写这个,怕以后就难有机会了,上次之所以写着写着就断开的原因,我记得是因为这篇要写的内容(短信模块)太过复杂,我回头去看这些代码的时候都觉得头大了,其实,后来我想想也没必要重新再把代码检阅一次,这样意义不大,因为这些代码估计真是再无人问津的了,主要还是整理下思路,思路永远不会过时,经历和经验同样不会过时。所以接下来的内容还是以“讲故事”为主。

    9.4 天气

    在智能手机时代之前,天气这个功能通常是用短信实现的,那时候还需要你花钱订阅,后来智能手机出现了,但还没现在的那么丰富多彩,我虽然用智能手机数年,但限于当时的硬件水平和网络状况,都没有用到天气这种功能,我一开始接触这种功能的时候,还有些不屑,我说:“这么关心天气干嘛?难道下雨就不用上班了吗?既然天晴和下雨没啥两样,那就别浪费时间看‘天气’了。”但后来用上了这个功能之后,发觉还真有些离不开,有事没事就拿出来刷刷天气。(传说中的“强迫症”?)但我不得不说,现在大多数天气预报软件都是不准的,说下雨结果不下,说要来台风,结果只是天阴了一会儿,说多云,结果却下起了雨……这种不准的情况实在太多了,天气预报,仅供参考,但准确率这么低,我看连参考价值都不大啊,但不管怎么说,这确实手机最常用的功能之一,现在几乎没有哪个智能手机用户不预装天气软件的,就怕你不用而已。

    SoSoPi的天气功能

    天气是SoSoPi唯一一个需要访问网络的功能,前面提到过,网络访问在现在的iOS及Android设备上根本不需要考虑的问题,但在Windows Mobile上真是个大问题,这是有历史原因的。Windows Mobile出现得相对较早,早期的手机很多是不带WIFI功能的,需要使用电话数据网络(也就是GPRS或CDMA),这种2G网络的特点是速度慢(啊,我真感觉比过去用56K的猫拨号上网还慢),而且,关键是流量费还很贵,用户使用起来真是心里很不踏实,很害怕自己一个不小心就会被扣掉好多数据流量费,所以用户时刻要关心自己的电话数据网络是否打开着,如果没打算用网络的话,还是关掉吧。我曾经就是这么一个“流量神经过敏”的人,老吴说我很out,因为他很早就换3G了,当然了,我换了3G之后这种流量神经过敏的症状也自动消失了。

     

    (SoSoPi的各种天气图标,要是不加文字说明,这些图标还真不太看得出来是什么天气)

    关于SoSoPi的网络访问相关的功能,老吴的想法很多,这里顺便说说吧,反正这系列文章就是讲故事。老吴考虑的使用网络的功能至少有这些:

    • 在线皮肤搜索与下载
    • 在线电子书搜索与下载
    • 用户每日签到与信息分享
    • 家园(这个功能我也不是很清楚究竟是什么,大概是一个在线社区功能,如今的SNS?)

    结果这些功能都没做,只有一个天气做出来了,做不来有以下两个原因:

    1. 所有的需要网络访问的功能都需要服务器的支持,我们的技术积累不足
    2. Windows Mobile的网络访问能力实在太“纠结”(接下来会提到)

    天气模块的核心功能当然是要去获取数据。我们遇到的一个大问题就是天气源的问题,因为我们不可能有一个气象站,由于老吴还考虑把SoSoPi国际化,所以一开始选了雅虎天气,当然了,雅虎天气不可能直接开放接口给我们用,我们只能到它的网页去抓数据,这是同事实现的功能。这之间大概是这么一种关系:

    用户向SoSoPi网站提起请求,要获取某个城市的天气信息,网站会到数据库里查询最近有没有更新那个城市的天气信息,如果有,就以我们事先约定好的格式返回给SoSoPi,如果没有则尝试到雅虎天气去抓取那个城市的天气信息,并存到数据库里。简单地说,要什么才更新什么,更新过的就直接返回结果,这样的设计很大程度上已经够用了。

    另外,不得不说的是,我们向雅虎天气网站抓数据这种行为后来不管用了,没多久之后我们服务器的IP地址就被雅虎天气屏蔽了,再也抓不到他们的数据,我后来想,如果要做“股票”这个模块的话恐怕也会面临这种尴尬,就是别人的网站不配合了,我们就一点办法都没有,也许你说可以向雅虎天气购买使用权嘛——这个价格估计不便宜,而SoSoPi只是一个不盈利的应用程序。后来怎么解决的?换呗,此处不留爷,自有留爷处,换到weather.com.cn了,这样对国外的城市的支持就不太好,国内嘛,还行,幸运的是weather.com.cn一直没封我们,而且网页也不怎么改版,一改版我们的程序也要跟着变一下的,这个相信大家都能理解。

    用户更新天气时候所指定的城市得用ID,而不是用名称,因为名称不精确,有可能重名,所以用户在添加城市的时候,是通过输入城市的名称(不输全也行),从服务器上获取含有他输入内容的城市的列表,供用户选择。比如上海的编号是“101020100”,要抓去上海的天气,就到这个地址去:http://www.weather.com.cn/weather/101020100.shtml。抓到了网页,就分析上面的数据,得出未来几天天气的情况。

    我和Web的故事

    当初在开发这个天气功能的时候,我不了解Web开发。对于Web开发,我的看法也是颇有起伏,这要从大约1999年说起,那时候刚接触网页,自学了点HTML,不懂什么div和css,只知道写几个文本格式控制标签,知道如何嵌入图片,大致就这样,自觉得很有趣,后来还参加了大学里的“网页设计大赛”,我和几个同学一起做,其实也就一些页面,几乎是纯粹的编辑,没涉及到什么动态的内容,虽然当时ASP(不带.net)已经很流行了,结果还不错,获得了一个“优秀奖”(我没记错的话),但很快我就觉得Web开发过于简单,不是自己想追求的东西,于是就自学别的内容去了,这种想当然认为“Web太简单”的思想伴随了我很久。事实上,Web技术也在不断发展,而且发展速度惊人,我的思想却一直这样止步不前,停留在2000年。

    直到2006年,由于我当时所在的公司的业务调整,我又有机会接触Web开发,那时候已经是ASP.net的天下了,.net 2.0大行其道,当然Java和PHP也是相当火,我想想:“好吧,反正借机了解一下,反正我估计我也呆不了太久了。”稍微动动手之后,我才觉得,一个正式的商用网站,根本没办法使用学校里“网页设计大赛”的方法来做的,而一向对自己的美工能力颇为自负的我,终于知道了自己其实根本不能充当“美工”,后来没多久,由于一些矛盾,我被逼离开了那公司,所以Web开发这方面的技术我没有再接触了,那时候也没学到什么东西,时间一长,就忘了,总体来说,我还是认为Web技术比较简单,我还是走自己的路,别多想了。

    再次接触到Web开发,就是到了做SoSoPi天气的时候了,由于之前的知识上的局限,我第一想到的不是借助Web服务,而是自己写一个天气更新的服务器,用Socket——C++程序员的思维啊!其实Socket编程是挺难的,出了问题还不知道究竟上哪里找,以前我做游戏服务器的时候就留了一个问题,到我离职的时候都不知道怎么回事,就是不定期掉线,我们几个工程师费尽心思都查不出到底是什么导致了掉线;而借助IIS或Apache这种Web服务器,这些底层问题基本都没了,对开发者的要求也低了不少,开发一个稳健的服务器变得很简单了。很快地,我发现公司里没其他懂C++和Socket,大多数只有ASP.net的经验,所以我就放弃了做用Socket做服务器的打算。

    又好多年过去了,时过境迁,Web技术并没有没落,反而越来越火,它现在无处不在,尤其随着移动互联网这几年的兴起,它渗透到了我们生活中的各个角落,它所具备的客户端零配置和跨平台的强大特性,甚至有取代许多传统应用的迹象,我早就不认为“Web太简单”,没技术含量,相反,我现在觉得它会在未来大放光彩。

    直接使用API访问网站

    SoSoPi天气遇到的最大最大的问题恐怕就是网络连接的问题,Windows Mobile的网络连接设置相当复杂,现在用惯了iPhone或Android的人恐怕会为之惊讶……

    经过一些研究,我发现HTTP其实也是对底层的一个更高级的封装,那能不能这样,服务器端还是用ASP.net,而我直接用C++的代码去获取其内容,然后再分析,只要格式固定,应该是没有问题的,事实上,最早我就是这么干的,而且貌似能行得通,我让Web开发人员把天气信息弄成我们自定义的格式,然后直接调用Windows Mobile提供的HTTP相关的API去获取,这个调用的过程跟现在用jQuery AJAX去调用Web服务相比,复杂程度的差距真是天壤之别,(啥?你问我哪个更复杂?)大致涉及到这些API的使用:

    • InternetOpen
    • InternetSetStatusCallback
    • InternetConnect
    • HttpOpenRequest
    • HttpSendRequest
    • InternetReadFileEx
    • InternetCloseHandle
    • WaitForSingleObject(以上操作大多是异步的,需要等待其完成)

    以上这些API有严格的调用次序要求和依赖关系,一环扣一环,其中几个还会返回一个句柄(handle),出现异常的时候也需要正确释放句柄(释放也要讲次序),否则可能导致程序死锁甚至死机(Windows Mobile问题很多),好吧,等你把这些逻辑都理清了之后,你发现:咋我的程序还是根本不能访问网络?因为在所有这些之前,你还需要做一件事:打开网络链接。

    之前提到过,Windows Moible有一套自己的API用于管理网络链接,这套API叫“Connection Manager”,又是一套让人心烦意乱的API:

    • ConnMgrMapURL
    • ConnMgrProviderMessage
    • ConnMgrEstablishConnection
    • ConnMgrConnectionStatus
    • ConnMgrReleaseConnection
    • WaitForSingleObject/WaitForMultipleObjects(猜对了,又是异步的)

    和前面一样,每个函数都带有一大坨参数,要完全了解这些参数的含义是很难的,涉及到太多网络方面的专业术语,调用次序,异常处理等同样让人头疼,所幸的的是网上有人提供了一个写好的类,对这些API进行了一个封装,可以用,尽管并不完美,得自己改改(其实,我从来没发现有什么C++类下载下来一点不改就能直接用的)。

    网络访问这部分代码乱得就像一团麻,再加上Windows Mobile严重的碎片化问题,网络访问的问题就更加显著。所幸的是我后来发现一个特点:如果能用IE打开baidu.com,那就能用SoSoPi更新天气,所以后来一旦有用户出了问题,说什么天气更新不了,我就说:“能用IE打开百度么?”言外之意是:这问题你得自己想想办法。但用户还是很诧异:我用UCWeb没问题啊,究竟怎么回事?——我也不知道。因为这里面涉及到太多太多设置的问题了,如果你对这些名词也不陌生的话,估计你对那一大堆问题就有些感性认识:CNNET,CNWAP,UNINET,UNIWAP,代理,Internet设置,单位设置,调制解调器,电话线路(GPRS)……在iPhone面前,Windows Mobile为了能上网而需要进行的这一堆设置看起来就像一坨翔,即便是像老吴这么资深的玩家,也不能完全理解其中各种设置的含义,更解决不了所有的上不了网的问题。这样,你就会明白乔布斯的“don't make user think”的格言是多么的必要和伟大。另外关于Windows Mobile的上网设置,我有篇博客:Windows Mobile上网设置详细图解

    最先的时候,为了让天气更新尽量少影响SoSoPi的运行,我是另外写了一个程序(SSPWeather.exe),当用户更新天气的时候,就先开一个线程,这个线程启动这个程序,并等待这个程序的进程的结束。SSPWeather.exe的执行结果写在一个本地的XML文件中,程序结束后(前面开的那个线程不是一直等待它结束么),等待线程会发消息通知主线程,主线程收到消息后就去读取这个XML文件,把天气的信息重新载入。这是大致的过程,另外,我还得防止用户启动多个SSPWeather.exe,如果SSPWeather.exe已经启动,那么“更新”按钮就应该显示为“更新中...”而不是“更新”。

    使用WebService

    后来,我对天气更新网络访问部分进行了一些改进,因为我后来了解了“Web Service”这个东西,我最早的对于Web Service的理解就是:本质上就是个网站,只不过不是用网页,而是用特定格式的XML作为数据交换而已。其实我现在对Web Service的了解也比这个强不去哪里……Web Service有着严格的格式定义,字符是字符,数字是数字,我认为它是更好的解决方案,而不是自己定一个格式。另外一个促使我改进的原因是貌似这种通过直接调用API的去获取HTTP的数据的方式并不怎么稳定,时不时会出现更新天气失败的情况,而且我找不到原因,我想换成Web Service或许能解决这个问题。(虽然后面发现这个问题还是存在)

    我前面说的对Web Service的理解是比较片面的,Web Service其实并不简单,相反,我现在认为它是一套过于重量级的技术,其所遵循的SOAP协议也过于繁琐,所以现在做数据交换的时候使用得越来越多的是JSON格式的数据,而不是XML了。

    在Java和.net平台下,有直接对Web Service的良好支持,比如.net,可以很轻松使用ASP.net创建出Web Service服务,当然也可以采用比较新的WCF,Visual Studio也提供了相应的工具,根据WSDL(WebService接口定义)来生成访问客户端代码。但,C++则没那么幸运了,Visual Studio并不提供直接的支持,甚至都不支持XML的操作(Visual Studio 2008),经过一阵子的研究,我最后选定了一套叫gSOAP的类库,这是一套跨平台的C++类库,用于帮助我们实现Web Service,这套类库现在还在更新,真是难得。

    gSOAP用起来并没有预想的那么顺畅,用它提供的工具生成的客户端代码用起来也是出现了一些问题,甚至都编译不过,这很大程度上是由于平台的差异引起的,你们觉得最正统的C++代码一般都是什么平台上的?肯定是Unix系平台的嘛,Unix系平台上有海量的C++开源项目,其中许多对世界产生了巨大影响力,Unix/C++这无疑才是真正的程序员的配置,不幸的是,一些Unix系平台的标准库在Windows平台下都没有被很好地实现,更别说Windows Mobile了。gSOAP号称支持Windows CE,Windows Mobile采用的是Windows CE的内核,但在程序集合上确实有有着明显的差距,所以在引入的过程中也要修改些东西,我后来写了一篇博客来讲述gSOAP在Windows Mobile下的使用:gSOAP在Windows Mobile平台上的使用总结

    WebService的协议当然是我来定,当时我还专门到网上去找资料自学了两天的WebService,什么XML,DTD,Schema,SOAP,WSDL啥的,我了解到,接口其实就是用WSDL来描述的,于是我手工创建了一个WSDL文件,交给做ASP.net的同事,我说,接口就在这里,你来实现这个服务,但他坚持说:“没有这种做法的。”后来我才了解到,从C#代码到WSDL,是可以的,反过来却不行,微软没提供这种功能,即便后来的WCF也没这种功能,最后只好作罢,我只告诉他功能要求。使用了gSOAP之后,我就把SSPWeather.exe去掉了,因为后来发现不用另开一个进程,开一个线程用于天气更新即可,不会有什么影响。至于用了gSOAP之后,更新失败的次数是不是减少了,我观察下来是的,但这个问题没法最终解决。从技术上来说,gSOAP最后也是调用Socket API来实现的,它表现比我之前的代码稳定,说明它代码的质量比较高,现在各种平台下都有对网络很好的封装的库,很少有必要自己再去直接使用Socket API,Socket API真的不太易用。

    功能其实很简单,只有两个方法,一是查询城市代码,二是获取天气信息。前者的输入参数只有一个字符串,WebService会返回一个城市列表,比如我输入“上”,那有可能返回“上海(101020100)”、“上饶(101240301)”、“上虞(101210503)”……等;后者的输入参数则是9位城市编码(可以是多个),然后返回相应的城市的天气情况的清单。

    自动更新

    天气模块还有一个“理所当然”,但技术上却很麻烦的功能——自动更新。很多用户喜欢一起床就看看手机上的天气,而且是直接看到天气的情况,他不希望还需要手动点一下“更新”,这个需求其实就变成了:隔一段时间就去更新一下天气。一开始,我们是把时间设为每天7:00更新一次,接下去4个小时更新一次这种方式,但这种方式有严重的问题,大家想想看?——严重的问题就是大量的用户会集中在7:00这个时候向服务器发请求,导致服务器处理不过来,更新失败。于是我后来重新设计了一套稍复杂的规则:更新间隔让用户设置,可以设为1、2、6、12和24小时,更新时间则不固定,比如用户设定4小时更新一次,现在时间是16:40,用户点了一下更新,那下次自动更新的时间就是20:40(如果更新成功的话),如果自动更新不成功,那会过10分钟再尝试一次。其实上面这些都不是很“麻烦”,真正麻烦的地方是:自动更新这个动作很多时候需要在用户手机待机的时候执行,这个才是最要命的。

    待机,就意味着CPU处于闲置(idel)的状态,任何程序都是无法运行的,我必须想办法将手机唤醒,通过一些特殊的方法,Windows Mobile提供了一个特殊的API——“CeRunAppAtTime”,可以实现这个功能:

    void RegisteTimeEvent(BOOL bReg, SYSTEMTIME* pst)
    {
         TCHAR szBuff[MAX_PATH];
         GetModuleFileName(NULL, szBuff, MAX_PATH);
         MakeItAPath(szBuff);
         StringCchCat(szBuff, MAX_PATH, TEXT("SSPWakeUp.exe"));
         if(bReg)
              CeRunAppAtTime(szBuff, pst);
         else
              CeRunAppAtTime(szBuff, NULL);
    }

    SSPWakeUp.exe在前面的系列文章中提过,是一个很小的程序,这是它的全部代码:

    #include <windows.h>
    #include <Notify.h>
    
    #define WM_SSP_RUN_AT_TIME (WM_USER+108)
    #define WM_SSP_WAKE_UP (WM_USER+109)
    
    int _tmain(int argc, _TCHAR* argv[])
    {
         HWND hWnd = FindWindow(L"SOSOPI", TEXT(""));
         if (hWnd)
         {
              if (argc>1)
              {
                   if (wcscmp(argv[1], APP_RUN_AFTER_WAKEUP)==0)
                   {
                        PostMessage(hWnd, WM_SSP_WAKE_UP, NULL, NULL);
                        return 0;
                   }
    
                   if (wcscmp(argv[1], APP_RUN_AT_TIME)==0)
                   {
                        PostMessage(hWnd, WM_SSP_RUN_AT_TIME, NULL, NULL);
                        return 0;
                   }
              }
         }
    
         return 0;
    }

    从代码也能看出来,这个小程序的功能就是:找到SoSoPi窗口并SoSoPi“现在系统被唤醒了”或“你的定时设置到了”。大概闹铃功能也是这么实现的,但这个功能并不一定奏效,我的Windows Mobile的闹铃就有过响不了的情况。这个唤醒功能用于天气更新还有一个潜在的坏处:如果天气更新失败,那10分钟后会再次唤醒,如果再失败,那10分钟后再唤醒……这么一来,连续的失败可能导致手机“整夜未眠”,而电量消耗很大,所以我一般不会设置自动更新,2G流量可是很贵的啊。但每个人想法真是很不一样,有个用户就说:SoSoPi最大的问题就是不能自动更新天气。自动更新天气这个功能肯定有,只是很多东西都不在我们控制范围里罢了……

    最后贴一下这个城市切换的效果图,这是我的创意。

    9.5 短信

    这恐怕是整个程序中最复杂的一块,这系列文章之所以中断这么长时间就是因为这个,上次我在回顾这些代码的时候,发觉自己都快晕掉了,怎么写出这么错综复杂的东西出来,但如果让我现在再写一遍的话恐怕也好不去哪里,这东西本来就那么复杂。现在我处理问题有个套路,那就是再怎么复杂的东西也能把它才分成几个相对没那么复杂的东西,相对没那么复杂的东西还可以再拆分开成为比较简单的东西,如果被拆分开的东西都被正确实现,那就完全有希望把这个很复杂的东西给做出来了。

    先看看Windows Mobile默认的短信程序的界面:

    界面相当土鳖,站在现在的角度来看。你觉得它像什么?像Outlook,对吧,其实不光表面像,里面的API就是Outlook的那套API,甚至邮件和短信都是一样的处理方式,我发觉除了Windows Mobile之外,没有别的手机系统是这样干的,对一个用户来讲,短信和邮件明显就是两回事,不管从技术上来说你可以把它们抽象得多相似。

    而且短信默认是不分组的(到了Windows Mobile 6.5才提供了分组的选项),也就是说,默认不提供类似iPhone或QQ这样的“聊天界面”,你要找跟某人联系的最后一条短信,你可能得翻好久,现在看相当不适,其实以前的旧手机也没有什么聊天界面,当时也没觉得有什么不适,正所谓后来iPhone一出,一切都变了。

    再看看马尼拉的短信功能:

    貌似挺好看,但一用起来就发现没什么价值,它只能一条一条读,没有短信列表,更没有聊天界面,如果点查看全部的话,就直接呼出系统的短信程序了。另外我参考了好几种短信程序,功能都不是我想要的,大多都仅限于读出信息而已。要具体分析的话,短信这个功能真不简单,下面我一点点分析。

    1,Windows Mobile的短信/邮件接口是一套比较复杂的接口,难掌握

    这是一套典型的COM接口,关于微软的COM技术,这里又穿插点故事,很早以前我就知道了COM,但最早的时候我还曾经以为COM编程是针对串口(COM)的编程,后来才知道COM其实是公共组件模型,微软为了让代码的复用性变得更好而推行的一套技术标准,愿望很美好,但COM技术过于复杂和难理解,掌握的人不多,能自行开发COM组件的人更少,大多数开发者都停留在hello world阶段,要不就只是会调用系统提供的组件而已,而且用还不一定用得好,hey,其实我讲的就是我。

    2004年初,一个同事送了我一本书,《COM+编程指南》,我被里面那一大堆似懂非懂的东西给震慑了,我自认为,这是很牛逼的技术,由于不懂,感觉高深,用现在的一个流行词来说是什么?——不明觉厉

    COM技术究竟有多牛逼,我不想再赘述了,我只想说:这么牛逼的东西咋现在也没声音了呢?当然,话不能乱说,要有些依据,现在就到china-pub.com上搜索COM技术相关的书籍,然后看看年份,2001、2002、2003,基本上就这几年的,一本本都绝版了,还得“按需印刷”,很明显,现在的人不玩这套了,微软的牛逼哄哄但后来都销声匿迹的技术远远不只是COM,这里有一篇我很推荐的译文:《Windows编程革命简史》,所以后来我也感慨自己居然被微软玩了这么多年,曰:微软技术微软做,微软做完微软用,微软用了就淘汰,又耍新招糊弄人。不过话不能说死,毕竟现在我很大程度上还是靠微软的技术吃饭,但相信意思大家都明白。

    我现在已经早就不研究COM技术了,虽然它现在在Windows系统里还无处不在,但微软会提供越来越简单的.net调用方式。再去用C++做Windows应用开发的话,那就绝对不是一个追求实效的程序员的做法。

    在SoSoPi中,用到COM的不只是短信这个模块,邮件也用到了(而且和短信是同一套接口),另外还有音乐模块,使用了Windows Media Player的接口,还有前面提到的联系人模块。

    COM的用法,还是蛮复杂的,近两年来习惯了.net的种种方便之后回头看这些调用方法,真有些不习惯了。本来还想尝试回顾一下代码,但实在不是文字能讲的清楚的……(太复杂了)

    2,Outlook接口本身比较复杂

    一个Outlook服务实体可以包含有多个Store,一个Store通常就是一个邮箱,短信也相当于一个邮箱,拥有一个名字叫“SMS”的Store,每个Store有多个目录,如“收件箱”、“已发送”和“垃圾箱”等,当然,还有可能是你自定义的一个目录,不知道是收还是发,目录里才是你想查看的短信,一条短信叫一个“Entry”。每个Store,每个目录和每个Entry都有N多的属性。

    3,通知消息接口

    短信是个经常要更新的东西,常常会出现“创建”、“删除”、“移动”、“复制”和“修改”(比如由未读变成已读)等动作,轮询是绝对不可能的,唯一的途径是通过实现一个叫“IMAPIAdviseSink”的接口,让系统来回调。另外,短信的载入并不是一个能瞬间完成的过程,我感觉还挺慢的,尤其是短信多起来的时候,要消耗好几秒钟都有可能。所以当短信发生改变的时候,你能重新获取一次全部的短信吗?——肯定不行啊,这样岂不要卡死。所以做法就是把所有短信在一开始的时候先载入,缓存起来,然后哪里改变了,就只动那个改变了的地方就好了。

    4,分组

    至于分组,也是个挺麻烦的事情,考虑下这些情况:

    • 有些号码前有“+86”,有些没有,有些带区号,有些没有
    • 一个人可以有多个号码,一个号码也可能被两个人拥有
    • 有可能用户用SIM卡保存联系人信息,而不是手机

    也许还有别的,但我一下子想不起来了。所幸的是后来发现了一个叫“FindMatchingContact”API函数,能大大简化我的工作,这个函数能够根据号码在联系人列表中找出含有这个号码的那个联系人(虽然在处理+86这种问题上还有些小bug,但已经很不错),每个联系人都有个OID(Outlook ID),你猜到了,联系人也是“Outlook”的一部分。所以分组有三种情况:存在于联系人列表中的号码,存在于SIM卡上的号码,陌生号码。

    5,“聊天界面”不太好弄

    这是SoSoPi的聊天界面:

    每条短信的长度都可能不一样,并且你不把它绘制出来,你就不知道它究竟高度是多少,那总的长度也就不知道,那也就没办法做划屏和滚动。

    由于每条短信的高度不一致,所以需要分别计算,并用一个数组来保存各条短信的高度(绘制时候需要知道这个),并且,一旦这组短信有改变,就要重新计算高度。

    6,杂七杂八

    头像怎么弄?分组如何按时间排序?彩信怎么办?还有一些由于Windows Mobile碎片化导致的问题。这里抽其中一个出来讲讲。

    可能现在的人对于“wap推送短信”有点陌生,但过去2G时代的时候我们常常会收到,我个人认为这种wap推送短信200%是垃圾,没有任何价值,无非就是把一个wap链接发给你,让你去访问,那时候流量贵啊,相当于花钱看广告。在SoSoPi短信功能完成没多久的时候,我发现了一个问题,那就是有些新收到的短信的内容无法被读取,在界面上显示空,但自己尝试又试不出来,这种情况除了我之外也有个别用户反映,但大多数用户都没出现这种问题,寻找问题的原因可是相当的棘手啊。

    经过了一阵子的观察,我渐渐把问题锁定在wap推送短信上,难道wap推送短信和普通短信有什么不同么?但wap推送短信只能由运营商发起,我无法自己制造一条出来,所以想进行调试难度很大,最后我是用Log的方式找到了问题所在,确实,在wap推送短信达到的时候,如果立即去获取其内容,将什么都获取不到,但如果稍微推迟一点点时间,再去获取其内容,就没问题了!事实上也并非完全如此,我总结回来,这应该又是一个OEM碎片化的问题,“稍微推迟一点点”,这一点点究竟是多长?我不知道,文档上完全没提到这点,我是靠自己去检验的,后来我给wap推送短信设定了30毫秒的“延迟读取”,这样就没问题了。

    类似的问题还很多,不一一说了。

    7,数据结构设计

    对我而言,最大的挑战就在于设计出一个数据结构来处理这一大堆逻辑,能够方便地遍历,查询,插入,删除,修改和排序。把之前所学的数据结构与算法,全部用上吧,这时不用,更等何时。任何复杂的问题,都可以将它抽象成一些简单的模型,并用图表将其描述清楚。对这个短信数据结构的设计,就先把它的功能列一列。

    1. 能够轻易往各个组里插入或删除一条短信
    2. 组内短信是按时间排序的
    3. 能够轻松遍历组并取出各组的最晚一条短信
    4. 组与组的集合也是有序的,当短信发生变化时,能够轻松对组进行重新排序,把发生动作的最晚的组排在前面
    5. 组也是能够被灵活增加或删除的
    6. 能够根据EntryID轻松获取一条短信的内容(因为短信变化通知传过来的参数是EntryID而不直接是短信内容)
    7. 能够根据OID(Outlook ID)轻松找到组
    8. 能够根据号码轻松找到组(毕竟不是所有发信人都存在联系人列表中)

    这样一来事情就相对明朗了,我的设计是这样的:

    实体有两种,一种是组实体,一种是消息实体。组实体形成一个链表结构,当短信发生改变的时候,链表能很方便地调整各个节点的次序。组实体内包含一个multimap,反映了时间到消息实体之间的对应关系,为什么是multimap而不是map?那是因为短信的时间有可能是完全相同的,比如你复制一条短信,用map的话就会出错,另外还有3个map,能方便地让我们找到相关的实体。还有一个处理sim卡联系人到组的对应关系的map没在图上标出。

    8,其它一些问题

    发送短信这个功能我是直接调用系统程序的,要自己实现这个功能不是不可以,由于碎片化的缘故,可能会导致很多问题。

    如何知道一个短信是收到的还是发送的?技术上来说,还真的不知道。在SoSoPi的处理当中,把“已发送”作为自己发送的,把“收件箱”作为自己接收的。事实上,用户可以将“收件箱”的短信转移到“已发送”,从而导致“误判”,另外,如果用户有自定义的文件夹的话,里面的短信也不在我的处理范围中。

    如果短信很多,并且选择了“删除全部短信”的话,很可能会导致SoSoPi程序甚至系统挂掉——具体原因未明,所以只能告诉用户,当你要对短信执行批量导入,或批量删除这种动作的时候,最好先退出SoSoPi。(多问一下:iPhone有这个问题吗?——哈哈,不破解情况下,想写一个程序去读用户的短信,门都没有)

    彩信的读取也是调用系统的程序,因为彩信也同样是一个OEM相关的令人处理起来抓狂的东西。

    关于短信这个模块,如果要认真写,可以写很多,但都要深入到代码里去,这不是本文的意图。

    9.6 邮件

    邮件的API和短信几乎完全一样,但这个模块的开发难度却比短信简单很多,因为我的任务只是把邮件一封一封读出来,并且,只是显示其中的一部分的文本内容。如图:

    这个模块老吴没对我提太多的要求,功能就仅仅是把邮件显示出来并提供一个方便的删除功能而已,粗体字表示邮件未读(字体颜色可以自定义的),前方的绿色图标表示这是收到的邮件,而红色图标则表示是发出去的邮件。

    前面提到过,一个Outlook服务实体可以包含有多个Store,一个Store通常就是一个邮箱(短信也是其中一个Store),所以,用户是可能会有多个邮箱的,所以SoSoPi也提供了选择邮箱的界面:

    至于一封邮件到底是收到的,还是发送的,则跟短信一样,用它所在的目录来区分了,如果在“收件箱”目录中,就认为它是收到的,如果在“已发件箱”中,则认为它是已发送的。

    总体而言,邮件和短信有许多重叠的东西,但邮件的处理又比短信简单许多。

    [回目录]

  • 相关阅读:
    bzoj4262
    bzoj3252
    海蜇?海蜇!
    AGC018F
    java数据类型;常量与变量;类型转化;
    java 基础,查看jar包源码,JD-GUI
    性能测试报告
    如何防止http请求数据被篡改
    支付业务,测试遇到请求超时怎么处理;支付业务流程;异步通知和同步通知;
    fiddler使用;
  • 原文地址:https://www.cnblogs.com/guogangj/p/3086281.html
Copyright © 2020-2023  润新知