65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Mobile 电源管理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (83投票s)

2008年8月27日

CPOL

22分钟阅读

viewsIcon

383025

downloadIcon

5083

关于与电源管理器交互以利用或禁用 Windows Mobile 设备中的电源节省功能的资料集合。

引言

作为 MSDN 支持论坛和其他在线开发社区的常客,我看到了几个(有些是重复出现的)关于 Windows Mobile 设备上电源管理和控制方面的问题。有时,开发人员希望减少对电源的需求,而另一些时候,则希望禁用电源节省功能。此外,还有一些编程模式会导致电池更快耗尽。为了响应社区的声音,我收集了关于 Windows Mobile 电源管理的资料。虽然本文档中提到了 Windows Mobile Standard 设备,但它主要围绕 Windows Mobile Professional 展开。电源管理是一个巨大的领域,我还有很多内容要讨论并添加到本文中。但是,目前,我觉得所提供的信息对许多人会有所帮助,因此我决定发布这篇文章,并计划在一段时间内对其进行完善,直到我认为它完整为止。如果文章变得太大,那么它可能会被拆分成多篇文章。

关于代码

本文随附的代码示例是演示一个或两个电源管理概念的小程序的集合。这些程序依赖于几个本机调用。为了避免在每个单独的程序中声明 P/Invoke,有一个单独的项目包含了所有必需的本机调用,示例程序通过该项目中的类进行低级调用。我通常会将这些调用封装在辅助函数中,但为了不增加我希望演示的调用之间的分离层,我避免在这些示例中使用辅助函数。所有示例代码均为 C#,带有 P/Invoke 调用。由于列出了所使用的 API,因此该信息对于 C++ 开发人员也应有用。

随附代码中的示例程序演示了以下内容

  • 显示设备电池的电压、电流和温度
  • 更改设备内硬件的电源状态
  • 枚举 Windows Mobile Professional 设备中的硬件
  • 枚举 Windows Professional 设备支持的电源模式
  • 防止设备进入睡眠状态
  • 切换屏幕背光的状态
  • 唤醒设备以执行工作,而无需提醒用户或打开屏幕

驱动程序依赖项

如前所述,电源管理的某些方面涉及低级调用。因此,由于对驱动程序实现的依赖,某些代码示例可能无法在您的设备上运行。在收集此信息期间,我对我拥有的 Windows Mobile 6 设备(现在是 6.1 设备)执行了固件更新。一些示例停止工作,问题的原因是该特定设备的制造商决定在其固件更新中不使用标准电源管理器。许多代码示例将在 Windows Mobile 5 设备上运行,但我的目标是 Windows Mobile 6。在 Windows Mobile 5 上运行某些示例可能会抛出“NotSupported”异常。在下周内,我计划解决所有 Windows Mobile 5 兼容性问题。

术语:Windows Mobile Professional 和 Windows Mobile Standard

本文中的部分信息特定于 Windows Mobile Professional 和 Windows Mobile Standard。我将使用这些平台的旧名称而不是这些术语来指代这些设备类别。对于没有触摸屏的设备,我将使用术语 Smartphone 而不是 Windows Mobile Standard 设备。对于带有触摸屏的设备,我将使用术语 Pocket PC 而不是 Windows Mobile Professional 设备。虽然这些术语被认为是过时的,但我发现更容易从视觉上区分这两个词。当我谈论两个平台共有的属性时,我将使用术语“Windows Mobile”。

“关闭”是什么意思?

本次讨论的基础是理解“关闭”一词的含义。典型的灯泡在典型的灯光开关上要么开,要么关,没有中间状态。从历史上看,许多电器都可以这样说,但在当今世界,除非电源中断,否则许多设备不会经常使用真正的关闭状态。关闭状态已被暂停状态或低功耗状态取代。Smartphone 和 Pocket PC 设备上的电源行为不同,因此我将分别描述它们。但是,两个平台都共同的是,如果我说设备“关闭”,这意味着设备中没有电流流过,而当普通用户说“关闭”时,他们将意味着设备屏幕关闭,无论设备是否实际消耗电源或执行操作。

智能手机(r)

Windows Mobile Standard 设备通常处于开启状态。如果您将手机切换到真正的关闭状态,那么您将无法接听电话。如果您不与设备交互,屏幕将关闭,处理器将以较低的速度运行,但它仍然处于开启和运行状态。根据 Windows Mobile 团队博客,让设备始终处于开启状态有助于更好地节省电量。这种模式的一个潜在问题是,编写不当的程序会在空闲时导致处理器消耗比所需更多的电量。

掌上电脑

Windows Mobile Professional 有四种我们应该了解的电源模式。在全功率状态下,设备的屏幕和背光灯都亮着。如果您按下电源按钮或不与设备交互,那么经过一段时间后,它会进入“无人值守”状态或暂停状态。如果程序指定需要继续运行,则设备进入无人值守状态。在无人值守状态下,设备仍在运行,但屏幕和背光灯已关闭。用户会将其视为关闭状态,但设备仍在唤醒。如果没有程序需要继续运行,则设备将进入暂停状态。在暂停状态下,所有正在运行的程序仍然在内存中,但由于 CPU 处于暂停状态,它们不会取得任何进展。我们关心的最后一个状态是真正的关闭状态。

电源状态

系统电源状态

Windows Mobile 设备有几个预定义的电源状态。其中一些状态特定于 Pocket PC,一些特定于 Smartphone,还有一些可以在两者上找到。下表列出了电源状态,并描述了将设备转换为所述电源状态时发生的情况。P 表示该状态适用于 Pocket PC,S 表示该状态适用于 Smartphone。

状态名称 描述
On SP 完全通电状态。
背光关闭 SP 用户已有一段时间未与系统交互,并且背光已关闭。此操作的超时时间通过控制面板中的背光设置进行设置。
用户空闲 S 此状态目前仅适用于智能手机设备,但一旦 Pocket PC 切换到始终开启的电源模式,也将实现此状态。在此状态下,背光和屏幕都已关闭。
屏幕关闭 SP 在此状态下,用户已专门关闭屏幕。这与由于空闲而导致屏幕关闭不同。
无人值守 P 此模式特定于 Pocket PC。屏幕、背光和音频都已关闭,但设备上的程序仍在运行。这使得 ActiveSync 可以在不提醒用户的情况下检查电子邮件。从用户的角度来看,设备处于睡眠状态。
恢复中 P 当设备从睡眠中唤醒时,它处于此模式。背光和屏幕仍然关闭,程序有 15 秒的时间将设备切换到另一个电源状态,否则设备将自动返回暂停状态。
Suspended P 设备正在睡眠,处理器已停止。设备不会从这种状态中退出,直到硬件事件将其唤醒。
关闭 SP 设备不执行任何操作,也不消耗电量(例外:在某些设备中,系统时钟可能仍会耗电以维持时间)。

请注意,程序可以改变设备在电源状态之间的转换。此类程序的一个示例是即时通讯程序,它可以阻止设备暂停,以便它可以继续接收消息。

设备驱动程序电源状态

Windows Mobile 设备的各个组件可以有自己的电源状态,其中最明显的是屏幕和背光。设备可以在背光或屏幕关闭时保持开启状态(使用媒体播放器时常见的电源配置)。设备驱动程序的电源状态命名方式更为抽象。

状态 描述
D0 设备完全通电
D1 设备功能正常,但处于省电模式
D2 设备处于待机状态
D3 设备处于睡眠模式
D4 设备已断电

根据硬件和驱动程序的不同,其中一些电源状态是相同的。当需要更改设备的电源状态时,应通过 DevicePowerNotify 传递更改状态的请求。请求电源状态更改并不能保证状态会更改。驱动程序仍然可以自行决定电源状态是否会更改(请记住,您的程序不是唯一运行的程序,也不是唯一影响电源状态的程序)。

请求和更改电源状态

如果需要监视或查询电源状态,请使用本机函数 GetDevicePower。您的程序可以通过 RequestPowerNotifications 请求电源更改事件的通知,而不是通过 GetDevicePower 轮询状态。

如果您的应用程序需要 Windows Mobile 设备保持在某个电源状态,请使用 SetPowerRequirement 请求您的应用程序所需的电源状态。操作系统将确保设备的电源状态不会低于所请求的状态。当不再需要该电源状态时,请调用 ReleasePowerRequirement。要设置设备驱动程序的电源状态而不是表达最低要求,请使用 SetDevicePower

系统电源状态可以通过本机函数 SetSystemPowerState 进行设置。Windows Mobile 设备中各个硬件项的电源状态可以使用 SetDevicePower 进行设置。

(您还可以请求在某些电源更改事件期间自动启动您的程序。有关更多详细信息,请参阅 在 Windows Mobile 上自动启动您的应用程序。)

当您的应用程序需要在无人值守模式下运行时,请使用本机函数 PowerPolicyNotify() 通知操作系统您需要设备继续运行而不会暂停。这是一个引用计数 API。对于每次使用 TRUE 调用此函数,您还必须使用 FALSE 调用此函数,以确保设备不会不必要地保持在较高的电源状态。

没有电源管理器

虽然 Windows Mobile 设备可以使用电源管理器,但 Windows CE 设备可能支持也可能不支持。对于不支持电源管理器 API 的设备,可以使用对 GWES 系统的调用以编程方式关闭系统电源。仅当电源管理器 API 不可用时才使用 GWES 函数。GwesPowerOffSystem 会暂停系统。或者,我们还可以通过使用 keybd_event 函数生成键盘消息来模拟按下电源按钮,如下所示:

keybd_event( VK_OFF,0, KEYEVENTF_SILENT,0);
keybd_event( VK_OFF,0, KEYEVENTF_SILENT|KEYEVENTF_KEYUP,0);

无论哪种情况,由于消息必须由 GWES 处理,函数调用将返回,并且在系统暂停之前会再执行几行代码。如果您在调用此方法后需要系统停止工作,则请调用 Sleep

枚举硬件

Windows CE 设备中的活动硬件可以通过首先查看注册表中的 HKLM\Drivers\Active 来找到。在该位置将找到一组具有数字名称的注册表项。查看每个键内的“Namestring 将提供硬件项的名称。在示例程序 CePowerState 中,我使用以下代码枚举硬件

// Get the names of all of the subkeys that
// refer to hardware on the device.
RegistryKey driverKeyRoot =
  Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Drivers\\Active");
string[] keyName = driverKeyRoot.GetSubKeyNames();

//We are saving this information to list for sorting later
List<string> deviceNameList = new List<string>();
for (int i = 0; i < keyName.Length; ++i)
{
   //Get the name of the hardware and add it to the list
   RegistryKey currentKey = driverKeyRoot.OpenSubKey(keyName[i]);
   string deviceName = currentKey.GetValue("Name") as string;
   if(deviceName!=null)
    deviceNameList.Add(deviceName);
}
//Sort the list
deviceNameList.Sort();
//Add the list to the list box so the user can select hardware
for (int i = 0; i < deviceNameList.Count; ++i)
{
   lstDeviceList.Items.Add(deviceNameList[i]);
}

示例程序 CePowerState 将枚举在该位置找到的所有硬件,并允许您通过调用 SetDevicePower 直接设置硬件的电源状态。屏幕背光通常(但并非总是)命名为 BKL1:,声音设备通常命名为 WAV1:。将背光的电源状态设置为 D0 将其打开,D4 将其关闭。如果您正在播放音频并将 WAV1: 的电源状态设置为 D4,则在将电源状态设置回 D0 之前,将不再听到声音。这是设置设备电源状态的代码。

//Get the name of the selected hardware
string deviceName = lstDeviceList.SelectedItem as string;
//Get the power state to which the device will be changed
CEDEVICE_POWER_STATE state = (CEDEVICE_POWER_STATE)Enum.Parse(
   typeof(CEDEVICE_POWER_STATE), lstPowerState.SelectedItem as string,true);
//deviceHandle = CoreDLL.SetPowerRequirement(deviceName, state,
                 (DevicePowerFlags)1 , IntPtr.Zero, 0);
CoreDLL.SetDevicePower(deviceName, DevicePowerFlags.POWER_NAME, state);

电源状态的已注册设备电源设置

某些电源状态有默认的设备电源设置。例如,在无人值守状态下,我的 Windows Mobile Professional 手机会关闭屏幕背光,但会保持声卡和 GPS 接收器通电。有关这些设置的信息可在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts\State\ 中找到。对于每个电源状态,您都会找到一个键。此键的内容是字符串子键,其名称与设备上的硬件匹配,其值与设备电源状态匹配(因此 0x04 匹配 D4,这意味着在匹配状态下某个硬件将以全功率运行)。

防止系统关机

如果未检测到用户操作,Pocket PC 将自动进入暂停状态。有时您可能需要设备保持全功率状态,即使用户没有直接与系统交互(例如使用 Windows Media Player 听音乐时)。为了防止系统因周期性空闲而关机,请定期调用 SystemIdleTimerReset()。调用此函数可以防止达到暂停超时。暂停超时可以在系统上更改,因此您需要查询暂停值并确保以小于最小暂停超时值的间隔调用 SystemIdleTimerReset。暂停超时值可以通过调用 SystemParametersInfo 获取。此函数可用于获取以下三个超时值:

超时类型 描述
SPI_GETBATTERYIDLETIMEOUT 上次用户输入到电池供电时暂停的超时时间。
SPI_GETEXTERNALIDLETIMEOUT 上次用户输入到外部供电时暂停的超时时间。
SPI_GETWAKEUPIDLETIMEOUT 系统自行开机到再次暂停的超时时间。

超时值也可以从注册表中检索。在 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power] 位置查找以下键

描述
电池关机 系统在电池供电下空闲多长时间后暂停。设置为零可禁用暂停。
禁用 GwesPowerOff 设置为非零值可禁用 GWES 对系统暂停的管理。
外部电源关闭 系统在外接电源下运行多长时间后暂停。设置为零可禁用暂停。
屏幕关闭 系统空闲多长时间后屏幕关闭。
唤醒关机 系统因非用户唤醒事件唤醒后,在暂停之前等待用户输入的时间。

电池状态

Windows Mobile 设备中的电池状态可通过本机方法 GetSystemPowerStatusEx2() 获取。提供的信息将告诉您设备是否连接到外部电源以及主电池和备用电池(如果存在)中剩余电量的估算值。本机函数将信息返回到 SYSTEM_POWER_STATUS_EX2 结构中,其中包含通过托管 SystemState 类无法获得的信息,例如电池温度或电池瞬时消耗的电量。返回的电池指标的准确性取决于 OEM。OEM 也可能决定不收集和返回某些电池指标,在这种情况下,可能会返回 xxxx_UNKNOWN 值之一。操作系统更新电池信息的最长时间可以在 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts\BatteryPoll] 中找到。

在随附的示例程序中,有一个名为“Battery Status”的程序,它显示了电池信息。程序顶部显示了电池最重要的几个数据:当前容量、是否连接到交流电源、电池温度、电压输出以及电池电流(请注意,当设备充电时,电流符号会改变)。

WiMoPower1/WiMoPower1003.jpg

Microsoft.WindowsMobile.Status.SystemState 类也可用于监视电源状态并接收电池变化的通知。SystemState 类可用的信息分辨率较低(它以“非常低”、“低”、“中”、“高”和“非常高”来描述电池状态,而本机函数以百分比形式返回电池电量)。SystemState 类也不会返回有关功耗或电池温度的信息。

节约电量

如果您希望确保您的程序负责任地运行,并且不会不必要地耗尽电池电量,那么您的程序能做的最好的事情就是在没有工作需要执行时停止执行工作。这听起来像是常识,不是吗?但是,您会惊讶于程序在可以通过不执行任何操作来节省电量时所做的额外工作量。我在支持论坛中遇到的最常见的耗电模式是连续轮询。此类实现的一个示例如下:

BeginSomeTimeConsumingTask();
while(!taskComplete)
{
   Application.DoEvents();
}

以上代码旨在在另一个线程上运行一个长时间运行的进程,并在主线程中等待其完成,而不会影响主线程的响应能力。如果主线程没有工作可做,那么 CPU 就会不必要地空转等待另一个线程继续。更好的替代方案是让后台任务发出信号。

问题“迭代检查”

描述

一个线程需要等待一个事件发生才能继续执行其余操作。许多开发人员会创建一个布尔变量,并在另一个线程可以继续时更改其值。等待线程轮询此变量直到它更改,然后继续其任务。

解释

等待线程不必要地轮询一个变量,消耗 CPU 周期,却没有任何实际工作。

解决方案

运行时和操作系统已经通过各种同步对象提供了实现相同功能的设施。可以使用同步技术来阻塞线程,直到任务完成或另一个线程发出完成信号。程序还可以等待外部事件,或者注册自身在某个外部事件(例如设备连接到电源,或 ActiveSync 会话初始化)期间启动。对于这些情况,程序可以注册在事件发生时收到通知或启动。当轮询是查询状态的唯一方法时,请考虑在检查之间让线程休眠。

关于同步文章的讨论应该有其自己的位置。我已经在另一篇文章中写过它们。我在这里写过它们:WiMoNativeSync.aspx

示例:无轮询

为了一个简单的例子,我将使用委托和线程池来执行我的长时间运行的任务。委托是必要的,因为 UI 元素不能从辅助线程更新,而委托可以用来确保 UI 更新发生在正确的线程上。完整的程序可以在随附的示例代码中找到。

delegate void UpdateStatusDelegate(int progress);
delegate void VoidDelegate();

UpdateStatusDelegate _updateStatus;
VoidDelegate _taskComplete;
WaitCallback _longRunningTask;

public Form1()
{
    InitializeComponent();
    _longRunningTask = new WaitCallback(LongRunningTask);
    _updateStatus = new UpdateStatusDelegate(UpdateStatus);
    _taskComplete = new VoidDelegate(TaskComplete);
}

// We cannot update UI elements from secondary threads. However
// the Control.Invoke method can be used to execute a delegate
// on the main thread. If UpdateStatus is called from a secondary
// thread it will automatically call itself through Control.Invoke
// to ensure its work is performed on the primary (UI) thread.
void UpdateStatus(int progress)
{
    if (this.InvokeRequired)
        BeginInvoke(_updateStatus, new object[] { progress });
    else
    {
        this.txtFeedback.Text = progress.ToString();
        pbWorkStatus.Value = progress;
    }
}

// The long running task is contained within this method.
// as the task runs it will pass progress updates back to
// the UI through the UpdateStatus method. Upon completion
// of the task a call is made to TaskComplete
void LongRunningTask(object o)
{
    try
    {
        for (int i = 0; i < 100; ++i)
        {
            Thread.Sleep(100);
            UpdateStatus(i);
        }
        TaskComplete();
    }
    catch (ThreadAbortException exc)
    {
        //The task is being cancelled.
    }
}

// Since the actions the program takes at completion of the long
// running task touch UI elements the TaskComplete method will
// also call itself (if necessary) through Control.Invoke to
// ensure that it is executing on the primary (UI) thread.
void TaskComplete()
{
    if (this.InvokeRequired)
    {
        this.Invoke(_taskComplete);
    }
    else
    {
        pbWorkStatus.Value = 0;
        miWork.Enabled = true;
        txtFeedback.Text = "Complete";
    }
}

// When the user selects the menu item to begin working
// I will disable the work menu item to prevent concurrent
// request and start the long running progress on a secondary
// thread.
private void miWork_Click(object sender, EventArgs e)
{

    miWork.Enabled = false;
    ThreadPool.QueueUserWorkItem(_longRunningTask);
}

private void miQuit_Click(object sender, EventArgs e)
{
    this.Close();
}

问题“不要让设备休眠”

描述

程序正在执行一项不需要用户交互的长时间运行任务,例如 GPS 导航(用户可能会查看屏幕但不触摸它)。程序运行一段时间后,屏幕关闭,打断了用户体验。

解释

由于用户没有与系统交互,因此系统会像往常一样工作,并关闭设备电源以节省电量。

解决方案

定期调用本机函数 SystemIdleTimerReset() 以防止设备断电。

示例程序:PreventSleep

程序“PreventSleep”从注册表读取空闲超时值,并将以略短于最短超时时间的间隔调用 SystemIdleTimeReset。因此,只要 PreventSleep 正在运行,设备就不会因缺乏用户交互而进入睡眠状态。

在代码示例中,SystemIdleTimeReset 是在计时器上调用的,因此主线程无需关注维护重置。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Win32;
using Microsoft.Win32;

namespace PreventSleep
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // Look in the registry to see what the shortest timeout
        // period is. Note that Zero is a special value with respect
        // to timeouts. It indicates that a timeout will not occur.
        // As long as SystemIdleTimeerReset is called on intervals
        // that are shorter than the smallest non-zero timeout value
        // then the device will not sleep from idleness. This does
        // not prevent the device from sleeping due to the power
        // button being pressed.
        int ShortestTimeoutInterval()
        {
            int retVal = 1000;
            RegistryKey key = Registry.LocalMachine.OpenSubKey(
                        @"\SYSTEM\CurrentControlSet\Control\Power");
            object oBatteryTimeout = key.GetValue("BattPowerOff");
            object oACTimeOut = key.GetValue("ExtPowerOff");
            object oScreenPowerOff = key.GetValue("ScreenPowerOff");

            if (oBatteryTimeout is int)
            {
                int v =  (int)oBatteryTimeout;
                if(v>0)
                    retVal = Math.Min(retVal,v);
             }
            if (oACTimeOut is int)
            {
                int v = (int)oACTimeOut;
                if(v>0)
                    retVal = Math.Min(retVal, v);
             }
            if (oScreenPowerOff is int)
            {
                int v = (int)oScreenPowerOff;
                if(v>0)
                    retVal = Math.Min(retVal, v);
           }

	//Since the interval is in seconds and out timer
	//operates in milliseconds the value needs to be multiplied
	//by 1000 to get the appropriate millisecond value. I've
	//multiplied by 900 instead so that I ensure that I call
	//SystemIdleTimerReset before the timeout is reached.
            return retVal*900;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Set the interval on our timer and start the
            // timer. It will run for the duration of the
            // program
            int interval = ShortestTimeoutInterval();
            resetTimer.Interval = interval;
            resetTimer.Enabled = true;
        }

        private void miQuit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        // Call the SystemIdleTimerReset method to prevent the
        // device from sleeping
        private void resetTimer_Tick(object sender, EventArgs e)
        {
            CoreDLL.SystemIdleTimerReset();
        }
    }
}

问题“悄悄地醒来并工作”

描述

您的程序需要在预定时间唤醒并执行一些工作。

解释

当设备唤醒并开始工作时,将其置于全功率模式会不必要地引起用户的注意,或者如果设备在用户手中,可能会导致屏幕和按钮接收到按键。我们需要在不开启屏幕的情况下唤醒设备。用户应该完全不知道设备正在做任何工作。

解决方案

当设备唤醒时,程序应立即将 Windows Mobile 设备置于无人值守模式。在此模式下,如果用户不使用设备,则设备的屏幕不会通电,但程序能够像设备完全通电一样运行。一旦程序完成工作,它可以释放其对设备处于无人值守模式的要求,并且可以在用户完全不知情的情况下再次进入睡眠状态。

示例程序:静默工作

“静默工作”允许用户在启动时选择一个声音文件和延迟时间。选择“运行”菜单选项后,程序将使用 CeRunAppAtTime 本机调用安排自身重新启动,如上一篇文章中关于程序调度启动和终止所述。在选定的延迟时间后,程序将启动并播放声音。播放声音与程序名称完全矛盾,但它是证明程序正在工作的最显著方式。

这属于少数我修改项目 Main() 方法的情况之一。当应用程序因调度而启动时,会传递命令行参数“AppRunAtTime”。Main() 方法的默认实现不接受命令行参数。当找到 AppRunAtTime 参数时,我让应用程序播放声音并退出,而不是加载窗体。

// Schedule the execution of the program and terminate.
// You can either put the device to sleep or leave it at
// full power. In either case the program will run at its
// assigned time, play a sound, and then terminate
private void miRun_Click(object sender, EventArgs e)
{
   int waitTime = int.Parse(this.cboStartTime.Text);
    DateTime startTime = DateTime.Now.AddSeconds(waitTime);
    string targetExecutable =
      this.GetType().Assembly.GetModules()[0].FullyQualifiedName;
    RunAppAtTime(targetExecutable, startTime);
    using (StreamWriter sw = new StreamWriter(targetExecutable + ".soundPath"))
    {
        sw.Write(txtSoundPath.Text);
        sw.Close();
    }
    this.Close();
}

以下是我的修改后的 Main 方法

[MTAThread]
static void Main(string[] args)
{
    if (args.Length == 0)
        Application.Run(new Form1());
    else if (args[0].Equals("AppRunAtTime"))
    {
        string soundPath;

        // We started due to a scheduled event
        CoreDLL.PowerPolicyNotify(PPNMessage.PPN_UNATTENDEDMODE, -1);
        string targetExecutable =
          typeof(Form1).Assembly.GetModules()[0].FullyQualifiedName;

        StreamWriter argInfo = new StreamWriter(targetExecutable + ".argument.txt");
        argInfo.WriteLine(args[0]);
        argInfo.Close();

        using (StreamReader sr = new StreamReader(targetExecutable + ".soundPath"))
        {
            soundPath = sr.ReadToEnd();
            sr.Close();
        }
        if (File.Exists(soundPath))
            Aygshell.SndPlaySync(soundPath, 0);
        CoreDLL.PowerPolicyNotify(PPNMessage.PPN_UNATTENDEDMODE, 0);
    }
}

问题:“您的程序需要查询电池电量”

能够查询电池电量对于创建 Windows Mobile 实用程序以及编写能够负责任地响应可用电量的程序非常有用。

示例程序:电池状态

获取电池状态信息的过程非常简单。P/Invoke 声明和辅助函数如下所示

[DllImport("CoreDLL")]
public static extern int GetSystemPowerStatusEx2(
      SYSTEM_POWER_STATUS_EX2 statusInfo,
     int length,
     int getLatest
  );

public static SYSTEM_POWER_STATUS_EX2 GetSystemPowerStatus()
{
  SYSTEM_POWER_STATUS_EX2 retVal = new SYSTEM_POWER_STATUS_EX2();
  int result =  GetSystemPowerStatusEx2( retVal, Marshal.SizeOf(retVal) , 0);
  return retVal;
}

参数 getLatest 控制是返回缓存信息还是更新的信息。缓存信息通常不会超过 5 秒,所以我认为没有必要要求更近的信息。如果您希望确保您的信息始终是最新的,请为 getLatest 参数传递一个非零值。示例程序在顶部显示最重要的电池信息,并带有图标,并在图形下方以列表框显示函数返回的所有值。顶部的信息包括剩余电量、电池电压输出、电池温度、当前消耗电流以及电池是否使用交流电源。请注意,当电池从充电变为放电时,电流符号可能会在正负之间切换。

WiMoPower1/WiMoPower1004.jpg WiMoPower1/WiMoPower1005.jpg

好奇:我的设备支持哪些电源状态

我在寻找其他信息时偶然发现了一些有趣的注册表项,从而产生了此程序。使用注册表,此程序将列出您设备的电源状态。选择其中一个电源状态将使程序告知您设备驱动程序的默认电源状态以及应具有除默认状态之外的某些状态的硬件。

WiMoPower1/WiMoPower1006.jpg

示例程序:MyPowerStates

这段代码除了过滤注册表转储之外什么也没做。这是程序主体的源代码

const string BASE_POWER_HIVE = @"System\CurrentControlSet\Control\Power\State";
string[] _powerStateNames = {"Full Power","Power Savings",
                             "Standby","Sleep Mode",
                             "Power Off"};

Regex _targetRegistryValue = new Regex("(DEFAULT)|(^.*:$)",
                                       RegexOptions.IgnoreCase);

string[] GetPowerStateList()
{
    RegistryKey powerStateKey = Registry.LocalMachine.OpenSubKey(BASE_POWER_HIVE);
    return powerStateKey.GetSubKeyNames();
}

string[][] GetPowerStateInfo(string stateName)
{
    RegistryKey stateInformationKey =
      Registry.LocalMachine.OpenSubKey(String.Format(@"{0}\{1}",
      BASE_POWER_HIVE, stateName));
    string[] valueList = stateInformationKey.GetValueNames();
    List<string[]> StateInfo = new List<string[]>();
    for (int i = 0; i < valueList.Length; ++i)
    {
        string currentValue = valueList[i];
        if (_targetRegistryValue.IsMatch(currentValue))
        {
            StateInfo.Add(new string[] { valueList[i],
              _powerStateNames[(int) stateInformationKey.GetValue(currentValue)]});
        }
    }
    return StateInfo.ToArray();
}

void PopulatePowerState()
{
    cboPowerState.Items.Clear();
    string[] stateList = GetPowerStateList();
    List<string> sortList = new List<string>(stateList);
    sortList.Sort();

    for (int i = 0; i < sortList.Count; ++i)
    {
        cboPowerState.Items.Add(sortList[i]);
    }
}

获取电源更改通知

如果您想接收设备电源状态变化的通知,您可以向电源管理器注册通知。这将需要理解本机同步事件和队列。请参阅文章Windows Mobile .NET 中使用消息队列进行进程间通信

新增功能

如前所述,本文档是一个正在进行的工作,我决定分享它,因为我觉得它对社区会有帮助。在接下来的几周里,文档将进行编辑并添加示例程序。如果您有什么想看到的,请随时提出请求。只需在下面留言即可。如果您喜欢这篇文章,请给它评分。

资源与参考

API 参考

文章和博客条目

名称 作者
自动在 Windows Mobile 上启动您的程序 Joel Ivory Johnson
Windows Mobile .NET 的本机线程同步 Joel Ivory Johnson
Windows Mobile .NET 中使用消息队列进行进程间通信 Joel Ivory Johnson
系统电源 迈克·卡利加罗
Windows CE 总线驱动程序的工作原理 廖大卫
保持用户界面响应迅速以及 Application.DoEvents 的危险 JFo

书籍

  • Programming Windows Embedded CE 6.0,Douglas Boling,Microsoft Press 版权所有 2008
  • 移动开发手册,Andy Wigley,David Moth,Peter Foot,Microsoft Press 版权所有

历史

  • 2008年8月28日
    • 初次发表
  • 2008年9月14日
    • 更新了示例代码以包含 SystemState
  • 2009年3月21日
    • 纠正了代码中的错误
    • 添加了对本机事件和队列文章的引用
© . All rights reserved.