文章目錄
- 3.3.1 處理掛機鍵隱藏所有視窗的問題
- 3.3.2 全屏/非全屏的問題
“很多O2O領域創業者每個人心底都有個“大而全平台”的想法,但創業伊始就那麼做往往不現實,要充分考慮手頭‘可調配資源’,這裡指的是自身有沒有對內對 外hold住的能力。希望看到更多的滿懷遠大理想、認認真真做事踏踏實實執行的創業者,別就偉大藍圖卻抓不住切入點,必須能找准垂直細分做透!”——這是最近在weibo上看到的一句話,深有感觸,我想互連網行業發展至今,對我等草根而言,大而全的平台是越來越不可能的了,其實當初按照老吳的想法,就是想把這個軟體做成一個平台……這個具體我就不展開了,還是繼續談談這個項目及其相關技術,希望有人能從中獲益。
三、基礎技術3.1,C++還是.net
我在著手做這個項目的時候,使用的是C++作為開發語言,說起C++,我話就多了,我不想在此挑起無聊的語言之爭,但我想說:每一個程式員都應該掌握C++。當然,這個項目使用C++,而不是.Net不只是我個人偏好的原因,考慮到Windows Mobile的硬體和許多實際情況,唯有C++的效能能夠應付得來,引用當初在Windows Mobile開發群裡一位老兄的話:“用.net的話不是快和慢的問題,而是根本能不能啟動並執行問題。”程式中使用了大量的貼圖,系統通知訊息處理,註冊表訪問,甚至硬體操作,這些功能使用原生代碼(Native Code)實現的話就很方便,而使用.net可是半點鐘優勢都沒有,還不說很多Windows Mobile手機都沒有安裝.net,如果強制使用者安裝的話還會一定程度上遭到使用者的抵觸。
註:Windows Mobile下的.net全稱叫“.NET Compact Framwork”,和Windows環境下的.net是不太一樣的,有興趣的話可以參考[維基百科]。
我使用了純粹的Windows編程,直接使用Windows API來寫這個程式,連MFC都沒有用到,因為在這麼一個特殊的應用裡,那些類庫協助不大。
那今天回頭看看,正好提些問題:前面說到效能,那麼Android用Java來開發,效能差嗎?今天還存在前面說的那些問題嗎?——很大程度上,這些問題都沒有了,因為今天的手機的效能實在太強悍了,四核手機都開始普及了,Android系統也在正常化,Google已經著力在改進Android片段化的問題,開發人員不必費盡心思去挖掘系統底層的那些函數和方法來實現自己的某些功能了。
3.2,今日外掛程式還是普通程式
Windows Mobile從它還是Pocket PC的時候開始,就有了一個叫“今日”(Today)的案頭,就相當於Windows的Desktop,大家看看:
(Windows Mobile的“案頭”——Today)
是不是覺得特別老土?還有這麼小的東西怎麼用手指操作?但它這麼設計也是有它的道理的,過去的手機跟現在很不一樣,有些根本沒有觸屏,還是靠導航鍵和OK鍵來選定,如這款Samsung i718手機,具有一定代表性:
(Samsung i718)
即便有觸屏的手機,恐怕也很難用手指操作,大多數是這樣操作的:
(用筆尖操作觸屏)
無限今日的做法就是做一個案頭外掛程式,把所有的內容渲染都做在這個外掛程式上,當然,SoSoPi也有,這是後來添加的功能,但僅僅是給使用者一個方便的啟動SoSoPi的功能:
(SoSoPi今日外掛程式)
SoSoPi並沒有做成外掛程式的形式,這一部分是因為今日外掛程式的調試難度相對較大,因為外掛程式有一套規則,按照那套規則去產生一個dll,然後把這個dll attach到Windows Mobile的shell進程去方可調試,另一部分則是老吳的意思,他希望程式能夠被隱藏掉,讓使用者看到自己的“案頭”,也就是“今日”。
3.3,解決一些棘手的問題3.3.1 處理掛機鍵隱藏所有視窗的問題
Windows Mobile的手機基本上都有一個“掛機鍵”,大家對這個鍵應該不陌生,過去的手機都有的,(參考上文Samsung i718的圖片)一個撥號鍵(通常是綠色的),一個掛機鍵(通常是紅色的),掛機鍵預設的行為是掛掉電話和顯示案頭,所以當自己想撤銷掉一切工作,隱藏掉所有程式的時候,猛按掛機鍵總歸沒有錯,可惜後面出來的手機基本上都去除了這經典的紅綠兩鍵。掛機鍵隱藏所有程式這個功能好是好,就是有點為難我了,因為我的程式作為一個“傳統型程式”,是不希望被這樣隱藏掉的,我嘗試了很多方法,包括用鉤子捕捉掛機鍵事件、隱藏後自動出現和屏蔽這個按鍵等等,效果都不理想,最後還是在開發群裡得到了最後的解決方案,說出來很簡單,就是在建立視窗的時候多加一個“WS_POPUP”屬性。我的天啊,就這麼簡單?
真的就這麼簡單,可知道這麼幹卻花掉了我好多時間,正所謂會則不難,很多事情就是這樣……大家在開發過程中有沒有過類似的經曆?
3.3.2 全屏/非全屏的問題
從技術意義上說,Windows Mobile的絕大多數程式都是“全屏”的,也就是說,一個螢幕上只顯示一個程式,所有程式預設都是“最大化”,而這裡的全屏指的這是:隱藏掉工作列,隱藏掉工具列,隱藏掉IME按鈕,並把程式視窗大小和位置調整為鋪滿整個螢幕。這樣才是對“全屏”的技術描述。
(工作列,功能表列與IME按鈕)
這些看起來不是問題的東西,在Windows Mobile下怎麼都成了問題,原因就是片段化,我們現在聽得最多的片段化我想來自於Android系統,每個手機廠商都想對系統進行一些差異化定製,有意無意地修改了系統的預設行為,導致軟體廠商在做開發的時候遇到一些不相容的問題,我想說的是:對比Windows Mobile,Android真是好太多了,起碼它還有些章法可循,而Windows Mobile則毫無章法,這個我在之後會詳細說。
最後,我找出了一種比較好的,讓程式能夠全螢幕顯示,並且輕易切換到非全螢幕顯示的方法,在諸多機器上測試通過,沒有太大問題,當然,其中也是經過了大量的嘗試。程式碼片段是這樣:
if(g_gm.IsFullScreen()) { // It is the responsibility of the application to make sure it is sized // FULL SCREEN before using this flag. Otherwise, it will appear as though // the function did nothing. // -- MSDN RECT rectFullScreen; SetRect(&rectFullScreen, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); SetWindowPos(hWnd, 0, rectFullScreen.left, rectFullScreen.top, rectFullScreen.right-rectFullScreen.left, rectFullScreen.bottom-rectFullScreen.top, SWP_NOZORDER); SHFullScreen(hWnd, SHFS_HIDETASKBAR|SHFS_HIDESIPBUTTON); } else { SHFullScreen(hWnd, SHFS_SHOWTASKBAR|SHFS_SHOWSTARTICON|SHFS_HIDESIPBUTTON); RECT rectWorkArea; SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)&rectWorkArea, 0); SetWindowPos(hWnd, 0, rectWorkArea.left, rectWorkArea.top, rectWorkArea.right-rectWorkArea.left, rectWorkArea.bottom-rectWorkArea.top, SWP_NOZORDER); }
注意看看那段注釋,都是細節,這種細節在Windows Mobile的開發過程中遇到太多了,這個還好,其它很多則根本就沒有文檔提及。
(SoSoPi的全屏與非全屏)
四、貼圖4.1,為什麼用貼圖?
開發一個程式的時候,我們習慣於用控制項,按鈕,下拉框,單選框,複選框,文字框……可你認為這些東西適合用在SoSoPi嗎?試想想SoSoPi的首頁需要多少個怎麼樣的控制項,其它頁需要多少個?這樣加起來可不得了,要知道,在Windows編程中,每個控制項其實就是一個子視窗(Child Window),而Windows Mobile的資源是十分有限的,它的開發文檔上並不推薦我們在一個視窗中放置大量的子視窗,另一個重要的原因就是用控制項不利於介面的定製,SoSoPi介面上很多效果的實現都跟現有控制項的行為差距甚大,控制項對我的開發沒什麼協助。
4.2 DirectX?
說起DirectX,我是有點心有餘悸的,這東西太晦澀難懂,所以我們在Windows下編程,需要圖形加速的話通常都用現有的引擎,不太會自己再重新去研究DirectX介面了,我很清楚它的難度,因為這事情我以前做過,太多圖形學專業的東西我不懂,出不了什麼成果。而現在,要做些簡單的動畫效果的話別的方法很多,WPF,Silverlight,Flash,甚至HTML5,總之就是不要自己再去碰底層,否則就算寫出個湊合能用的東西,回頭一看也是通篇的垃圾代碼。
另一個關鍵的原因是:經過一系列的研究,我發覺Windows Mobile根本就不支援DirectX加速,雖然有介面提供,但調用會出現莫名其妙的失敗,並且每台手機的行為不一致,貼圖速度根本沒有任何提高,我的結論就是:使用DirectX不會提高效率,反而會提高代碼的複雜性。
4.3 位元影像及貼圖效率
位元影像分為兩種,DIB和DDB,DIB(Device Independent Bitmap)是裝置無關位元影像,DDB(Device Dependent Bitmap)是裝置相關位元影像。簡單地說,能在各種不同的裝置上顯示出來的位元影像就是DIB,否則就是DDB。那我們常見的jpg檔案是DIB還是DDB?——肯定是DIB了,jpg哪都能用對吧?bmp呢?還不是?哪都能用啊。那DDB是什嗎?DDB存在於你的顯示裝置的記憶體中(嗯,俗稱顯存),你之所以能在螢幕上看到你的照片,那是顯存中有這麼一個地區,擺放了你的照片,這個地區的資料格式是裝置相關的,不同的顯卡、解析度和色深,都可能導致其格式不一樣,但它是最直接的位元影像顯示方式,所有的DIB要真正顯示出來,都要轉為DDB。
我花了很多時間去研究貼圖效率,研究的結果竟然是這個:要想貼圖快,就得把那些要描繪的圖轉為跟Windows Mobile的顯示裝置相關的位元影像。
將一個裝置相關位元影像,通過BitBlt繪製到視窗上去的速度是相當快的,我調試下來發覺一張滿屏的位元影像通常只需要1-2ms,這麼快的速度足夠實現流暢的動畫效果了!
Windows Mobile的裝置相關位元影像用兩個位元組(16bit)來表示一個點(俗稱16位色),RGB分別佔據5bit、6bit和5bit,用16位色而不是24位色我想完全是從資源節省的角度考慮。那現在問題來了:我要貼半透明的圖片怎麼辦?
半透明的位元影像,不可能是DDB,前面提到了,用16bit表示一個點,裡面根本就沒有表示半透明度的地方啊?這個我還真沒什麼好辦法,所以我在設計的時候,就盡量把半透明貼圖做得小一點,這樣運算量會少很多。半透明貼圖總的來說是要比DDB的BitBlt慢很多的,一張全屏的半透明貼圖,得花上100ms左右,在較快的機器上,也得花上20-30ms。
在把這些主要的問題都研究透徹之後,我寫了一個Demo程式,用來示範類似HTC Sense的底欄的滑動的效果,拿給老吳看,老吳贊口不絕:“太厲害了,居然比官方(HTC)的還流暢!”老吳這樣的誇獎還是很少見的,看得出來他當時很興奮。
關於貼圖相關的技術,我有一篇部落格: http://www.cppblog.com/guogangj/archive/2010/06/20/118316.html
4.4 程式的貼圖方式
我省略掉了圖片載入的具體過程,我有些擔心本文因為描述了太多的細節而對讀者失去吸引力。而貼圖方式則是一套很混雜的代碼,為什麼會這麼混雜,還不是因為要提高那點效率,我在代碼中使用了大量的技巧,這裡面已經很難用文字描述清楚了,所以我畫了這麼一個圖,示範手指按下滑動區滑塊從一個模組切換至另一模組的貼圖過程。
(貼圖原理圖)
1,手指按上滑塊的時候,就截個屏,把螢幕的內容存放到一個緩衝位元影像中,這個緩衝位元影像稱為“FastSurface”,這是我起的名字,意思是協助快速貼圖的緩衝位元影像,它是一個裝置相關位元影像。這個動作是相當快的,通常只需要1-2ms。再在這個FastSurface上繪製一層半透明遮蓋,這個動作是一個半透明貼圖,執行速度較慢,在比較慢的手機上大概需要100ms左右,但所幸的是在這個移動滑塊的過程中,只需要一次。
2,為了使得繪製過程不出現閃爍,我建立了一個跟程式視窗大小一致的裝置相關位元影像,叫“BackSurface”,所有最終要繪製到螢幕上的東西都先繪製到BackSurface,現在,把剛才產生的帶半透明覆蓋層的那個在FastSurface上的位元影像繪製到BackSurface的對應的位置去,這個過程很快,也是1-2ms。
3,滑塊區背景和工作列上的內容我同樣建立了裝置相關位元影像作為其緩衝,現在,把它們也繪製到BackSurface的對應位置去,這個動作也是很快,1ms這樣的開銷。
4,接下來把要展現在上層的一些元素繪製到BackSurface上去,包括滑塊區的表徵圖,滑塊,螢幕中心區表徵圖和文字,這些內容會隨著使用者手指的移動發生改變,所以無法緩衝,只能直接繪製,而且大多是半透明貼圖,開銷相對較大,但由於要貼圖的圖片都不太大,所以通常都能在30ms左右完成。
5,最後就是把BackSurface貼到螢幕上去。這個1-2ms的耗時。
手指在移動的時候重複動作2到5,就能看到動畫效果,在很舊的機器上,也能達到20多幀每秒,已經算是極限了。而在效能較好的機器上,相當流暢,如老吳的HTC HD2,用起來是很爽的。
從這個例子看,我的基本的思路就是:減少繪製!把不用每次都要繪製的元素,緩衝到裝置相關位元影像裡,只繪製必須繪製的。
實際的繪製過程還比我上面描述的要複雜一些,這其中還有一些細節,比如剪圖,在繪製滑動區的表徵圖的時候,要將超出邊界的部分剪掉,這些細節我就不一一列舉了,從原理上看,貼圖基本上就是這樣。
也許你要問:那你看看那些Windows Mobile上的遊戲,為什麼能達到那麼流暢的效果呢?而且還是3D的。——呃,這個問題我還真不知道怎麼回答,我確確實實用HTC HD2玩過Windows Mobile版的極品飛車,效果還不錯,但我真不知道這是怎麼做出來的,眾所周知,Windows Mobile是一個完全可以由裝置製造商定製的系統,也許有不少手機製造商在其中加入了對3D渲染的硬體支援,使得運行這些大型遊戲成為可能,你想確實這些遊戲也並非所有手機都能跑呀。
最後,關於貼圖,我還要提的一點就是:由於程式運用了大量的這種前面所提及到的“技巧”,其代碼也就變得有些難維護了,如果後面還有人來維護這套代碼的話(當然這個可能性為0,這裡只是學術性的“如果”),他得好好看看這篇文章,明白了我的意圖之後,才能比較好地去做維護,編程中其實這種技巧運用得越多,代碼的品質恐怕也就越糟糕,但這是沒辦法的。
4.5 圖片旋轉
使用Windows GDI、GDI+或WPF去旋轉一張圖片是不難的事情,可Windows Mobile不支援圖片的旋轉。當我需要繪製一個類比時鐘的時候,想用貼圖的方式來繪製時分秒針,就真是個問題了,我嘗試了許多的方法,最簡單的方法當然是用繪製線條的方式代替位元影像旋轉,但效果並不好,而且這樣做不利於個人化,最後我找到了一種用二次線性插值旋轉位元影像的方法,代碼有少少複雜。旋轉的如下:
(圖片旋轉效果)
大家可以清楚地看到,旋轉後的圖片尺寸發生了變化,圖片本身也會“走樣”,這是顯而易見的,那麼,我們如何繪製一個類比時鐘呢?——必須確定針的中軸位置,所以我們規定:作圖的時候要把針軸放在最中心點!這樣一來,不管怎麼旋轉,針軸還依然是在最中心點……這樣,有問題嗎?各位。
(時鐘的針)
有問題!舉個例子,如果你做了一張5*5的圖,中心點顯而易見是(2,2),可一旋轉,這張圖可能就變成了6*6,那中心點請問是……所以按照這種旋轉圖片的方法繪製出來的類比時鐘,仔細看總歸是有那麼一點瑕疵,經過無數次的實驗和校正,我都沒辦法繪製出一個完美的時鐘,我認為這個問題“無解”。事實上並非無解,公司的美工就拿了一個別人的產品給我看,這是一家國外公司的產品,所繪製出來的類比時鐘真是十分精美,而且時分秒針動起來可是相當的完美,我想不出他們是如何?的,也許是專門分別繪製了15幅時分秒的貼圖,(水平/垂直翻轉圖片不會出現走樣,所以只需要15幅圖,而不是60)而不是採用旋轉圖片的方式,我猜的。
最後,SoSoPi並沒有提供類比時鐘的繪製功能,因為我編寫的那個二次線性插值的位元影像旋轉方法實在太慢了(其實我已經儘力最佳化它了),一個稍大的圖片的旋轉,都得耗上幾百毫秒,耗時也耗電,所以最終還是拿掉了。
(待續...)