簡介
為系統掛起與恢復而進行的應用准備步驟
曾幾何時,當您正要通過應用提交或發布一些重要數據時,突然遇到一些急事需要處理,而且會耽誤很長時間。當您完成任務回到電腦前時,發現電腦已經自動進入 了掛起狀態,或是完全關機。您可能因此丟失了部分或全部重要數據,而這僅僅是因為應用沒能在停止執行前“保存”數據。相信擁有類似經歷的人不在少數。現 在,應用開發人員設計出了一種專門的應用,來幫助我們避免發生這種情況。該應用可在系統掛起或休眠之前,通過操作系統發送相應消息與事件通知用戶。
本文通過案例描述了 Windows* 環境下系統准備掛起時的狀態,以及掛起操作和從掛起狀態恢復操作。我們將對消息和事件的發送目的進行講解,提供一些有關用法與計時方面的信息,並給出一些 高效使用建議,助您避免潛在的數據丟失。此外,我們還會深入探討應該通過何種類型的應用來實現對此類消息與事件的收發。我們將重點研究 Windows XP* Professional 操作系統上的消息傳遞,所提供的代碼實例將用來講解如何准備和從掛起狀態恢復。此外,本文還提出一些建議,幫您避免掛起或是繞過當前的種種局限。本文的討 論內容面向對 Windows 消息收發有一定認識基礎的讀者。
定義
WM_POWERBROADCAST – 通過 WindowProc() 函數(告知電源管理事件已經/正在運行)向應用廣播的消息。
PBT_APMQUERYSUSPEND – 需要經許可才能掛起的事件
PBT_APMQUERYSUSPENDFAILED – 通知掛起請求失敗的事件
PBT_APMRESUMEAUTOMATIC – 自動從掛起狀態恢復時所收到的事件
PBT_APMRESUMECRITICAL – 通知從未知或不穩定狀態恢復的事件
PBT_APMRESUMESUSPEND – 通知從掛起狀態恢復的事件
PBT_APMSUSPEND – 系統掛起前收到的事件
推動因素
應用程序的開發是一項技巧性很強的工作。隨着移動環境的到來,這種情況也會變得日益凸顯。首先,我們 需要確定哪些應用可能會因系統掛起而丟失數據,哪些應用則不會。需要考慮的應用包括:任何會受到操作系統環境變化影響的應用,如網絡連接、USB 連接或 Firewire* 連接,任何操作完成前不能中斷的應用以及任何無需用戶交互就能在顯示器上顯示信息的應用。
假如應用正在使用網絡中的某個文件時系統進入掛起狀態,將會出現何種情況?此時,即使執行了恢復操作,之前的網絡連接也不再可用,除非應用能預先“意識” 到您將遭遇困境。如果情況發生在通過 Firewire 或 USB 連接刻錄 CD/DVD 盤片時,您將如何應對?如果真是這樣,那么很不幸,您的盤片只能當杯墊用了。假如您在向一位重要客戶進行演示,中途停下來進行講解或問答時卻出現系統掛 起、顯示中止的尷尬局面?這時,您不得不花費寶貴的時間來重啟電腦,打開演示並找到此前位置,繼續進行演示。由此可見,防止應用掛起是多么的重要。
眾多事例都證明了一個觀點:開發一種依靠系統消息來防范掛起的應用是很有必要的。當系統無法從掛起狀態恢復、需要重新啟動時,能夠對電源消息做出回應的應 用將顯得更為靈活和可靠。通過創建備份文件,您可更輕松地檢索掛起前存儲的數據,更省力實現掛起恢復。
系統掛起
系統掛起進程主要用於節省移動設備的能耗或在移動某一設備前關閉硬盤。該進程一般會關聯到用戶設置, 用來關閉特定時間內未被使用的設備。如果電源設置適當,還可通過關閉筆記本電腦頂蓋來調用系統掛起進程。為了展示與系統掛起相關的消息模式,我們以遠程控 制方式連接到了測試系統,並在測試系統上調用 Spy++,用來檢查和捕捉由測試系統廣播的 WM_POWERBROADCAST 消息和事件。通常,應用收發的消息模式如下:
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMQUERYSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001] SWM_POWERBROADCAST (long)dwPowerEvent:PBT_APMSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
(注:以字母“S”開頭的命令行表示消息需要通過 SendMessage() 函數發送。而以字母“R”開頭的命令行表示來自應用的消息返回值。) 上述模式表明:系統向應用發送一個包含 PBT_APMQUERYSUSPEND 事件的 WM_POWERBROADCAST 消息並請求應用許可,如果得到允許則掛起系統。該消息和事件會發送給系統內當前運行的所有應用、線程及進程。在所有事件中,該事件通常會首先引起系統掛 起。收到消息后,應用應當在返回前做好關閉准備。這些准備工作根據不同應用而異,但應當包含諸如將數據庫表存儲到硬盤、保存文件到硬盤、釋放內存或共享資 源、斷開網絡連接或關閉任何其它有利於確保應用安全退出的任務。一般情況下,系統最多能從應用的消息隊列中提取 20 秒長的消息。因此,任何必要的關閉操作都應當在收到事件時立即開始。本案例中,應用返回值為“TRUE(真)”,表示可以繼續執行掛起。
系統掛起前發送的消息為帶有 PBT_SPMSUSPEND 事件的 WM_POWERBROADCAST。而返回值“TRUE(真)”由應用返回。
拒絕掛起請求
如果應用不准許或“不希望”系統執行掛起,就會通過某種方式通知系統請求已被應用拒絕。為此,應用會 在收到 PBT_APMQUERYSUSPEND 事件時返回 BROADCAST_QUERY_DENY 值。這時系統就會“知道”:某一應用或進程不能或“不允許”掛起操作。在應用拒絕掛起的情況下,消息模式如下:
S WM_POWERBROADCAST (long) dwPowerEvent:PBT_APMQUERYSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:424D5144]
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMQUERYSUSPENDFAILED
請注意,在該案例中,lResult 中的應用返回值為 424D5144,即 BROADCAST_QUERY_DENY 的值。隨后,系統發出 PBT_APMQUERYSUSPENDFAILED 事件,通知所有應用:掛起請求失敗,繼續執行應用內的正常操作。
此外,我們也可以使用 SetThreadExecutionState() 函數防止系統掛起。該函數用來向系統發出其正在被使用的通知,並完全防止系統發送用來執行掛起的 WM_POWERBROADCAST 消息和事件(正如我們的測試所表明的那樣)。但系統會繼續發出用來處理電池和電源狀態變化的事件,因此這些事件仍然可被監測到。有 3 個標記與此函數存在關聯:ES_SYSTEM_REQUIRED——表明某些操作正在執行;ES_DISPLAY_REQUIRED——表明某些操作需要 進行顯示;ES_CONTINUOUS——通知系統在發生變化前保持伴隨狀態。我們建議:像傳真服務器、應答機、備份代理以及網絡管理這樣的應用都應當使 用 ES_SYSTEM_REQUIRED,而多媒體應用(如視頻播放器和演示應用等)應當使用 ES_DISPLAY_REQUIRED。如未使用 ES_CONTINUOUS,空閑計時器將被完全重置,因此需要定期調用該函數來重置計時器。但也要注意,該函數不會阻止因關閉筆記本電腦頂蓋造成的系統 掛起。從根本上阻止系統掛起的唯一辦法就是拒絕上文中提到的 PBT_APMQUERYSUSPEND 事件。此外,該函數不會一直阻止屏保的運行。另外還有許多其它方式也可以阻止掛起,如事先定義好一個時間值,然后用 SystemParametersInfo() 函數來查詢和重置屏保超時數值。
從掛起狀態恢復
對應用而言,離開掛起狀態並繼續執行操作與進入掛起狀態這一系統行為同等重要,因此應用開發人員在處 理進程執行中收到消息時應尤其注意。恢復內存、磁盤狀態以及數據庫表這些案例僅代表了應用中的部分元素,而應用離開掛起狀態時還需要進行刷新和重新配置。 如果這些元素未處於正常狀態,那么即使是正常的應用行為也會造成數據的破壞和丟失。在掛起過程中,WM_POWERBROADCAST 消息用於向應用發出正在/已經完成恢復操作的告警。一般來講,下列消息和事件都會由操作系統發出:
S WM_POWERBROADCAST (long)dwPowerEvent:0012
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMRESUMESUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
第一個事件為 0x12,對應 PBT_APMRESUMEAUTOMATIC。該事件僅用於向應用發出正在執行自動恢復操作的通知。通常情況下,該事件無需回應,但也有一個例外——這 一點會在下文中講到,設計應用時要注意這個問題。下面要發出的事件為 PBT_APMRESUMESUSPEND。只有在執行掛起進程的過程中發送 PBT_APMSUSPEND 的情況下,該事件才會被發出。這表明:系統已經安全完成了恢復操作,應用可以繼續執行。如果應用在掛起時釋放內存、數據庫表或其它重要信息,開發人員就必 須對這些應用內的運行元素重新進行初始化或分配。上文已經提到,使應用保持正常運行狀態十分重要。
其它能夠收到的恢復事件包括:PBT_APMQUERYSUSPENDFAILED 和 PBT_APMRESUMECRITICAL。上文已經說過,如果某些其它進程已經拒絕了系統發出的掛起請求,系統就會收到表明應用可以繼續執行正常操作 的PBT_APMQUERYSUSPENDFAILED 事件。PBT_APMRESUMECRITICAL 會在部分或全部應用沒有收到 PBT_APMSUSPEND 事件(如在電池耗盡后的情況)時發出。消息是非常重要的,因為如未收到消息,那么先前可用的資源或數據可能會變得無法使用。而對於異常脆弱的網絡連接,應 用更應提前保存位於網絡上的任何文件或數據。總之,任何應用都應當在收到這一事件時恢復到最佳狀態,保證最理想的效能。
休眠
系統關閉時,休眠功能會完全保留系統狀態,以便在電源重新接通時系統能夠恢復到休眠前的狀態。但是, 當系統由掛起狀態轉入休眠狀態,然后再回到先前狀態時,消息模式中會出現一件十分有趣的反常現象。下面的例子便描述了該模式下的異常狀態:
即將掛起
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMQUERYSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
即將休眠
S WM_POWERBROADCAST (long)dwPowerEvent:0012
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMQUERYSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
S WM_POWERBROADCAST (long)dwPowerEvent:PBT_APMSUSPEND
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
按下開機按鈕
S WM_POWERBROADCAST (long)dwPowerEvent:0012
R WM_POWERBROADCAST fSucceeded:True [lResult:00000001]
S WM_POWERBROADCAST (long)dwPowerEvent:0012
系統進入休眠狀態時,事件順序發生了變化,當我們再次啟動系統后,接收到了 PBT_APMAUTOMATICRESUE,0x12 事件。與正常順序比較后我們發現:PBT_APMRESUMESUSPEND 事件不見了,這表明系統執行了恢復操作。測試后我們得出結論:有些情況下,只能接收到一條 0x12 代碼;而有些情況下則會收到兩條。無論怎樣測試,從休眠狀態恢復時我們都沒有收到 PBT_APMRESUMESUSPEND 事件,即使文檔記錄中提示該事件將發出的情況下,我們也未收到。也許我們需要在 PBT_APMRESUMESUSPEND 和 PBT_APMAUTOMATICRESUME 事件之間進行協調,以確保應用能在掛起操作之后保持正常的運行狀態。
代碼實例
為便於讀者透徹了解如何處理與掛起和恢復操作相關的消息和事件,我們特舉如下范例說明:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
.
.
.
case WM_POWERBROADCAST:
switch (wParam)
{
case PBT_APMQUERYSUSPEND:
PrepToSuspend(TRUE);
return (TRUE);
case PBT_APMQUERYSUSPENDFAILED:
ReInitVars();
break;
case PBT_APMRESUMESUSPEND:
ReInitVars();
break;
case PBT_APMSUSPEND:
break;
case PBT_APMRESUMECRITICAL:
ReInitVarsCritical();
break;
case PBT_APMBATTERYLOW:
MessageBox(hWnd,”Battery is Critical”,”Low Battery”,MB_OK);
break;
case PBT_APMPOWERSTATUSCHANGE:
AdvisePowerChange();
Break;
case PBT_APMOEMEVENT:
break;
case PBT_APMRESUMEAUTOMATIC:
break;
}
.
.
.
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
這個方法可以處理每個與 WM_POWERBROADCAST 消息存在關聯的事件,但絕對不是唯一的方法。請注意,在收到 PBT_APMQUERYSUSPEND 事件並返回 TRUE(真)后,應用對 PrepToSuspend(TRUE) 的調用也隨之發生。如果應用需要拒絕掛起請求,只需返回一個 BROADCAST_QUERY_DENY,而不必像實例中那樣調用函數。還需注意的是,每個非關鍵恢復相關消息都會調用 ReInitVars() 函數,而關鍵恢復事件則會調用 ReInitVarsCritical() 函數。函數內容的差異可以通過一個更為強大且面向關鍵恢復的初始化過程(前文已經提到)反映出來。
上面的實例中還列出了本文並未提及的 3 個事件。PBT_APMOEMEVENT 並不是一個統一事件,OEM 廠商會根據自己的判斷來嘗試捕捉各自事件。多數情況下,該事件無需回應。如果系統標出了這一狀態,PBT_APMBATTERYLOW 就會被收到。必要時還會采取其它適當行動,而不僅僅是通知用戶電池電量不足。PBT_POWERSTATUSCHANGE 屬於事件,由應用處理。該事件會標示出機器中電源狀態的變化,並及時報告電池電量信息及其它應用所需的重要數據。具體細節還很多,就留待各位讀者自己去發 現吧。
結論
每次審視應用的新功能、確定其是否與特定產品相關時,我們都會提出“我們為什么要這樣編寫應用?”這 個老問題。在解決掛起這個問題上,隨着越來越多的人選擇購買筆記本電腦並將其作為家用和辦公環境中的主要系統,如何避免因掛起導致的數據丟失便成了擺在應 用開發人員面前的一個日益緊迫的問題。能夠自由的掛起或恢復至運行狀態、將錯誤和問題降至歷史最低點、像欣賞藝術那樣體驗應用掛起——這就是用戶心中的理 想應用。需要依賴網絡中的文件或內容,需要在無需用戶交互的情況下確保長時間的顯示和運行,需要能夠捕捉用戶輸入用於存儲或日后調用的信息,需要存儲大量 數據等等——只有符合這些標准的應用才能可靠地處理此類消息和事件。軟件架構師和戰略家能夠設計出更為可靠、更加優秀的應用,幫助人們應對移動辦公和生活 方式帶來的挑戰。
如今,要滿足用戶對移動計算的需求,就必須對軟件需求進行一次重大改造。而操作系統可以利用 WM_POWERBROADCAST 消息來標示系統運行過程中即將出現的各種變化。眾所周知,掛起功能可以節約電池能耗,今后勢必將給應用帶來更為深遠的影響。而應用設計也應更加重視能耗問 題,想方設法延長電池的使用時間。通過合理安排消息與事件,我們終將實現降低應用能耗的目標。
http://blog.csdn.net/harbinzju/article/details/5844791