優秀程式員的天性就是好奇,軟體是怎麼運作的、螢幕是如何顯示的、案頭表單為何能如此人性化的被滑鼠拖動?如果你經常會有這樣一些問題迸發在腦海中,恭喜你,你是一名很有潛力的程式員。
我在大學讀的是自動化專業,屬於電子類,再者對電腦相當感興趣,第一次看到這玩意時,就覺得這東西太神奇了(其實當時只要看到有螢幕的東西,都覺得很神奇)。硬體+軟體的深入讓我直接打通了瞭解這一神秘機器的任督二脈。軟體我不是最牛逼的,硬體其實我很歇菜。但是透過硬體看軟體估計我還有那麼一點點發言權。下面相關內容是個人小小的感性認識,希望能對大家有一定啟發。因為個人從畢業至今做了約2年安卓,所以就拿安卓來看吧。
安卓屬於作業系統(以下簡稱os),運行於硬體之上,os上可搭載app,app基於os暴漏的api編寫。編寫安卓程式時,你會看到程式引用了android.jar(這玩意在"ad目錄\sdk\platforms\android-17"下,約18.2M),程式之中用到的大部分api都在這裡(如:activity),但是一個奇怪的現象時,編譯的apk中並沒有這玩意,一個簡單的demo編譯的apk只有幾M。但是如果引用第三方類庫(如libs中匯入jar包、引用外部包)時,編譯的apk中就會包含該jar包,並且體積增大。這是因為android.jar中的代碼已經存在於作業系統內了(不一定叫android.jar,且分散在多個jar中),而寫代碼時用的android.jar主要作用是用來做編譯時間檢查、支援代碼提示、跳轉至源碼,android.jar就是一個編程範本。
ok,編寫完最小demo後,就可以運行在安卓手機上了,一小段代碼即可顯示很豐富的介面、按鈕、點擊處理。但是這一小段代碼脫離os是無法運作的,簡單的效果背後是龐當的體系在作支撐。
比如顯示,xml編寫的布局檔案,運行時會被翻譯成java,類、屬性、結構層次一一對應(所以xml寫的東西一般能完全用java替代)。顯示的視圖大部分基於View,View.draw負責該View的全部繪製,你所看到的按鈕的預設背景以及點擊效果是系統內建的png圖片。但是View.draw到底是誰調用呢,如果打斷點調試,會發現層次非常的,從外向內調,很容易看暈(斷點找機制這種事要長期磨練,非一日之寒)。斷點能看到最終發起者是Choreographer(這玩意負責"繪製/事件"等的"發起"),那Choreographer又是被誰驅動去"發起繪製"的呢,ActivityThread,ActivityThread是apk每幀運作的根源。瞭解GUI編程的同學知道,程式看是沒有運作,但是可以響應點擊,是因為app在一直監視著你,這種監視叫"訊息迴圈"(說白了,就是個死迴圈),這個死迴圈一直在判斷有沒有訊息(你可以簡單的把它理解為一個標誌位)到來(比如點擊),如果有有就分支回調相應處理函數。
ok,Choreographer是如何被驅動的就又清晰了一步,有人向ActivityThread的訊息迴圈的投了一個"繪製訊息"。但是這個訊息是哪來的呢。程式第一次運行時,會主動拋一個(你可以假想在程式初始化中拋),運行時,各種點擊、動畫都會拋(類似view.invalidate的東西),觸發重新繪製。
ok,繪製觸發的根源大概也追蹤到了一定程度。但是視圖是如何顯示在螢幕上的呢,雖然view.draw中可以很清晰的看到用canvas畫了寫什麼東西(如最常用的canvas.drawBitmap),但是canvas的這些draw到底是怎麼傳送到螢幕上的呢。這一段根源我暫時還沒探究過源碼,但是畢竟搞過arm,手動驅動過顯示屏,這裡只做大致猜測,基本原理應該差不到哪去。
基本思路是,app繪製→os→驅動→硬體,哈哈,貌似所有和硬體相關的最終機制都是這樣。驅動是直接和硬體打交道的,一般是彙編或c編寫,直接控制硬體介面,一般被稱為hal(硬體抽象層)。os作用中間橋樑。canvas含一塊bitmap(對應一塊記憶體),canvas的各種draw會形成像素矩陣(螢幕像素點的描述)存在bitmap內,經過os傳遞給驅動,驅動直接驅動螢幕。此間的細節代碼、機制相當繁瑣。但理論上,映像描述在記憶體中,基本上就可以顯示出來了,那bitmap這塊記憶體是如何會顯示出來的呢?
有一塊記憶體叫"顯存"(是不是很熟悉,台式機的顯存一般可放在顯卡中,其實放哪都是塊記憶體),顧名思義,顯存就是用來顯示的記憶體。基本原理是,驅動用兩個for(行列)掃描顯存,讀取每個像素,將單個像素訊號設定到對應螢幕的一個點上。兩個for就是一個面了。但是這單個像素訊號是怎麼設定到螢幕單點上的呢?這裡我們可以在將單點簡化為"電燈泡",只有兩種顏色(白+黑),燈泡接在主板的一個io介面上,io口上電,燈就亮。斷掉,燈就黑。驅動的代碼根據顯存中單點資料,控制io介面的上斷電即可。
ok,複雜一點,現在的螢幕顏色是RGB(0xffffff表白色,ARGB中alpha只是用來給多個層運算混合,最終顯示的還是RGB)。一個螢幕點其實由三個點(紅/綠/藍)組成(貼近你家的電視會看得比較清楚)。每個點的強弱從不亮到最亮劃分等級,用一個位元組表示(0x00~0xff,即0~255,共256級),一個點接8個io介面就可以控制了,驅動控制8x3個io口就可以顯示一個點的所有顏色了。
是不是清晰許多了。但是一個螢幕有這麼多點(1024x768=69632),晶片雖小,但也不可能有這麼多介面吧。玩過嵌入式的同學知道,一般接一個顯示屏,應該不會超過50根線(應該比這少的多,記得不是很清楚)。剛才說過驅動的繪製一般是用兩個for一個點一個點的掃的,所以理論上暴漏一個點的介面(8x3個)就可以了(實際稍有差別),但是螢幕怎麼知道這一個點是繪製在哪一個實際點上呢?主板、螢幕通過約定好的時序同步進行就可以了(加幾條同步訊號介面,表示哪行哪列)。
這次大概就漫遊到這裡吧,窮追不捨不是為了死磚牛角尖,而是一步步驗證自己的思路,鍛煉自己的左腦。安卓中結構最好的源碼莫過於andorid-os,那是凝聚力多少思維的結晶。