纪念一个曾经的软件产品(五)——任务栏、系统通知与锁屏

[回目录]

七、任务栏与系统通知

什么是系统通知?我大致列一下:电量的变化、蜂窝电话信号强弱的变化、WIFI的状态、蓝牙的状态变化、通话状态变化、新短信……好多,这些东西的共同特点就是你不知道它们会在什么时候发生,你必须“监听”它们,也就是处理这些系统通知,而处理的方法也是不尽相同的。而且,有些方法在一些特定的机器上不奏效,碎片化问题在这里开始爆发了。

7.1,系统通知

最简单的一类通知消息是通过监视注册表的一些键值来实现,所有的能监视的键值,我做了一个列表:

//State Center
//////////////////////////////////////////////////////////////////////////
//REG monitoring type
enum RegMonitorType
{
    RMT_ALARM_ENABLED_1 = 0,        // HKLM\Software\Microsoft\Clock\0\AlarmFlags
    RMT_ALARM_ENABLED_2,            // HKLM\Software\Microsoft\Clock\1\AlarmFlags
    RMT_ALARM_ENABLED_3,            // HKLM\Software\Microsoft\Clock\2\AlarmFlags
    RMT_ALARM_DAYS_1,               // HKLM\Software\Microsoft\Clock\0\AlarmDays
    RMT_ALARM_DAYS_2,               // HKLM\Software\Microsoft\Clock\1\AlarmDays
    RMT_ALARM_DAYS_3,               // HKLM\Software\Microsoft\Clock\2\AlarmDays
    RMT_ALARM_TIME_1,               // HKLM\Software\Microsoft\Clock\2\AlarmTime
    RMT_ALARM_TIME_2,               // HKLM\Software\Microsoft\Clock\2\AlarmTime
    RMT_ALARM_TIME_3,               // HKLM\Software\Microsoft\Clock\2\AlarmTime

    RMT_PHONE_STATUS,               // HKLM\System\State\Phone\Status
    RMT_PHONE_EX_STATUS,            // HKLM\System\State\Phone\Extended Status
    RMT_CELL_NET_AVAILABLE,         // HKLM\System\State\Phone\Cellular System Available
    RMT_CELL_NET_CONNECTED,         // HKLM\System\State\Phone\Cellular System Connected

    RMT_MISSED_PHONE_NUM,           // HKLM\System\State\Phone\Missed Call Count
    RMT_UNREAD_SMS,                 // HKLM\System\State\Messages\sms\Unread\Count
    RMT_UNREAD_MAIL,                // HKLM\System\State\Messages\TotalEmail\Unread\Count

    RMT_DESKTOP_CONNECTION,         // HKLM\System\State\Connections\Desktop\Count
    RMT_WIFI_STATE,                 // HKLM\System\State\Hardware\Wifi
    RMT_BLUETOOTH_STATE,            // HKLM\System\State\Hardware\Bluetooth
    RMT_SIGNAL_STRENGTH,            // HKLM\System\State\Phone\Signal Strength

    RMT_LOCK_STATE,                 // HKLM\System\State\Lock

    RMT_AMOUNT
};

大家看看名称就大致了解这是什么了,可监视的内容包括闹铃、电话状态、可用蜂窝网络类型、蜂窝网络连接类型、未接电话、未读短信、WIFI状态、蓝牙状态、蜂窝信号强度及锁定状态等。我只需要通过API RegistryNotifyCallback注册通知事件,当注册表中那些键值发生改变时候,我的回调函数就会被调用,我就可以执行进一步的处理,最常见的处理也就是重绘任务栏,比如信号强度变弱了,得少画一格。

需要说明的是注册表里这些值并不是那么直观,比如电话状态有两个键值,第一个键值的第七个bit为1并且第二个键值的第一个bit为1的时候,才能确定电话处于振动状态,这是我自己摸索出来的,文档里并没有提到这点,我不知道微软为什么这么设计,(或者这并非微软的设计,而是OEM相关的东西)另外判断信号类型等也是很不直观的,有些时候就要自己去试验才知道怎么回事。这些都是小事,关键——这些东西在别的手机上不一定管用,例如电话状态那个判定,在我的HTC HD2上就有时候不灵,并非总是不灵,而是*有时候*,而在我的旧三星手机上总是OK,WIFI也是,貌似有个很古怪的延迟,通知不会马上到来,而蓝牙就很正常,用户反映出来的问题也是千奇百怪,有些用户则说很好用。这种不规范,给开发带来了极大的困难,所以说iOS的开发者是蛮幸福的,其它嘛,就比较苦逼了,这是我最近看到的一条微博,发这里博大家一乐:

(你是前端工程师?)

有些系统通知则不能简单地通过监视注册表获得,如邮件和短信的变化,通过前面提到的注册表监视,仅仅能获得未读短信和未接来电的变化,但对于自己新建、删除和编辑短信邮件等事件,它是无能为力的,邮件和短信只能通过MAPI注册通知接收器(Advise Sink)来获得其变化通知,这是另外一套完全不同的代码。

另外还有手机电量的变化,也不是简单的监视注册表,它需要利用“RequestPowerNotifications”这个API函数配合一个消息队列来获取电量变化的信息,当电量发生变化时,系统就会往消息队列中扔一些消息,我们需要开一个线程专门监视这个队列,并从这个队列中Dequeue出我们想要的信息。另外,电量变化消息貌似不只是电量发生变化了才会有,这消息发送还是很频的,我也是经过了一番摸索才知道哪些消息是我需要关心的……微软这方面的开发接口还真的很不人性化(继续抱怨)。

7.2,任务栏的设计

任务栏的高度在3个DPI下分别是:52、35和26,(看吧,又是DPI,这种处理贯穿了整个程序)这几个高度的设计依据当然是默认的Windows Moible的任务栏的高度,但实际上Windows Mobile的任务栏的高度发生过变化,如图:

如果我没记错的话,微软在发布的最后的Windows Mobile 6.5.3这个ROM的时候(2010年发布),将任务栏调得矮一点了,究其原因,我想是因为受到了Android和iOS的影响,可见从那个时候起,微软开始承认自己需要向Android和iOS看齐了。另外比较显著的改动还有把开始菜单的按钮和关闭按钮放到下栏去了,这个倒不是仿Android啥的,估计微软觉得这样方便操作。

在Windows Mobile 6.5之后,点击任务栏,会出现类似的这么一个界面:

而之前版本的Windows Mobile的是这样的:

看到了没?Windows Mobile 6.5之前的任务栏更像Windows的,就好像你现在点击你的任务栏上那个小喇叭图标(假设你在用Windows,并且没有隐藏掉那个小喇叭的话),就出现了音量调整,而Windows Mobile 6.5的话就变了,不管你按任务栏上什么区域(开始按钮除外),都会出现一个统一的界面,让你选择进一步的处理,你看,是不是又向Android和iOS靠拢了?

其实微软向谁看齐对我来说无关紧要,但各种版本行为不一致,这个可真是为难了我,任务栏的行为估计也没有标准的,现在假设用户使用的是Windows Mobile 6.0,他希望点SoSoPi任务栏上那个小喇叭就和点系统任务栏上的小喇叭一样,出现音量调整那个“泡泡”,我如何让那个泡泡出现?——查阅了大量资料之后,得知这是OEM相关的东西(由手机厂商或ROM提供者定制),与之类似的还有屏幕的亮度,也是OEM相关,没有统一API调整,那怎么办?啊……(瘫痪状)

我想出的办法就是“点击穿透”,我查找到系统任务栏这个窗口,并且把用户在SoSoPi任务栏上的点击动作通过SendMessage“穿透”给它,那一旦SoSoPi任务栏上的图标的位置和系统任务栏上图标的位置不一样呢?岂不是乱套了?没关系,我提供了一个界面供用户自行调整SoSoPi任务栏上的图标的位置:

(任务栏图标位置调整)

如果这样调整还是不合适,那就索性让用户用回系统默认的任务栏吧,很简单,把SoSoPi设置为非全屏即可。

八、锁屏

锁屏,其实更准确的说法应该是“锁设备”,因为锁的不光是屏,机器上的按键也要锁住,防止误按,但为了通俗起见,下文还是叫“锁屏”。这又是一个无法最终完成的功能……究其原因,也是没有统一接口,碎片化很严重,每台机器都有不同的按键,按键的功能又是由厂商定义的,某些按键厂商做了些特殊处理就没办法按照正常的方法将其“锁住”,就算同一手机,不同的ROM表现出来的结果也可能不同。

现在我们来看看Windows Mobile 6.5之前的版本的“锁屏”:

(Windows Mobile的锁屏)

虽然说是锁屏,其实是连键盘一块儿锁了,真正的英文名称是叫“Lock Device”的,锁设备。锁了之后,你碰一碰键盘上其它按键,大致就这样显示:

(Windows Mobile锁屏后碰任意键)

表示你要继续使用设备的话,得先“解锁”。

从按键盘按键设备还有反应这点上看,所谓锁屏其实并没有真正屏蔽掉所有按键,只是对这些按键进行了一些别的处理而已。

但无疑这个锁屏太土了,很多人不喜欢,所以才有了别的锁屏软件,比如下面这个叫S2U2,这是个免费软件,好评率还蛮高的,这是它默认的锁屏界面,其实界面还是可以定制的,可以弄得很漂亮:

(S2U2)

这个界面不用说了吧,完全仿iPhone的。Windows Mobile 6.5的锁屏比之前的稍微好看点,多了个像iPhone那样的解锁滑块。

关于锁键的实现,我确实做了很长时间的研究,但仍然没有结果,老吴给过我一些建议:看看为什么S2U2可以?我后来在好几台机器上尝试了一下S2U2,确实可以,感觉没什么缺陷,做的很完美,这是怎么做到的呢?经过研究,我发现它竟然针对很多不同的机器(市场上能看到的各种机器)都做了“特殊处理”,这个可以从它的配置说明文档中得到一些信息:

(S2U2的“特殊处理”)

天啊!真相竟然如此,但我仍然不知道它底下究竟是怎么做的,因为我没有它的源代码,我只是很佩服它,这么困难的事情都去做了。(上面列出的仅仅是一部分,不是全部)而这种做法的缺陷也是显而易见的,如果新出了一款机器,它很可能得再更新一下代码,针对这款新机器也来做一些“特殊处理”。程序我做了很多年,现在我对“特殊处理”有些反感,“特殊处理”越多,代码的可维护性往往就越差,可迫于现实的原因,又不得不制造出一堆的“特殊处理”,你也许想到了,没错,SoSoPi里也有一大堆的“特殊处理”。

现在回头看看SoSoPi的这个“锁屏”,应该说还是相当实用的,通过皮肤定制,还可以把它弄得十分有创意。

(SoSoPi锁屏页)

另外需要解决的问题:

1,锁屏时候来电如何处理?

锁屏窗口按道理是应该在最上层的,它把别的窗口都遮挡住,这样才能“锁住”啊,没错吧,但我不得不考虑来电的时候的处理,如果突然来了一个电话,但来电显示的窗口又被锁屏窗口给挡住了,那怎么接电话?我的做法是监视手机的状态,(如何监视在上一节中有提到)当手机状态变为来电中,并且SoSoPi正在锁屏的时候,就把SoSoPi隐藏掉;当检测到手机结束了通话状态的时候,再重新让SoSoPi窗口给显示出来。

2,如何关掉系统的自动锁屏?

在以前的Windows Mobile中,像iPhone这样自动锁屏是默认是不打开的,一不小心就很容易误操作,现在看来很不人性化(当时不觉得,只觉得有些不便),后来到了Windows Mobile 6.5就默认多了自动锁屏。但如果用了SoSoPi的锁屏,又打开了系统的自动锁屏的话,就需要两次解锁,所以我得尝试关掉系统的自动锁屏,我做了这么一个锁屏设置项:

其实就是修改注册表里的一个键值,HKCU\ControlPanel\Keybd\DeviceLockWhenSuspend,当这个键值为1的时候,就代表启动系统自动锁屏。需要说明的是:修改注册表这个键值也不一定奏效,在有些手机上注册表里根本没有这个键值。(另外:这个做法是通过研究S2U2得知的)

3,SoSoPi自动锁屏是怎样实现的?

一般来说,当用户按下手机“电源”键的时候,手机就转入待机状态(当然如果你在播放音乐的话,系统会做一些特殊处理,让手机继续运行,而不是待机),“转入待机状态”这个动作是肯定来不及通知程序的,就算有通知,程序也是来不及处理的,因为瞬间就转入待机了,所有程序都乖乖睡觉去。所以我得捕捉的是“从待机中唤醒”这个动作,这个动作明显是系统级别的,不是普通的Windows消息,需要通过特殊的API来注册一个通知消息:

void RegisterWakeUpEvent(BOOL bReg)
{
    TCHAR (&szBuff)[MAX_PATH] = g_szLocalPath;
    GetModuleFileName(NULL, szBuff, MAX_PATH);
    MakeItAPath(szBuff);
    StringCchCat(szBuff, MAX_PATH, TEXT("SSPWakeUp.exe"));
    if(bReg)
        CeRunAppAtEvent(szBuff, NOTIFICATION_EVENT_WAKEUP);
    else
        CeRunAppAtEvent(szBuff, NOTIFICATION_EVENT_NONE);
}

CeRunAppAtEvent这个调用就是告诉系统:在唤醒的时候,给我运行一下SSPWakeUp.exe这个程序。也许你想说:“为什么不直接调SoSoPi.exe呢?”因为SoSoPi.exe比较大,加载速度可能太慢,而SSPWakeUp.exe是一个十分简短的程序(只有4.5KB),这个程序的作用就是寻找SoSoPi,并给SoSoPi Post一个自定义的消息说:“手机被唤醒了,执不执行锁屏,你看着办”。SSPWakeup还有另一个功能:天气自动更新,这个后面会提到。

最后,纪念一下SoSoPi这个很具创意的解锁动画(慢速度):

这是老吴的创意,当然我也花了不少工夫在像素校对上面。

[回目录]

原文地址:https://www.cnblogs.com/guogangj/p/3036463.html