Today Screen与Home Screen插件的架构对比

12/28/2009

 

Nicolas Guibourgé, Microsoft Corporation

2007年2月

摘要

对Today screen插件(PPC)和Home screen插件(Smartphones)的架构进行比较。同时对如何高效设计这些插件给出一些建议和示例。本文假定你已经对Microsoft® Windows® CE操作系统、编程技能、C++语言以及COM有了基本的了解。
从微软下载中心下载本文 源代码

适用于

Microsoft Windows Mobile®(PPC)
Microsoft Windows Mobile®(Smartphones)
 

简介

本文包含三个部分:插件的运作方式,如何优化插件,以及示例。第一部分将对Today Screen插件和Home Screen插件的架构进行解释。第二部分将讨论一些好的实践和优化。最后一部分将给出一些示例代码。

插件的运作方式

本节将拆分为两部分,分别介绍Today Screen插件和Home Screen插件的架构。

Today Screen

本节将对Today Screen插件的静态组织、进程及线程的架构以及插件的初始化过程进行讨论。请牢记Today Screen插件仅仅适用于PPC。
静态组织
Today screen插件是Today screen的一个子窗口。Today screen插件是一个动态链接库(DLL)文件,其包含一个 InitializeCustomItem的出口函数,插件自身有一个窗口处理过程。Today screen应用程序(Shell32.exe)通过 LoadLibrary 函数动态载入插件DLL。你可以通过设置程序或修改注册表来启用插件。
进程及线程架构
Today screen插件运行在Shell32主进程之中。Today screen主线程将创建Today screen以及调用插件dll的 InitializeCustomItem 函数来初始化插件,如图1所示。
图1 Today screen的进程与线程架构
插件的接口
Today screen插件是非常容易编写的,一般包含如下步骤:
  • 在插件DLL中定义一个出口函数,名称为InitializeCustomItem,该函数返回插件窗口的句柄。函数使用一个出口序数,Shell32进程使用将使用LoadLibraryGetProcAddress 函数通过此序数来获取函数的入口地址。
  • 在插件的窗口处理过程中处理如下特定的消息 (不包含标准的消息, 如WM_PAINT):
    • WM_TODAYCUSTOM_CLEARCACHE
  •  
    • WM_TODAYCUSTOM_QUERYREFRESHCACHE
插件的初始化
Shell32进程在启动时间或者用户通过PPC设置修改了Today screen项目列表时将启动插件的初始化,Shell32进程将做如下动作:
    • 如果DLL尚未载入,则载入DLL (DLL的DllMain函数将被调用)。
  1. 调用 InitializeCustomItem 函数获取插件子窗口的句柄。
  2. 发送 WM_TODAYCUSTOM_CLEARCACHE 消息给插件。
  3. 发送 WM_TODAYCUSTOM_QUERYREFRESHCACHE 消息给插件。插件必须注明他的它的显示高度并返回TRUE;否则,插件将不会被显示。

Home Screen

本节将对Home Screen插件的静态组织、进程及线程的架构以及插件的初始化过程进行讨论。同样,请请牢记Home Screen插件仅仅适用于Smartphones。
静态组织

Home screen插件没有任何窗口,更准确的说,它没有自己的窗口——它仅仅是画一个矩形。Home主进程将决定插件将被绘制到那个矩形区域内。

Home screen插件是一个组件对象模型(COM)对象,它实现了IHomePluginIPersistStream 接口。Home进程通过一个类工厂机制来载入插件。

Home进程通过一个XML文件来获取需要载入的插件列表。每个插件都在XML文件中定义了一个GUID。GUID是在插件安装时分配给插件COM组件的。

进程及线程架构
Home screen插件运行在Home进程之下。Home进程是Home screen窗口的窗口处理过程,如图2所示。由于插件并没有窗口,所以并不会接收任何Windows消息。Home进程发送事件(诸如PE_PAINT或PE_KEYDOWN),将通过调用插件的 IHomePlugin::OnEvent 接口。Home进程是唯一可发送给插件PE_*事件的程序。
图2 Home screen进程与线程架构
插件的接口
编写Home screen插件比编写Today screen插件要复杂的多,因为类工厂以及COM接口都必须编码来实现。不过,也可以将类工厂、COM接口以及激活时插件的绘制工作完全独立出来(更多信息,可参考 示例)。这种设计,在管理插件绘制的代码和Today screen插件十分的类似。插件必须通过 IHomePlugin::Initialize 和 IPersistStream::Load 方法来初始化自身,以及通过 IHomePlugin::OnEvent 接口来管理事件,

它在Today screen插件中被窗口处理函数代替。

插件的初始化
Home screen插件的初始化包括如下两个步骤:
  1. 当你选择一个包含插件的Home screen布局时,Home screen程序(Home.exe)将:
    • 载入插件DLL并通过DllGetClassObjectIClassFactory::CreateInstance 接口创建一个插件的实例。
    • 调用IHomePlugin::Initialize 接口方法初始化插件。接口将可以访问通过参数传递过来的包含Home screen布局特性的XML描述。
    • 调用IPersistStream::Save 接口方法,保存插件的数据.
    • 卸载插件DLL.
  2. 当智能机(Smartphone)启动或用户选择了一个新的Home screen布局之后,Home screen程序(Home.exe)将:
    • 载入插件DLL并通过DllGetClassObjectIClassFactory::CreateInstance 接口创建一个插件的实例。
    • 载入通过IPersistStream::Load 接口保存的数据。
在插件初始化之后,Home进程将通过  IHomePlugin::OnEvent 接口方法绘制插件。该接口和其他标准接口只在插件运行时方有效。当用户关机或选择了一个新的未包含本插件的Home screen布局时,插件将通过标准COM技术,调用 DLLCanUnloadNow卸载其DLL。不过,当DLL不能立即卸载时, DllCanUnloadNow 函数返回S_OK,Home screen程序将在其卸载前等待几分钟。

第一步为开发者提供对接收的XML格式文件获取初始数据并保存的机会。第二步完成每次插件的载入。IPersistStream 接口在第一步中用于保存数据,在第二步中读取数据。

如何优化

本节包含5个部分:数据流、电源管理、显示管理、内存耗费以及初始化。在此之下,将列出软件设计者在设计Today screen或Home screen插件时应该考虑的因素。

数据流

本节给出关于数据流的两个通用的规则:
  • 数据与插件呈现分离
  • 用通知来替代投票
本节还将说明如何管理通知
数据与插件呈现的分离
检索数据速度较慢,甚至是非常慢的,例如:找出当前日期的所有约会
Shell32或Home程序依次绘制它们的插件。它将遍历插件列表并为每一个列表发送WM_PAINT或PE_PAINT消息。如果插件在接受到WM_PAINT或PE_PAINT消息后花费时间来检索或计算数据,将会延迟插件自身及后续插件的呈现。
Today screen或Home screen插件应该在独立于插件消息处理线程的独立线程中接受和处理数据——尽管这种方法与在接收到WM_PAINT或PE_PAINT消息时处理数据相比要复杂得多。
使用通知来替代轮询
使用通知来替代轮询将会节省CPU处理时间、电池耗费以及提高插件的响应时间。在某些情况下,甚至可以提高整个设备的响应时间。
为了说明这一点,假定你将设计一个插件用于显示用户在本周的所有约会数量,该数据必须是及时的,实现此功能一般有下面两种方式:
  • 每秒钟去检查日历数据库中是否有新增约会,或被移动或删除的约会,据此更新约会数量。当你使用此方法时,插件在每秒检查约会数量是否发生变化时将耗费CPU以及电池资源。
  • 等待日历程序新增约会、约会变更或删除的通知,据此更新约会数量。在此方法下,CPU及电池仅仅在必要时才会发生耗费(并不是每一秒)。
管理通知
处理通知有多种方法,设计插件时必须保证数据处理不能延迟插件的呈现。在多数情况下,这意味着数据的检索和运算必须在独立于插件呈现线程的另一个线程之内。
一个明确的设计是创建一个专门的工作线程,当线程接收到通知,数据处理之后,调用 IHomePluginEnvironment::InvalidatePlugin 接口(Home screen插件)或InvalidateRect方法(Today screen插件)来启动一个新的呈现,如图3所示。Home或Shell32程序由此发送PE_PAINT或WM_PAINT消息到插件。
图3 工作线程
一个通知可以是一个回调过程,一个事件,一个消息队列或一个Windows消息。在Windows Mobile5.0系统中,state and notification broker可以使用以上任意方式来发送系统通知。但在Pocket Outlook® 对象模型(POOM)中只能使用WIndows消息的形式,诸如从来自日历的PIM_ITEM_*消息。Today screen插件通过窗口过程可以非常方便的处理这种类型的通知。需要强调的是,插件的窗口过程所在线程同时会执行其他已激活插件的窗口过程。所以一个好的设计是把数据管理交给工作线程来做。
由于Home screen插件没有窗口也没有窗口过程,所以Home screen的开发者在不通过额外编程的情况下,不能使用WIndows消息。由此,处理Windows消息类型的通知时,Home screen插件必须创建一个监听窗口,在其窗口过程中管理Windows消息。监听窗口非常的简单,它并不显示,也不呈现任何东西。监听窗口的窗口过程只管理Windows消息通知。相关概念和说明在 通知示例代码中。同样,你也应该记住窗口过程是在创建它的线程中运行的。你可以采用两种方式来处理通知:
  • 监听窗口将通知发送给工作线程,工作线程管理数据并调用IHomePluginEnvironment::InvalidatePlugin接口来重绘插件。
  • 创建一个工作线程,并在该线程创建监听窗口,在监听窗口的窗口过程中处理通知消息和数据,然后调用IHomePluginEnvironment::InvalidatePlugin 接口来重绘插件。

电源管理

由于电源在PPC和智能机上是稀缺资源,我们必须尽量保存它。Today screen或Home screen插件必须限制电源使用量以及只有在屏幕开启或者是相当重要的信息(如:提醒)时才重绘插件。在智能机下,你可以使用 GetSystemPowerState  API来判断屏幕是否开启,代码如下:
    DWORD PwrFlag, NameLength ;
    TCHAR StateName[64] = { 0 } ;
    GetSystemPowerState(StateName, NameLength, &PwrFlag) ;
    if(POWER_STATE_ON != PwrFlag)
        InvalidateRect(hPlugInWnd, NULL, TRUE) ; 

显示管理

本节将讨论你在设计Today screen或Home screen插件的呈现时应该考虑的事项。
屏幕分辨率
使用正确的分辨率是值得肯定的,例如:如果插件未使用设备正确的分辨率, BitBlt 函数的在将一个屏幕缓冲传送到另一个屏幕缓冲时花费的时间将会慢10倍甚至更多。如果两个屏幕缓冲区的分辨率不一样, BitBlt 将根据目标缓冲区的分辨率来调整每一个像素。如果两个屏幕缓冲区的分辨率相同, BitBlt 函数只需直接复制
字节数据到目标缓冲区。如果插件通过 CreateDIBSection 函数来创建一个32位的离屏(off-screen)缓冲区,使用 BitBlt 函数将此离屏缓冲区传送到屏幕缓冲区时将要比使用设备分辨率来创建的设备无关位图(DIB)要慢的多。你可以使用 GetDeviceCaps(hDC, BITSPIXEL) 函数来获取设备分辨率。基于同样的原因,应该使用 CreateCompatibleDC 函数代替 CreateDC 函数来创建设备上下文(DC)。
高分辨率
本节仅适用于Today screen插件。
基于Windows Mobile 2003的插件同样兼容于Windows Mobile 5.0。但是,由于基于Windows Mobile 2003的插件不能提供高分辨率显示,插件在Windows Mobile 5.0将采用像素增倍的方式来实现高分辨率。
如果插件只是针对Windows Mobile 5.0,最好避免这种像素增倍的技术来产生高分辨率。
屏幕旋转
本节仅适用于Today screen插件。
由于Today screen插件只是Today screen窗口的子窗口,在屏幕旋转时,它并不能接收到SETTINGCHANGE 消息。插件将接收到如下消息:
    • WM_TODAYCUSTOM_CLEARCACHE
  1. WM_TODAYCUSTOM_QUERYREFRESHCACHE
如果在屏幕旋转时,插件的高度需要发生改变,新的高度必须在此时指定。Today screen插件在插件窗口旋转之前接收此消息,由此,只是使用窗口的矩形区域来判断屏幕的方向是非常危险的。 安全的做法是使用 GetSystemMetrics 函数来获取系统状态。
  1. WM_WINDOWPOSCHANGED or WM_SIZE
  2. WM_PAINT

如果插件在屏幕旋转时不用改变它的高速,那屏幕旋转会更快。

内存耗费

内存是在宿主进程——在PPC中是Shell32.exe、在智能机种是Home.exe中耗费。内存堆(heap)是属于进程的,因此,插件在进程的堆中分配的内存其他插件或DLL也可以使用(例如,PPC上的Shell32进程同样是控制面板的宿主进程)。从进程堆中分配大块的内存(容量达几兆)但不释放它们,当无法再从进程堆中分配足够内存时,将引发内存短缺情况,尽管从全系统角度来看,设备的RAM是可能是足够的。发生内存短缺时,DLL的载入将被阻止,例如,再系统载入时,DLL临时从进程堆中分配内存。

一些Windows API函数将耗费进程堆的内存(例如,CreateDIBSection 函数将在默认的进程堆中分配它的缓冲区)。例如,插件在创建一个与屏幕大小一致的32位设备无关位图(DIB)时,将在进程堆中耗费几兆的内存。

初始化

本节包含两个主题:等待API就绪以及在设置中启用或禁止插件。

等待API就绪

Shell32程序早于启动进程载入,因此,当Shell32载入插件时,可能API集尚不可用。在API未准备好之前调用,将会引发应用程序异常。要判断某个API是否已准备好,插件可使用IsAPIReady 函数,或者更好的办法是处理API就绪事件。该事件的名称被定义在注册表中(HKEY_LOCAL_MACHINE/System/Events,例如,Shell API对应的事件名称为ShellAPIReady )。

尽管这更适合Today screen插件,但在Home screen插件中使用也会使插件更加稳健。

在设置中启用或禁用插件

在用户更改Today screen设置时(例如,启用或禁用插件、更改Today screen主题等),Shell32进程将销毁所有修改之前已经启用的插件窗口,然后初始化所有当前启用的插件,这将引发以下几个步骤:

  • 所有修改之前的已启用插件将收到WM_DESTROY 消息。
  • 卸载被禁用插件的DLL,DLL的主函数将被调用(通常为DllMain函数。
  • 初始化所有启用的插件,每个插件的InitializeCustomItem 函数将被调用。

InitializeCustomItem 函数在DLL存续期内可能被调用多次,因为插件从一个设置改变到另一个设置时,可能插件一直是启用状态,这样插件DLL是不会卸载的,但会调用初始化函数。InitializeCustomItem 中,分配内存应该十分小心,避免重复分配内存和对象。一个安全的规则是,在InitializeCustomItem 中分配内存和实例化对象,在插件接收到WM_DESTROY后释放内存和销毁对象。

你也应该注意InitializeCustomItem 被调用时,都会重新创建插件窗口,即插件窗口的句柄会发生变化。 这在插件窗口接收通知消息时记住这一点是十分重要的。

示例

本节将介绍三个示例,来说明本文内容

Today screen概略

一个Today screen示例,将在屏幕上写一些文本及处理屏幕旋转, 下载本文示例

Home screen概略

Home screen示例的结构如下:下载本文示例

  • PluginFactory 类是对IClassFactory 接口的实现。
  • SpPlugInBase类实现Home screen插件必须实现的标准接口。本类不会实现一些与具体插件功能相关的方法,诸如 OnEvent 方法,他是纯虚的。SpPlugInBase 类不可实例化,必须派生为SpPlugIn 类,在此派生类中实现这些方法。
  • SpPlugIn 类从SpPlugInBase 类派生并实现所有与插件功能相关的方法。为简便,示例中仅使用DrawText 函数绘制一些文本。
这种三级架构简化了插件的实现,将COM和类工厂封装到 PluginFactorySpPlugInBase 类中,使开发者得重点转移到实现插件功能的 SpPlugIn 类中。

通知

通知处理示例重用了Today screen概略示例并添加如下内容: 下载本文示例
  • DataMgt_t 类采用 工作线程/监听窗口结构。工作线程创建一个监听窗口并处理它的消息循环。监听窗口接收POOM的通知。
  • PoomAgenda_t类处理POOM接口。

结论

Today screen插件与Home screen插件的主要不同点如下:
  • Home screen插件使用类工厂和COM架构,而Today screen使用的是标准的Win32® DLL。
  • Home screen和Today screen插件的初始化方式不同。
  • 呈现方式不同:窗口方式与矩形方式——或更准备的说,是窗口过程方式与IHomePlugin::OnEvent接口方式。
  • Today screen插件可通过它自己的窗口接收所有的WM_消息以及额外的消息(例如,POOM的通知消息)。
  • Home screen插件使用PE_*事件,如PE_PAINT或PE_KEYDOWN。所以,Home screen插件不能直接管理任何通过WM_message方式发送的通知(例如,POOM通知)。
Today screen插件的窗体消息处理和Home screen插件的接口方法是运行在宿主进程中(Home.exe或Shell32.exe) 的,该进程同时管理着其他的插件。所以,插件应该考虑对其宿主进程、线程以及其他插件的影响——尤其是数据管理、呈现、内存耗费方面。在设计插件时也应考虑电池对基于Windows Mobile的设备是一种稀缺资源,应该尽量节省。
 
以下是Today screen(PPC)的在线资源:

以下是Home screen (Smartphone)的在线资源:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值