新項目的手機需要實現關機狀態下的鬧鐘,早在剛開始接觸 android 的時候都在想為什麼 android 不支援關機狀態下的一些功能呢?像充電或者鬧鐘什麼的,雖然每個平台的驅動不一樣但上層應用是可以提供統一介面的呀,果然在 4.0 的時候支援關機充電了,關機鬧鐘仍然不在預設支援中。市場上的很多品牌手機也都不支援這個功能,讓很多用慣了 Feature Phone 以及擔心輻射的使用者都不習慣。這次做關機鬧鐘在一些思路上借鑒了關機充電的實現方法。整體思路如下:在 uboot 中通過 PMU 判斷開機的原因,如果是 RTC 模組使能開機則在 uboot 中傳遞啟動參數 androidboot.mode=alarm,然後在 init 進程中判斷啟動模式(當前系統有 3 種啟動模式:normal、charger、alarm),如果是 alarm 模式則啟動 alarm 服務,alarm 服務與應用程式 alarm關聯,因此需要編寫應用程式來實現關機鬧鐘的功能。應用程式主要實現以下幾個個方面的功能:1、顯示關機鬧鐘的 UI 以及目前時間;2、播放鬧鈴;3、讀取 input 事件判斷使用者操作;4、使用者可以在 UI 中選擇懶人模式、開機或者關機。下面逐個解析這幾個功能的實現:1、UI在 zygote 沒有啟動之前完成 UI 顯示可以參考 charger 的做法,用 android 的 minui 介面,這些介面實現了圖形的描繪以及固定大小的文字顯示,函數介紹如下:[cpp] int gr_init(void); /* 初始化圖形顯示,主要是開啟裝置、分配記憶體、初始化一些參數 */ void gr_exit(void); /* 登出圖形顯示,關閉裝置並釋放記憶體 */ int gr_fb_width(void); /* 擷取螢幕的寬度 */ int gr_fb_height(void); /* 擷取螢幕的高度 */ gr_pixel *gr_fb_data(void); /* 擷取顯示資料緩衝的地址 */ void gr_flip(void); /* 重新整理顯示內容 */ void gr_fb_blank(bool blank); /* 清屏 */ void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); /* 設定字型顏色 */ void gr_fill(int x, int y, int w, int h); /* 填充矩形地區,參數分別代表起始座標、矩形地區大小 */ int gr_text(int x, int y, const char *s); /* 顯示字串 */ int gr_measure(const char *s); /* 擷取字串在預設字型檔中佔用的像素長度 */ void gr_font_size(int *x, int *y); /* 擷取當前字型檔一個字元所佔的長寬 */ void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); /* 填充由source指定的圖片 */ unsigned int gr_get_width(gr_surface surface); /* 擷取圖片寬度 */ unsigned int gr_get_height(gr_surface surface); /* 擷取圖片高度 */ /* 根據圖片建立顯示資源資料,name為圖片在mk檔案指定的相對路徑 */ int res_create_surface(const char* name, gr_surface* pSurface); void res_free_surface(gr_surface surface); /* 釋放資源資料 */ 圖片只支援 png 格式,做這個 UI 的圖片資源花了不少時間(沒做過美工),一般圖片的顯示先由 res_create_surface 建立資源資料,然後調用 gr_blit 填充,最後調用 gr_flip 重新整理顯示。在關機鬧鐘的介面還需要顯示目前時間,最開始調用 minui 預設的字型檔來顯示,但是預設字型檔的字型太小了,只支援 10 x 18 ASIC-II 編碼的字元,效果很不好,後來就把時間需要的 10 個數字以及符號以圖片的形式顯示。2、鬧鈴在這個階段播放鬧鈴只能選擇 tinyplay,tinyplay 是 android 內建的一款簡易播放器,只能播放固定格式的 wav 檔案。UI 顯示以及播放鬧鈴分別獨佔一個線程,以保證各自不被幹擾。3、input evnt當鬧鐘開始響後,使用者可以通過觸控螢幕點擊選擇是否開關機或者進入懶人模式,這裡就需要對使用者操作做出判斷,即在程式中去讀取 /dev/input 下面裝置的資料。當進入懶人模式後會停止鬧鈴 5 分鐘再響,這個階段需要關閉 lcd 和 觸控螢幕,使用者可以通過按鍵喚醒 lcd。input event 是在進程去迴圈讀取並處理的,範例程式碼如下:[cpp] static int event_loop(void) { int i; int ret = 0; int nfds = ALARM_MAX_DEVICE; struct input_event event; const char *device = NULL; const char *device_path = "/dev/input"; ret = scan_dir(device_path); /* 掃描該目錄下的裝置節點,我們只開啟觸控螢幕和按鍵 */ if(ret < 0) { printf("scan dir failed for %s.\n", device_path); return ret; } for(;;) { poll(ufds, nfds, -1); /* 輪詢檢測是否有觸控螢幕或者按鍵事件 */ for (i = 0; i < nfds; i++) { if(ufds[i].revents) { /* have valid value. */ if(ufds[i].revents & POLLIN) { ret = read(ufds[i].fd, &event, sizeof(event)); /* 讀取事件 */ if (ret < (int)sizeof(event)) { printf("could not get event.\n"); continue; } handle_event(event.type, event.code, event.value); /* 處理事件 */ } } } } return 0; } 附:android 許可權管理機制這次在編譯使用 alarm 的時候遇到了一個關於許可權的問題:編譯出來的 alarm 可執行程式在 out 目錄下面是擁有可執行許可權的,但是在燒錄到機器後發現沒有可執行許可權了,最後才發現是 android 的許可權管理機制引起的。