Android應(yīng)用程序開發(fā)以及背后的設(shè)計(jì)思想深度剖析(5)

轉(zhuǎn)載于? http://www.uml.org.cn/mobiledev/201211063.asp#2

功耗控制

在嵌入式領(lǐng)域爽篷,功耗與運(yùn)算量幾乎成正比徽龟。操作系統(tǒng)里所需要的功能越來越復(fù)雜控嗜、安全性需求越來越高双饥,則會需要更強(qiáng)大的處理能力支持灶搜。像在老式的實(shí)時(shí)操作系統(tǒng)里善玫,沒有進(jìn)程概念,不需要虛擬內(nèi)存支持魄鸦,這時(shí)即便是寫一些簡單應(yīng)用宴杀,所需要的運(yùn)算量、內(nèi)存都非常小拾因,而一旦換用支持虛擬內(nèi)存的系統(tǒng)旺罢,則所需要的硬件處理能力、電量都會成倍上漲绢记,像一些功能性手機(jī)平臺扁达,可以成為一臺不錯的手機(jī),但運(yùn)行起一個Linux操作系統(tǒng)都很困難庭惜。而隨著操作系統(tǒng)能力增強(qiáng)罩驻,則所能支持的硬件又得以提升,可以使用更大的屏幕护赊、使用更大量內(nèi)存、支持更多的無線芯片砾跃,這些功能增強(qiáng)的同時(shí)骏啰,也進(jìn)一步加劇了電量的消耗。雖然現(xiàn)在芯片技術(shù)不斷提高生產(chǎn)工藝降低制程(就是芯片內(nèi)部燒寫邏輯時(shí)的門電路尺寸)抽高,幾乎都已經(jīng)接近了物理上的極限(40納米判耕、28納米、22納米)翘骂,但是出于設(shè)計(jì)更復(fù)雜芯片為目的的壁熄,隨著雙核、四核碳竟、以及越來越高的工作頻率草丧,事實(shí)上,功耗問題不但沒有降低莹桅,反而進(jìn)一步被加劇了昌执。

面對這樣越來越大的功耗上的挑戰(zhàn),Android在設(shè)計(jì)上诈泼,必須在考慮其他設(shè)計(jì)因素之前懂拾,更關(guān)注功耗控制問題。Android在設(shè)計(jì)上的一些特點(diǎn)铐达,使系統(tǒng)所需要的功耗要高于傳統(tǒng)設(shè)計(jì):Android是使用Java語言執(zhí)行環(huán)境的岖赋,所有在虛擬機(jī)之上運(yùn)行的代碼都需要更大的運(yùn)算量,使用機(jī)器代碼中需要一條指令的地方瓮孙,在虛擬機(jī)環(huán)境下執(zhí)行則可能需要十幾條指令唐断;與其他偽多任務(wù)不同选脊,Android是真實(shí)多任務(wù)的,多任務(wù)則意味著在同一時(shí)刻會有更多任務(wù)在運(yùn)行栗涂;Android是構(gòu)建上Linux內(nèi)核之上的系統(tǒng)知牌,Linux內(nèi)核在性能上表現(xiàn)奇佳,在功耗處理上則是短板斤程,就拿PC環(huán)境來說角寸,Linux的桌面環(huán)境在功耗控制上從來不如其他操作系統(tǒng),MacOS或是Windows忿墅。

當(dāng)然扁藕,有時(shí)沒有歷史包袱,也未必就是壞事疚脐,比如Linux內(nèi)核在功耗管理上做得還不夠好亿柑,于是就不會在Linux內(nèi)核環(huán)境里死磕,Android可以通過新的設(shè)計(jì)來進(jìn)行功耗控制上的提升棍弄。出于跟前面我們所說過的可減小對Linux內(nèi)核依賴性望薄、加強(qiáng)系統(tǒng)可移植性的設(shè)計(jì)需求,于是不可避免的呼畸,功耗控制將會盡可能多地被推到系統(tǒng)的上層痕支。在我們前面對于安全性的分層中可以看到,Android相當(dāng)于把整個操作系統(tǒng)都在用戶態(tài)重新設(shè)計(jì)了一次蛮原,SystemServer這個系統(tǒng)級進(jìn)程相當(dāng)于用戶態(tài)的一個Linux內(nèi)核卧须,于是將功耗控制更多地抽到用戶態(tài)來執(zhí)行,也沒有什么不合理的儒陨。

在Android的整體系統(tǒng)設(shè)計(jì)里花嘶,功耗控制會先從應(yīng)用程序著手,通過多任務(wù)并行時(shí)減小不必要的開銷開始蹦漠;在整個系統(tǒng)構(gòu)架里椭员,唯一知道當(dāng)前系統(tǒng)對功耗需求的是SystemServer,于是可以通過相應(yīng)的安全接口津辩,將功耗的控制提取出來拆撼,可由SystemServer來進(jìn)行后續(xù)的處理。Android系統(tǒng)所面臨的運(yùn)行環(huán)境需求里喘沿,電源是極度有限的資源闸度,于是功耗控制應(yīng)該是暴力型的,盡可能有能力關(guān)閉不需要使用的電源輸出蚜印。當(dāng)然暴力關(guān)電莺禁,則可能引起某些外設(shè)芯片不正常工作,于是在芯片驅(qū)動里需要做小范圍修改窄赋。與其他功能部分的設(shè)計(jì)不同哟冬,既然我們功耗控制是通過與驅(qū)動打交道來實(shí)現(xiàn)楼熄,可能無法避免地需要驅(qū)動,但要讓修改盡可能小浩峡,以提供可移植性可岂。

在這種修改方案里,最需要解決的當(dāng)然首先是多任務(wù)處理翰灾。我們可以得到的就是我們的生命周期缕粹。所謂的生命周期,是不是僅僅只是提供更多一些編程上的回調(diào)接口而已呢纸淮?不僅如此平斩,我們的所謂生命周期是一種休眠狀態(tài)點(diǎn),更多地起到休眠操作時(shí)我們有機(jī)會插入代碼的作用咽块。如果僅是提供編程功能绘面,我們可以參考JAVA ME里對于應(yīng)用程序?qū)崿F(xiàn):

JAVA ME框架里對待應(yīng)用程序只有三個狀態(tài)點(diǎn),運(yùn)行侈沪、暫停揭璃、關(guān)閉,對應(yīng)提供三種回調(diào)接口就可以驅(qū)動起這種編程模型亭罪。但我們的Android不是這樣處理的塘辅,Android在編程模型上,把帶顯示與不帶顯示的代碼邏輯分別抽象成Activity與Service皆撩,每種不同邏輯實(shí)現(xiàn)都有其獨(dú)特的生命周期,以更好地融入到系統(tǒng)的電源管理框架里哲银。

像我們的與顯示相關(guān)的處理扛吞,Activity,它擁有6種不同狀態(tài):

它的不同生命周期階段荆责,取決于這一Activity是否處于交互狀態(tài)滥比,是否處理可見狀態(tài)。如果加入這兩個限制條件做院,于是Activity的生命周期則是為這兩種狀態(tài)而設(shè)計(jì)的盲泛。onResume()與onResume()分別是進(jìn)入交互與退出交互時(shí)的狀態(tài)點(diǎn),在onResume()執(zhí)行完之后键耕,這時(shí)系統(tǒng)進(jìn)入了交互狀態(tài)寺滚,也就是Activity的Running狀態(tài),而此時(shí)如果由于Activity發(fā)生調(diào)用或是另一個Activity主動執(zhí)行屈雄,彈出一個小對話框村视,使原來處于Running狀態(tài)的Activity被擋住,這時(shí)Activity就被視為不需要交互了酒奶,這時(shí)Activity進(jìn)入不可見互狀態(tài)蚁孔,觸發(fā)onPause()回調(diào)奶赔。onStart()與onStop()則是對應(yīng)于是否可見,在onStart()回調(diào)之后杠氢,應(yīng)用程序這里就可以被顯示出來站刑,但不會真正進(jìn)入交互期,當(dāng)Activity變得完全不可見之后鼻百,則會觸發(fā)onStop()恩尾。而Android的多任務(wù)實(shí)現(xiàn),還會造成進(jìn)程會被殺死掉斧账,于是也提供兩個onCreate()與onDestroy()兩種回調(diào)方法來提供進(jìn)程被創(chuàng)建之后與進(jìn)程被殺死之前的兩種不同操作著蟹。

這種設(shè)計(jì)的技巧在于,當(dāng)Activity處于可交互狀況時(shí)中贝,這是系統(tǒng)里的全馬力執(zhí)行的周期囤捻。而再向外走一個狀態(tài)期,只是處于可見但不可交互狀態(tài)時(shí)邻寿,我們就可以開始通過技巧降功耗了蝎土,比如此時(shí)界面不再刷新、可以關(guān)閉一些所有與用戶交互相關(guān)的硬件绣否。當(dāng)Activity再進(jìn)一步退出可見狀態(tài)時(shí)誊涯,可以進(jìn)一步退出所有硬件設(shè)備的使用,這時(shí)就可以全關(guān)電了蒜撮。編寫應(yīng)用程序時(shí)暴构,當(dāng)我們希望它有不一樣的表現(xiàn)時(shí),我們可以去通過IoC去靈活地覆蓋并改進(jìn)這些回調(diào)接口段磨,而假如這種標(biāo)準(zhǔn)的模型滿足我們的需求取逾,我們就什么都不需要用,自動地被這種框架所管理起來苹支。

當(dāng)然砾隅,這種模型也不符合所有的需求,比如對于很多應(yīng)用程序來說债蜜,在后臺不可見狀態(tài)下晴埂,仍然需要做一些特定的操作。于是Android的應(yīng)用程序模型里寻定,又增加了一個Service儒洛。對于一些暴力派的開發(fā)者,比較喜歡使用后臺線程來實(shí)現(xiàn)這種需求特姐,但這種實(shí)現(xiàn)在Android并不科學(xué)晶丘,因?yàn)橹煌ㄟ^Activity承載的后臺線程,有可能會被殺死掉,在有狀態(tài)更新需求時(shí)浅浮,后臺線程需要通過Activity重繪界面沫浆,實(shí)際上這樣也會破壞Android在功耗控制上的這種合理性設(shè)計(jì)。比較合適的做法滚秩,所有不帶界面专执、需要在后臺持續(xù)進(jìn)行某些操作的實(shí)現(xiàn),都需要使用Service來實(shí)現(xiàn)郁油,而狀態(tài)顯示的改變應(yīng)該是在onStart()里完成的本股,狀態(tài)上的交互則需要放到onResume()方法里,這樣的實(shí)現(xiàn)可以有效繞開進(jìn)程被殺死的問題桐腌。并且在我們后面介紹AIDL的部分拄显,還可以看到,這樣實(shí)現(xiàn)還可以加強(qiáng)后臺任務(wù)的可交互性案站,當(dāng)我們進(jìn)一步將Service通過AIDL轉(zhuǎn)換成Remote Service之后躬审,則我們的實(shí)現(xiàn)會具備強(qiáng)大的可復(fù)用性,多個進(jìn)程都可以訪問到蟆盐。

Service也會有其生存周期承边,但Service的生存周期相對而言要簡單得多,因?yàn)樗纳嬷芷谥淮嬖凇笆欠裾诒皇褂谩钡膮^(qū)別石挂。當(dāng)然博助,同樣出于Android的多任務(wù)設(shè)計(jì),“使用中”這個狀態(tài)之外痹愚,也會有進(jìn)程是否存在的狀態(tài)富岳。

于是,我們的Service也可被納入到這種代碼活躍狀態(tài)的受控環(huán)境拯腮,當(dāng)是不需要與后臺的Service發(fā)生交互城瞎,這時(shí),我們可能只是通過一個startService()發(fā)出Intent疾瓮,這時(shí)Service在執(zhí)行完相應(yīng)的處理請求則直接退出。而如果是一個AIDL方式拋出的Remote Service飒箭,或是自己進(jìn)程范圍內(nèi)的Service狼电,但使用bindService()進(jìn)行了交互,這時(shí)弦蹂,Service的運(yùn)行狀態(tài)肩碟,只處于onBind()與OnUnbind()回調(diào)方法之間。

當(dāng)我們的應(yīng)用程序的各種不同執(zhí)行邏輯凸椿,都是處于一個可控狀態(tài)下時(shí)削祈,這時(shí),我們的功耗控制就可以被集中到一個系統(tǒng)進(jìn)程的SystemServer來完成。這時(shí)髓抑,我們面臨一種設(shè)計(jì)上的選擇咙崎,是默認(rèn)提供一種松散的電源控制,讓應(yīng)用程序盡可能多自由地控制電源使用吨拍,還是提供一種嚴(yán)格邏輯褪猛,默認(rèn)情況下實(shí)施嚴(yán)格的電源輸出管理,只允許應(yīng)用程序出于特殊的需求來調(diào)高它的需求羹饰?當(dāng)然伊滋,前一種方式靈活,但出于電源的有限性队秩,這時(shí)Android系統(tǒng)里使用了第二次邏輯笑旺,盡可能多地嚴(yán)格電源輸出控制。

在默認(rèn)情況下馍资,Android會嘗試讓系統(tǒng)盡可能多地進(jìn)入到休眠狀態(tài)之中筒主。在從用戶開始進(jìn)行了最后一次交互之后,系統(tǒng)則會觸發(fā)一個計(jì)時(shí)器迷帜,計(jì)時(shí)器會在一定的時(shí)間間隔后超時(shí)物舒,但每次用戶的交互操作都會重置這一計(jì)時(shí)器。如果用戶一直沒有進(jìn)行第二次交互戏锹,計(jì)時(shí)器超時(shí)則觸發(fā)一些功耗控制的操作冠胯。比如第一步,會先變暗直至關(guān)閉系統(tǒng)的屏幕锦针,如果在后續(xù)的一定時(shí)間內(nèi)用戶繼續(xù)沒有任何操作荠察,這時(shí)系統(tǒng)則會進(jìn)一步嘗試將整個系統(tǒng)變成休眠狀態(tài)。

休眠部分的操作奈搜,基本上是Linux內(nèi)核的功耗控制邏輯了悉盆。休眠操作的最后,會將內(nèi)存控制器設(shè)成自刷新模式馋吗,關(guān)掉CPU焕盟。到這種低功耗運(yùn)行模式之下,這時(shí)系統(tǒng)功耗會降到最低宏粤,如果是不帶3G模組的芯片脚翘,待機(jī)電流應(yīng)該處于1mA以下。但我們的系統(tǒng)是手機(jī)绍哎,一般2G来农、3G、或是4G是必須存在的崇堰,而且待機(jī)狀態(tài)時(shí)關(guān)掉這種不同網(wǎng)絡(luò)制式下的Modem沃于,也失去了手機(jī)存在的意義涩咖,于是,一般功耗上會加上一個移動Modem繁莹,專業(yè)術(shù)語是基帶(Baseband)的功耗檩互,這時(shí)一般要控制在10 – 30mA的待機(jī)電流,100mW左右的待機(jī)功耗蒋困。如果這時(shí)盾似,用戶按些某些用于喚醒的按鍵、或是基帶芯片上過來了一些短信或是電話之類的信息雪标,則系統(tǒng)會通過喚醒操作零院,回到休眠之前的狀態(tài)。

但是Linux內(nèi)核的Suspend與Resume方案村刨,是針對ACPI里通用計(jì)算環(huán)境(我們的PC告抄、筆記本、服務(wù)器)的功耗控制方案嵌牺,并不完全與手機(jī)的使用需求相符合打洼。而Linux內(nèi)核所缺失的,主要是UI控制上功耗管理逆粹,手機(jī)平臺上耗電最大的元器件募疮,是屏幕與背光,是無法通過Linux內(nèi)核的suspend/resume兩級模型來實(shí)現(xiàn)高效的電源管理僻弹。于是阿浓,Android系統(tǒng),在原始的suspend與resume接口之外蹋绽,再增加了兩級early_suspend與late_resume芭毙,用于UI交互時(shí)的提前休眠。

我們的Android系統(tǒng)卸耘,在出現(xiàn)用戶操作超時(shí)的情況下退敦,會先進(jìn)入early_suspend狀態(tài)點(diǎn),關(guān)閉一些UI交互相關(guān)的硬件設(shè)備蚣抗,比如屏幕侈百、背光、觸摸屏翰铡、Sensor设哗、攝像頭等。然后两蟀,在進(jìn)一步?jīng)]有相應(yīng)喚醒操作時(shí),會進(jìn)入suspend關(guān)閉系統(tǒng)里的其他類的硬件震缭。最后系統(tǒng)進(jìn)入到內(nèi)存自刷新赂毯、CPU關(guān)電的狀態(tài)。如果在系統(tǒng)完全休眠的情況下,發(fā)生了某種喚醒事件党涕,比如電話打進(jìn)來烦感、短信、或是用戶按了電源鍵膛堤,這時(shí)就會先進(jìn)resume手趣,將與UI交互不相關(guān)的硬件喚醒,再進(jìn)入late_resume喚醒與UI交互相關(guān)的硬件肥荔。但如果設(shè)備在進(jìn)入early_suspend狀態(tài)但還沒有開始suspend操作之前發(fā)生了喚醒事件绿渣,這時(shí)就直接會走到late_resume,喚醒UI交互的硬件驅(qū)動燕耿,從而用戶又可以看到屏幕上的顯示中符,并且可以進(jìn)行交互操作。

經(jīng)過了這樣的修改誉帅,在沒有用戶操作的情況下淀散,系統(tǒng)會不斷進(jìn)入休眠模式省電,而用戶并不會感受到這種變化蚜锨,在頻繁操作時(shí)档插,實(shí)際上休眠與喚醒只是快進(jìn)快出的UI相關(guān)硬件的休眠與喚醒。但完全暴力型的休眠也會存在問題亚再,比如我們有些應(yīng)用程序郭膛,QQ需要保持登錄,下載需要一直在后臺下載针余,這些都不符合Android的需求的饲鄙,于是,我們還需要一種機(jī)制圆雁,讓某些特殊的應(yīng)用程序忍级,在萬不得已的情況下,我們還是可以得這些應(yīng)用程序足夠的供電運(yùn)行得下去伪朽。

于是Android在設(shè)計(jì)上轴咱,又提出了一套創(chuàng)新框架,wake_lock烈涮,在多加了early_suspend與late_resume之外朴肺,再加上可以提供功耗上的特殊控制。Wake_lock這套機(jī)制坚洽,跟我們C++里使用的智能指針(Smart pointer)戈稿,借用智能指針的思想來設(shè)計(jì)電源的使用和分配。我們也知道Smart Pointer都是引用讶舰,則它的引用計(jì)數(shù)會自動加1鞍盗,取消引用則引用計(jì)數(shù)減1需了,使用了智能指針的對象,當(dāng)它的引用計(jì)數(shù)為0時(shí)般甲,則該對象會被回收掉肋乍。同樣,我們的wake_lock也保持使用計(jì)數(shù)敷存,只不過這種“智能指針”的所使用的資源不再是內(nèi)存墓造,而是電量。應(yīng)用程序會通過特定的WakeLock去訪問硬件锚烦,然后硬件會根據(jù)引用計(jì)數(shù)是否為0來決定是不是需要關(guān)閉這一硬件的供電觅闽。

Suspend與wake_lock這兩種新加入的機(jī)制,最后也是需要加放SystemServer這個進(jìn)程里挽牢,因?yàn)檫@是屬于系統(tǒng)級的服務(wù)谱煤,需要特權(quán)才能保證“沙盒”機(jī)制。于是禽拔,我們得到了Android里的電源管理框架:

當(dāng)然刘离,這里唯一不太好的地方,就是Android系統(tǒng)設(shè)計(jì)必須對Linux內(nèi)核原有的電源管理機(jī)制進(jìn)行改動睹栖,需要加入wake_lock機(jī)制的處理硫惕,也需要在原始的內(nèi)核驅(qū)動之上加入新的early_suspend與late_resume兩個新的電源管理級別與wake_lock相配套。這部分的代碼野来,則會造成Android系統(tǒng)所需要的驅(qū)動恼除,與標(biāo)準(zhǔn)Linux內(nèi)核的驅(qū)動并不完全匹配,同時(shí)這種簡單粗暴的方式曼氛,也會破壞掉內(nèi)核原有的清晰簡要的風(fēng)格豁辉。這方面也造成了Linux社區(qū)與Android社區(qū)之間曾一度吵得很兇,Linux內(nèi)核拒絕Android提交的修改舀患,而Android源代碼則不再使用標(biāo)準(zhǔn)的Linux內(nèi)核源代碼徽级,使用自己特殊的分支進(jìn)行開發(fā)。

我們再來看Android系統(tǒng)對于功能接口的設(shè)計(jì)聊浅。

1.6 功能接口設(shè)計(jì)

我們實(shí)現(xiàn)一個系統(tǒng)餐抢,必須盡可能多地提供給應(yīng)用程序盡可能多的開發(fā)接口,作為一個開源系統(tǒng)更應(yīng)該如此低匙。雖然我們前面提到了旷痕,我們需要有權(quán)限控制機(jī)制來限制應(yīng)用程序可訪問系統(tǒng)功能與硬件功能,但是這是權(quán)限控制的角度顽冶,如果應(yīng)用程序得到了授權(quán)欺抗,應(yīng)該有理由來使用這一功能,一個能夠獲得所有權(quán)限的應(yīng)用程序强重,則理所當(dāng)然應(yīng)該享受系統(tǒng)里所提供的一切功能绞呈。

對于一個標(biāo)準(zhǔn)的Java系統(tǒng)团滥,無論是桌面環(huán)境里使用的Java SE還是嵌入式環(huán)境里使用的Java ME,都不存在任何問題报强,因?yàn)檫@時(shí)Java本就只是系統(tǒng)的一層“皮”,每個Java寫成的應(yīng)用程序拱燃,只是一層底層系統(tǒng)上的二次封裝秉溉,實(shí)際上都是借用底層操作系統(tǒng)來完成訪問請求的。對于傳統(tǒng)的應(yīng)用程序碗誉,一個main()進(jìn)入死循環(huán)處理UI召嘶,也不存在這個問題,通過鏈接到系統(tǒng)里的動態(tài)鏈接庫或是直接訪問設(shè)備文件哮缺,也可以實(shí)現(xiàn)弄跌。但這樣的方式,到了Android系統(tǒng)里尝苇,就會面臨一個功能接口的插分問題铛只。因?yàn)槲覀兊腁ndroid,不再是一層操作系統(tǒng)之上的Java虛擬機(jī)封裝糠溜,而是抽象出來的在用戶態(tài)運(yùn)轉(zhuǎn)的操作系統(tǒng)淳玩,同時(shí)還會有“沙盒”模式,應(yīng)用程序并不見得擁有所有權(quán)限來訪問系統(tǒng)資源非竿,則又不能影響它的正常運(yùn)行蜕着。

于是,對于Android在功能接口設(shè)計(jì)上红柱,會被劃分成兩個層次的承匣,一種是以“受托管”環(huán)境下通過一個系統(tǒng)進(jìn)程SystemServer來執(zhí)行,另一種是被映射到應(yīng)用程序的進(jìn)程空間內(nèi)來完成锤悄。而我們前面分析的使用Java編程語言韧骗,而Framework層功能只以API方式向上提供訪問接口,就變得非常有遠(yuǎn)見铁蹈。使用了Java語言宽闲,則我們更容易實(shí)現(xiàn)代碼結(jié)構(gòu)上的重構(gòu),如果我們的功耗接口有變動握牧,則可以通過訪問接口的重構(gòu)來隱藏掉這樣的差異性容诬;只以Framework的API版本為標(biāo)準(zhǔn)來支持應(yīng)用程序,則進(jìn)一步提供封裝沿腰,在絕大部分情況下览徒,雖然我們底層結(jié)構(gòu)已經(jīng)發(fā)生了巨大變動,應(yīng)用程序卻完全不受影響颂龙,也不會知道有這樣的變化习蓬。

從這種設(shè)計(jì)思路纽什,我們再去看Android的進(jìn)程模型,我們就可以看到躲叼,通常意義上的Framework芦缰,實(shí)際上被拆分成兩部分:一部分被應(yīng)用程序用Java實(shí)現(xiàn)的classes.dex所引用,這部分用來提供應(yīng)用程序運(yùn)行所必須的功能枫慷;另一部分让蕾,則是由我們的SystemServer進(jìn)程來提供。

在應(yīng)用程序只需要完成基本的功能或听,比如只是使用Activity來處理圖形交互時(shí)探孝,通過Activity來構(gòu)建方便用戶使用的一些功能時(shí),這時(shí)會通過自己進(jìn)程空間內(nèi)映射的功能來完成誉裆。而如果要使用一些特殊功能顿颅,像打電話、發(fā)短信足丢,則需要通過一種跨進(jìn)程通訊粱腻,將請求提交到SystemServer來完成。

這種由于特殊設(shè)計(jì)而得到的運(yùn)行模型很重要霎桅,也是Android系統(tǒng)有別于其他系統(tǒng)很重要的一個區(qū)別栖疑。這樣的框架設(shè)計(jì),使Android與傳統(tǒng)Linux上所面臨的易用性問題在設(shè)計(jì)角度就更容易解決滔驶。

比如顯示處理遇革。我們傳統(tǒng)的嵌入式環(huán)境里,要不就是簡單的Framebuffer直接運(yùn)行揭糕,要么會針對通用性使用一個DirectFB的顯示處理方案萝快,但這種方案通用性很低,安全性極差著角。為了達(dá)到安全性揪漩,同時(shí)又能盡可能兼容傳統(tǒng)桌面環(huán)境下的應(yīng)用程序,大都會傳承桌面環(huán)境里的一個Xorg的顯示系統(tǒng)吏口,比如Meego奄容,以及Meego的前身Maemo,都是使用了Xorg用來處理圖形产徊。但Xorg有個很嚴(yán)重的性能問題:

使用Xorg處理顯示的昂勒,所有的應(yīng)用程序?qū)嶋H上只是一個客戶端,通過Unix Socket舟铜,使用一種與傳統(tǒng)兼容的X11的網(wǎng)絡(luò)協(xié)議戈盈。用戶交互,應(yīng)用程序會在自己的交互循環(huán)里,通過X11發(fā)起創(chuàng)建窗口的請求塘娶,之后的交互归斤,則會通過輸入設(shè)備讀取輸入事件,再通過Xorg服務(wù)器刁岸,轉(zhuǎn)回客戶端脏里,而應(yīng)用程序界面上的重繪操作,則還是會通過X11協(xié)議虹曙,走回到Xorg Server之后膝宁,再進(jìn)行最后的繪制與輸出。雖然現(xiàn)在我們使用的經(jīng)過模塊化重新設(shè)計(jì)的XorgR7.7根吁,已經(jīng)盡可能通過硬件加速來完成這種操作,Xorg服務(wù)器還是有可能會成為整個圖形交互的瓶頸合蔽,更重要的是復(fù)雜度太高,在這種構(gòu)架里修改一個bug都有點(diǎn)困難,更不要說改進(jìn)盯腌。在嵌入式平臺上更是如此弓摘,性能本就不夠的系統(tǒng)環(huán)境,Xorg的缺陷暴露無移刃宵,比如使用Xorg的Meego更新過程遠(yuǎn)比Android要困難衡瓶,用戶交互體驗(yàn)也比較差。

在Android里牲证,處理模型則跟傳統(tǒng)的Xorg構(gòu)架很不一樣哮针。從設(shè)計(jì)角度來講,繪制圖形界面與獲取輸入設(shè)備過來的輸入事件坦袍,本來不需要像Xorg那樣的中控服務(wù)器十厢,尤其像Android運(yùn)行環(huán)境這樣,并不存在多窗口問題(多窗口的系統(tǒng)需要有個服務(wù)器決定哪個窗口處于前臺捂齐,哪個窗口處于交互狀態(tài)中)蛮放。而從實(shí)現(xiàn)的角度,如果能夠提供一種設(shè)計(jì)奠宜,將圖形處理與最終輸出分開包颁,則更容易實(shí)現(xiàn)優(yōu)化處理⊙拐妫基于圖形界面的交互娩嚼,實(shí)際上將由三個不同的功能實(shí)體來完成:應(yīng)用程序、負(fù)責(zé)將圖層進(jìn)行疊加渲染的SurfaceFlinger榴都、以及負(fù)責(zé)輸入事件管理和選擇合適的地址進(jìn)行發(fā)送的SystemServer待锈。當(dāng)然,我們的上層的應(yīng)用程序不會看到內(nèi)部的復(fù)雜邏輯嘴高,它只知道通過android.view這個包來訪問所有的圖形交互功能竿音。

于是得到Android系統(tǒng)的圖形處理框架:

我們的SurfaceFlinger和屎,是Android里的一種Native Service的實(shí)現(xiàn),所以有原理上來說春瞬,只要有一個承載它的執(zhí)行體(進(jìn)程柴信、線程皆可),就可以在系統(tǒng)里執(zhí)行宽气。在實(shí)現(xiàn)過程里随常,SurfaceFlinger作為一個線程在SystemServer這個進(jìn)程空間里完成也是可以的,只是出于穩(wěn)定性的考慮萄涯,一般將它獨(dú)立成一個單獨(dú)的SurfaceFlinger的獨(dú)立進(jìn)程绪氛。

這種設(shè)計(jì),可以達(dá)到一個低耦合設(shè)計(jì)的優(yōu)勢涝影,這套圖形處理框架將變得更簡單枣察,同時(shí)也不會將Xorg那樣需要大量的特殊內(nèi)核接口與其適配,如果在別的操作系統(tǒng)內(nèi)核之上進(jìn)行移植燃逻,也不會有太大的依賴性序目。但這時(shí)會帶來嚴(yán)重的性能問題,因?yàn)閳D層的處理和輸出是需要大量內(nèi)存的(如果是24位真彩色輸出伯襟,即使是800x480的分辯率猿涨,每秒60楨的輸出頻率,也需要3*800*480*60 = 69120000姆怪,69M Byte/s)叛赚,這種開銷對于嵌入式方案而言,是難以承受的稽揭。在進(jìn)程間傳遞數(shù)據(jù)時(shí)红伦,會先需要在一個進(jìn)程執(zhí)行上下文環(huán)境里通過copy_from_user()把數(shù)據(jù)從用戶態(tài)拷貝到內(nèi)核態(tài),然后在另一個進(jìn)程執(zhí)行的上下文環(huán)境里通過copy_to_user()把數(shù)據(jù)拷貝從內(nèi)核態(tài)拷貝到另一個用戶態(tài)環(huán)境淀衣,這樣才能保證互不干擾昙读。

而回過頭來看Linux內(nèi)核,搞過Linux內(nèi)核態(tài)開發(fā)的都知道膨桥,在Linux系統(tǒng)的進(jìn)程之間減小內(nèi)存拷貝的開銷蛮浑,最直接的手段就是通過mmap()來完成內(nèi)存映射,讓保存數(shù)據(jù)的內(nèi)存頁只會在內(nèi)核態(tài)里循環(huán)只嚣,這時(shí)就沒有內(nèi)存拷拷貝的開銷了沮稚。使用了mmap()之后,內(nèi)存頁是直接在內(nèi)核態(tài)分配的內(nèi)存册舞,兩個進(jìn)程都通過mmap()把這段區(qū)域映射到自己的用戶空間蕴掏,然后可以一個進(jìn)程直接操作內(nèi)存,另一個進(jìn)程就可以直接訪問到。在圖層處理上盛杰,最好這些在內(nèi)核態(tài)申請的內(nèi)存是連續(xù)內(nèi)存挽荡,這時(shí)就可以直接通過LCD控制器的DMA直接輸出,Android于是提供了一種新的特殊驅(qū)動pmem即供,用來處理連續(xù)物理內(nèi)存的分配與管理定拟。同時(shí),這種方式很裸逗嫡,最好還在上層提供一次抽象青自,編程時(shí)則靈活度會更高,針對這種需求驱证,就有了我們的Gralloc的HAL接口延窜。加入了這兩種接口之后,Android在圖像處理上便自成體系抹锄,不再受限于傳統(tǒng)實(shí)現(xiàn)了需曾。

我們的圖層,是由應(yīng)用程序在創(chuàng)建是通過Gralloc來申請圖層存儲空間祈远,然后被包裝成上層的Surface類,在Activity實(shí)現(xiàn)里Surface則是按需要進(jìn)行重繪(調(diào)用view的draw()方法)商源,并在繪制完成后通過post()將繪制完成的消息發(fā)送給SurfaceComposer遠(yuǎn)程對象车份。而在SurfaceFlinger這段,則是將已經(jīng)繪制完成的Surface通過其對應(yīng)的模式牡彻,進(jìn)行圖層的合成并輸出到屏幕扫沼。對于上層實(shí)現(xiàn),貌似是一種很松散的交互庄吼,而對于底層實(shí)現(xiàn)缎除,實(shí)際則是一種很高效的流水線操作。

這里总寻,值得一提的是Surface本身也包含了圖層處理加速的另一種技巧器罐,就是double buffer技術(shù)。一個Surface會有兩個圖層buffer渐行,一楨在后臺被繪制轰坊,另一楨在前臺進(jìn)行輸出。當(dāng)后臺繪制完成后祟印,會通過一次Page Flipping操作肴沫,原來的后臺楨被換到前臺進(jìn)行輸出,而繪制操作則繼續(xù)在后臺完成蕴忆。這樣用戶總會看到繪制完整的圖像颤芬,因?yàn)閳D層總是繪制完成后才能輸出。而有了double buffer,使我們圖形輸出的性能也得到提升站蝠,我們輸出繪制與輸出使用獨(dú)立的循環(huán)汰具,通過流水線加快了圖層處理,尤其在Android里沉衣,可能有多個繪制的邏輯部分郁副,性能得以進(jìn)一步加速。在Android 4.1里面豌习,這種圖形處理得以進(jìn)一步優(yōu)化存谎,使用了triple buffer(三重緩沖),加深了圖層處理的流水線操作能力肥隆。

這種顯示處理上的靈活性既荚,在Android系統(tǒng)里也具備非常重要的意義,可以讓整個系統(tǒng)在功能設(shè)計(jì)上可以變得更加靈活栋艳。我們提供了一種“零拷貝”圖層處理技術(shù)之后恰聘,最終上層都可以通過一個特殊的可以跨進(jìn)程的Surface對象來進(jìn)行異步的繪制處理(如果我們不是直接操作控件,而是通過“打洞”方式來操作圖形界面上的某個區(qū)域吸占,則屬于SurfaceView提供的晴叨,當(dāng)然,這時(shí)也只是操作Surface的某一部分)矾屯。我們的Surface的繪制與post()異步進(jìn)行的兼蕊,于是多個執(zhí)行體可以并行處理圖層,而用戶只會看到通過post()發(fā)送的圖層繪制完成的同步事件之后的完整圖層件蚕,圖層質(zhì)量與流暢性反而可以更佳孙技。比如,我們的VOIP應(yīng)用程序排作,可以會涉及多個功能實(shí)體的交互牵啦,Camera、多媒體編解碼妄痪、應(yīng)用程序哈雏、SurfaceFlinger。

應(yīng)用程序衫生、多媒體編解碼與Camera都只會通過一個Surface對象來在后臺楨上進(jìn)行交互界面的繪制僧著,像前攝像頭出來的回顯,從網(wǎng)絡(luò)解碼出來的遠(yuǎn)端的視頻障簿,然后應(yīng)用程序的操作控件盹愚,都將重繪后臺圖層。而如果這一應(yīng)用程序處于Activity的可交互狀態(tài)(見前面的生命周期的部分)站故,就會通過找到同一Surface對象皆怕,將這一Surface對象的前臺楨(也就是繪制完成但還沒有輸出的圖層)輸出毅舆。輸出完則對這一Surface對象的前后兩楨圖層進(jìn)行對調(diào),于是這樣的流水線則可以很完美的運(yùn)行下去愈腾。

Android并非是最高效的方案憋活,而只是一種通過面向?qū)ο蠓绞酵耆匦略O(shè)計(jì)的嵌入式解決方案,高效是其設(shè)計(jì)的一部分要素虱黄。如果單從效率角度出發(fā)悦即,無進(jìn)程概念的實(shí)時(shí)操作系統(tǒng)最高效,調(diào)度開銷也小橱乱,沒有虛址切換時(shí)的開銷辜梳。作為Android系統(tǒng),通過目前我們看到的功能性接口的設(shè)計(jì)泳叠,至少得到了以良好的構(gòu)架為基礎(chǔ)同時(shí)又兼顧性能的一種設(shè)計(jì)作瞄。

當(dāng)然,我們前面所總結(jié)的危纫,對于Android系統(tǒng)的種種特性宗挥,最終得到的一種印象是每種設(shè)計(jì)都是萬能膠,同一種設(shè)計(jì)收獲了多種的好處种蝶。那是不是這種方式最好契耿,大家都應(yīng)該遵循這種設(shè)計(jì)上的思路與技巧?That depends螃征,要看情況搪桂。像Android這樣要完整地實(shí)現(xiàn)一整套這種在嵌入式環(huán)境里運(yùn)行的,面向?qū)ο笫降幕岚粒沂腔谏澈心J降南到y(tǒng),要么會得到效率不高的解決方案拙泽,要么會兼顧性能而得到大量黑客式的接口淌山。Android最終也就是這么一套黑客式的系統(tǒng),這個系統(tǒng)一環(huán)套一環(huán)顾瞻,作為系統(tǒng)核心部分的設(shè)計(jì)泼疑,都彼此過分依賴,拆都拆不開荷荤,對它進(jìn)行拆分退渗、精減或是定制,其實(shí)都很困難蕴纳。但Android会油,其系統(tǒng)的核心就是Framework,而所謂的Framework古毛,從軟件工程學(xué)意義上來說翻翩,這樣的構(gòu)架卻是可以接受的都许。所謂的Framework,對上提供統(tǒng)一接口嫂冻,保持系統(tǒng)演進(jìn)時(shí)的靈活性胶征;對下則提供抽象,封裝掉底層實(shí)現(xiàn)的細(xì)節(jié)桨仿。Android的整個系統(tǒng)層構(gòu)架睛低,則很好的完成了這樣的抽象,出于這樣的角度服傍,我們來看看Android的可移植性設(shè)計(jì)钱雷。

1.7 可移植性

單純從可移植性角度來說,Linux內(nèi)核是目前世界上可移植性最強(qiáng)的操作系統(tǒng)內(nèi)核伴嗡,沒有之一急波。目前,只要處理器芯片能夠提供基本的運(yùn)算能力(可以支撐多進(jìn)程在調(diào)度上的開銷)瘪校,只要能夠提供C語言的編譯器(準(zhǔn)確地說是Gnu C編譯工具鏈)澄暮,就可以運(yùn)行Linux內(nèi)核。Linux內(nèi)核在設(shè)計(jì)上保持了傳統(tǒng)Unix的特點(diǎn)阱扬,大部分使用了C語言開發(fā)泣懊,極少部分機(jī)器相關(guān)的代碼使用匯編,這種結(jié)構(gòu)使其可移植性很強(qiáng)麻惶。在Linux內(nèi)核發(fā)展到2.6版本之后馍刮,這種強(qiáng)大的可移植性得到進(jìn)一步提升,通過驅(qū)動模型與驅(qū)動框架的引入和不斷加強(qiáng)窃蹋,使Linux內(nèi)核里絕大部分源代碼幾乎都沒有硬件平臺上的依賴性卡啰。于是,Linux內(nèi)核幾乎能夠運(yùn)行在所有的硬件平臺之上警没,常見有的X86匈辱、ARM,不那么常見但可能也會在不知道不覺地使用到的有MIPS杀迹、PowerPC亡脸、Alpha,另外還有一些我們聽都沒有聽過的树酪,像Blackfin浅碾,Cris、SuperH续语、Xtensa垂谢,Linux內(nèi)核都支持,平臺支持可參考linux內(nèi)核源代碼的arch目錄疮茄。甚至埂陆,出于Linux內(nèi)核的可移植性苛白,Linux一般也被作為芯片驗(yàn)證的工具,芯片從FPGA設(shè)計(jì)到最終出廠前焚虱,都會通過Linux內(nèi)核來檢測這一芯片是否可以運(yùn)行购裙,是否存在芯片設(shè)計(jì)上的錯誤。

得益于Linux內(nèi)核鹃栽,構(gòu)建于其上的操作系統(tǒng)躏率,多多少少可繼承這樣的可移植性。但Android又完成應(yīng)用程序運(yùn)行環(huán)境的二次抽象民鼓,在用戶態(tài)幾乎又構(gòu)造出一層新的操作系統(tǒng)薇芝,于是它的可移植性多多少少會受此影響,而且丰嘉,像我們前面所分析出來的夯到,Android的核心層構(gòu)建本身,也因?yàn)樾阅苌系目紤]饮亏,耦合性也有點(diǎn)強(qiáng)耍贾,于是在可移植性也會面臨挑戰(zhàn)÷沸遥“窮山惡水出刁民”荐开,正因?yàn)樘魬?zhàn)大,于是Android反倒通過各種技巧來加強(qiáng)系統(tǒng)本身的可移植性简肴,反而做得遠(yuǎn)比其他系統(tǒng)要好得多晃听。Android在可移植性上的特點(diǎn)有:

按需要定制可移植性。與傳統(tǒng)嵌入式Linux操作系統(tǒng)不同砰识,Android在設(shè)計(jì)上有明確的設(shè)計(jì)思想與目標(biāo)能扒,不會為了使用更多開源軟件而提供更高兼容性的編譯環(huán)境,而是列出功能需求辫狼,按功能需求來定制所需要的開源軟件初斑。有些開源軟件能夠提供更復(fù)雜的功能,但在Android環(huán)境里予借,只會選擇其驗(yàn)證過的必需功能越平,像藍(lán)牙频蛔,BlueZ本身可以提供更復(fù)雜的藍(lán)牙控制灵迫,但Android只選擇了BlueZ的基本功能,更多功能是由Android自己來實(shí)現(xiàn)晦溪,于是減小了依賴性瀑粥,也降低了移植時(shí)的風(fēng)險(xiǎn)性。

盡可能跨平臺三圆。與以前的系統(tǒng)相比狞换,Android在跨平臺上得益于Java語言的使用避咆,使其跨平臺能力更強(qiáng),在開發(fā)上幾乎可以使用任何Java環(huán)境可以運(yùn)行的操作系統(tǒng)里修噪。而在源代碼級別查库,它也能夠在MacOSX與Linux環(huán)境里進(jìn)行編譯,這也是一個大的突破黄琼。

硬件抽象層樊销。Android在系統(tǒng)設(shè)計(jì)的最初,便規(guī)劃了硬件抽象層脏款,通過對硬件訪問接口的抽象围苫,使硬件的訪問接口相對穩(wěn)定,而具體的實(shí)現(xiàn)則可在底層換用不同硬件訪問接口時(shí)靈活地加以實(shí)現(xiàn)撤师,不要說應(yīng)用程序剂府,就是Framework都不會意識到這種變動。這是以前的嵌入式Linux操作系統(tǒng)所沒有的一種優(yōu)點(diǎn)剃盾。硬件抽象層的使用腺占,使Android并不一定需要局限于Linux內(nèi)核之上,如果將底層偷換成別的接口万俗,也不會有太大的工作量湾笛。

實(shí)現(xiàn)接口統(tǒng)一的規(guī)范化。Android在構(gòu)架上闰歪,都是奉行一種統(tǒng)一化的思路嚎研,先定義好API,然后會有Framework層的實(shí)現(xiàn)库倘,然后再到硬件抽象層上的變動临扮。API可在同一版本上拓展,F(xiàn)ramework也在逐步加強(qiáng)教翩,而硬件抽象層本身可提供的能力也越來越強(qiáng)杆勇,但這一切都在有組織有紀(jì)律的環(huán)境下進(jìn)行,變動在任何一次版本更新上來看饱亿,都是增量的小范圍變動蚜退,而不會像普通的Linux環(huán)境那樣時(shí)刻都在變,時(shí)刻都有不兼容的風(fēng)險(xiǎn)彪笼。從可移植性角度來說钻注,這種規(guī)范化提供的好處,便是大幅降低了移植時(shí)的工作量配猫。

盡可能簡單幅恋。簡單明了是Android系統(tǒng)構(gòu)成上的一大特色,這種特色在可移植性上也是如此泵肄。像編譯環(huán)境捆交,Android在交叉編譯環(huán)境上淑翼,是通過固化編譯選項(xiàng)來達(dá)到簡編譯過程的上的,最終品追,Android源代碼的編譯工程玄括,會是一個個由Android.mk來構(gòu)造的可編譯環(huán)境,這當(dāng)然會降低靈活性肉瓦,但直接導(dǎo)致了這套框架在跨平臺上表現(xiàn)非常出色惠豺。再比如硬件抽象層,同樣的抽象在現(xiàn)代嵌入式操作系統(tǒng)上可能都有风宁,但是大都會遠(yuǎn)比Android的HAL層要復(fù)雜洁墙,簡單于是容易理解和開發(fā),在跨平臺性方面也會表現(xiàn)更好戒财。

我們傳統(tǒng)的嵌入式Linux環(huán)境热监,幾乎都會遵從一種約定俗成的傳統(tǒng),就是專注于如何將開源軟件精減饮寞,然后盡可能將PC上的運(yùn)行環(huán)境照搬到嵌入式孝扛。在這種思路引導(dǎo)下開發(fā)出來的系統(tǒng),可移植性本身是沒什么問題的幽崩,只是不是跟X86綁定的源代碼苦始,鐵定是可以移植。但是慌申,這樣構(gòu)建出來的系統(tǒng)陌选,一般都在結(jié)構(gòu)上過于復(fù)雜,會有過多的依賴性蹄溉,應(yīng)用程序接口并不統(tǒng)一咨油,升級也困難。所有這樣的系統(tǒng)柒爵,最后反倒是影響到了系統(tǒng)的可移植性役电。比如開源嵌入式Linux解決方案,maemo棉胀,就是一個很好的例子:

對于Maemo的整體框架而言法瑟,我們似乎也可以看到類似于Android的層次化結(jié)構(gòu),但注意看這種系統(tǒng)組成時(shí)唁奢,我們就不難發(fā)現(xiàn)霎挟,這樣的層次化結(jié)構(gòu)是假的,在Maemo環(huán)境里驮瞧,實(shí)際上就是一個小型化的Linux桌面環(huán)境氓扛,有Xorg枯芬,有g(shù)tk论笔,有一大堆的依賴庫采郎,編程環(huán)境幾乎與傳統(tǒng)Linux沒任何區(qū)別。這種所謂的軟件上的構(gòu)架狂魔,到了Maemo的后繼者M(jìn)eego蒜埋,也是如此,只不過把gtk的圖形界面換成了Qt的最楷,然后再在Qt庫環(huán)境里包裝出所謂的UX整份,換湯不換藥,這時(shí)籽孙,Meego還是擁有一顆PC的心烈评。

一般這種系統(tǒng)的交叉編譯環(huán)境,還必須構(gòu)建在一套比較復(fù)雜的編譯環(huán)境之上犯建,通過在編譯環(huán)境里模擬出一個Linux運(yùn)行環(huán)境讲冠,然后才能編譯盡可能多的源代碼。這樣的交叉編譯環(huán)境有Open Embedded,ScratchBox等。雖然有不同的交叉編譯實(shí)現(xiàn)上的思路减江,但并沒有解決可移植性問題裹唆,它們必須在Linux操作系統(tǒng)里運(yùn)行,而且使用上的復(fù)雜程度讼昆,不是經(jīng)驗(yàn)豐富的Linux工作者還沒辦法靈活使用。即便是比較易用的ScratchBox,也會有如下令人眼花繚亂的結(jié)構(gòu)列荔。

針對這樣的現(xiàn)狀,Android在解決可移植性問題時(shí)的思路就要簡單得多枚尼。既然原來的嘗試不成功肌毅,PC被精減到嵌入式環(huán)境里效果并不好,這時(shí)就可以換一種思路姑原,一種“返璞歸真”的思路悬而,直接從最底層來簡化設(shè)計(jì),簡化交叉編譯锭汛。這樣做法的一個最重要前提條件笨奠,就是Android本身是完整重新設(shè)計(jì)與實(shí)現(xiàn)的,是一個自包含的系統(tǒng)唤殴,所有的編譯環(huán)境般婆,都可以從源代碼里把系統(tǒng)編譯出來。

在系統(tǒng)結(jié)構(gòu)上朵逝,Android在設(shè)計(jì)上便拋棄了傳統(tǒng)的大肆搜刮開源代碼的做法蔚袍,由自己的設(shè)計(jì)來定位需要使用的開源代碼,如果沒有合適的開源代碼,則會提供一個簡單實(shí)現(xiàn)來實(shí)現(xiàn)這一部分的功能啤咽。于是晋辆,得到我們經(jīng)常見到的Android的四層結(jié)構(gòu):

從這樣簡化過的四層結(jié)構(gòu)里,最底層的Linux內(nèi)核層宇整,這些都與其他嵌入式Linux解決方案是共通的特性瓶佳,都是一樣的。其他三層就與其他操作系統(tǒng)大相徑庭了:應(yīng)用程序?qū)邮且环N基于“沙盒”模式的鳞青,以功能共享為最終目的的統(tǒng)一開發(fā)層霸饲,并非只是用于開發(fā),同時(shí)還會通過API來規(guī)范這些應(yīng)用程序的行為臂拓;Framework層厚脉,這是Android真正的核心層,而從編程環(huán)境上來說胶惰,這一層算是Java層器仗,任何底層功能或硬件接口的訪問,都會通過JNI訪問到更低層次來實(shí)現(xiàn)童番;給Framework提供支撐的就是Library層精钮,也就是使用的一個自己實(shí)現(xiàn)的,或是第三方的庫環(huán)境剃斧,這一層以C/C++編寫的可以直接在機(jī)器上執(zhí)行的ELF文件為主轨香。

有了這種更簡化的層次關(guān)系,使Android最后得到的源代碼相對來說更加固定幼东,應(yīng)用程序這層我們只會編譯Java臂容,F(xiàn)ramework層的編譯要么是Java要么是JNI,而Library層則會是C/C++的編譯根蟹。在比較固定的編譯目標(biāo)基礎(chǔ)上脓杉,編譯環(huán)境所需要解決的問題則會比較少,于是更容易通過一些簡化過的編譯環(huán)境來實(shí)現(xiàn)简逮。Android使用了最基本的編譯環(huán)境GnuMake球散,然后再在其上使用最基本的Gnu工具鏈(不帶library與動態(tài)鏈接支持)來編譯源代碼,最后再通過簡化過的鏈接器來完成動態(tài)鏈接散庶。得到的結(jié)果是幾乎不需要編譯主機(jī)上的環(huán)境支持蕉堰,從而可以在多種操作系統(tǒng)環(huán)境里運(yùn)行,Android的編譯工程文件還比較簡單悲龟,更容易編寫屋讶,Android.mk這種Android里的編譯工程文件,遠(yuǎn)比天書般的autoconf工具要簡單须教,比傳統(tǒng)的Makefile也更容易理解皿渗。

Android的編譯系統(tǒng),由build目錄里一系列.mk編譯腳本和其他腳本組成,以build/main.mk作為主入口乐疆。main.mk文件會改到配置項(xiàng)划乖,然后再通過配置項(xiàng)循環(huán)地去編譯出所需要的LOCAL_MODULE。而這些LOCAL_MODULE的編譯目標(biāo)诀拭,是由Android.mk來定義的,而到底如何編譯目標(biāo)對象煤蚌,則是由簡單的include $(BUILD_*)這樣的編譯宏選項(xiàng)來提供耕挨,比如include $(BUILD_SHARED_LIBRARY)則會編譯生成動態(tài)鏈接庫.so文件。這樣的編譯系統(tǒng)拋棄autoconf的靈活性尉桩,換回了跨平臺筒占、編寫簡單。

當(dāng)然蜘犁,這樣的編譯工具翰苫,也不是沒有代價(jià)的,使用簡易化的Android編譯環(huán)境則意味著Android放棄了一些現(xiàn)有絕大部分代碼的可移植性这橙,Linux環(huán)境里的一些常用庫移植到Android環(huán)境則需要大量移植工作奏窑,像萬能播放器核心ffmpeg,目前幾乎只有商用應(yīng)用程序才肯花精力將它移植到Android系統(tǒng)里屈扎。

當(dāng)然埃唯,光有自己代碼結(jié)構(gòu),光有更簡易的編譯環(huán)境鹰晨,并不解決所有問題墨叛,我們的Android系統(tǒng),最后還必須通過訪問系統(tǒng)調(diào)用模蜡、讀寫驅(qū)動的設(shè)備文件來完成底層的操作漠趁。不然我們的Android系統(tǒng)就只會是一個光桿司令,什么都不是忍疾,沒有任何功能闯传。既然我們擁有了相關(guān)簡化的系統(tǒng)結(jié)構(gòu),我們在內(nèi)部自己按自己的想法去訪問硬件也是可以卤妒,只是這樣在升級與維護(hù)起來的代碼會變得很大丸边,比如我們通過”/dev/input/event0”來讀觸摸屏,”/dev/input/event1”來讀重力感應(yīng)器荚孵,如果換個平臺妹窖,這些設(shè)備名字變了怎么辦?或者有些私有化平臺收叶,都完全不使用這樣的標(biāo)準(zhǔn)化設(shè)備文件命名時(shí)怎么辦骄呼?難道針對每個平臺準(zhǔn)備一份源代碼來做這些修改?于是就有了內(nèi)部訪問接口統(tǒng)一化的問題,Android在對于底層的設(shè)備文件的訪問上蜓萄,又完成了一層抽象隅茎,也就是我們的硬件抽象層。

硬件抽象層嫉沽,準(zhǔn)確地說辟犀,是介于Framework層與Linux內(nèi)核層之間的一個層次,F(xiàn)ramework通過硬件抽象層的統(tǒng)一接口來向下訪問绸硕,在Linux內(nèi)核上硬件訪問接口上的差異性堂竟,則通過硬件抽象層來進(jìn)行屏蔽。硬件抽象層玻佩,英文是Hardware Abstraction Layer出嘹,簡稱HAL,于是也就是我們常見到的AndroidHAL層咬崔。

提供硬件抽象層之后税稼,這時(shí)Framework層與底層Linux內(nèi)核之間的耦合性,就完全消除掉了垮斯。如果要將Android部署到其他操作系統(tǒng)郎仆、或是操作系統(tǒng)內(nèi)核之上,只需要將HAL層的相應(yīng)接口實(shí)現(xiàn)換成其他平臺上的訪問機(jī)制即可兜蠕,而Framework只會使用HAL層的統(tǒng)一接口向下訪問丸升,完全不知道底層變動信息。

在Android的世界里牺氨,硬件抽象其實(shí)是包含多種含義的狡耻,可能會有多種的硬件訪問實(shí)現(xiàn),比如RIL猴凹、BlueZ這樣的盡可能兼容已有解決方案的廣義上的HAL夷狰,F(xiàn)ramework會通過Socket來訪問傳統(tǒng)方式實(shí)現(xiàn)的daemon,也有Gralloc郊霎、Camera這樣先定義Framework向下訪問的接口沼头,然后由硬件向上提供接口實(shí)現(xiàn)的狹義上的HAL。我們的Android上的HAL實(shí)現(xiàn)书劝,更多地專指狹義上的HAL进倍,由源代碼里的hardware目錄提供。

狹義上的HAL购对,在實(shí)現(xiàn)上也會分成兩種:一種基于函數(shù)直接訪問的方式來實(shí)現(xiàn)猾昆,這種方式比較簡單粗暴,所以在命名上骡苞,被稱為libhardware_legacy實(shí)現(xiàn)垂蜗,由目錄hardware/libhardware_legacy里實(shí)現(xiàn)的一個個動態(tài)鏈接庫來實(shí)現(xiàn)楷扬;另一種則使用了面向?qū)ο蟮募记桑蠈油ㄟ^一個hardware_module_t來訪問贴见,而具體的某一類HAL的實(shí)現(xiàn)烘苹,則會將這一hardware_module_t按需求進(jìn)行拓展,這種方式被稱為libhardware實(shí)現(xiàn)片部,由hardware/libhardware提供hardware_modult_t的訪問接口镣衡,硬件訪問的實(shí)現(xiàn)部分,也是動態(tài)鏈接庫文件档悠,但會在運(yùn)行時(shí)根據(jù)板卡的具體配置廊鸥,進(jìn)行動態(tài)加載。從長遠(yuǎn)看站粟,libhardware_legacy結(jié)構(gòu)的HAL實(shí)現(xiàn)是一種中間方案黍图,在Android發(fā)展過程里有可能會被libhardware方式的實(shí)現(xiàn)所取代曾雕,比如Android 4.0里奴烙,Audio、Camera完成了libhardware_legacy到libhardware的轉(zhuǎn)變剖张。

從設(shè)計(jì)的角度來看切诀,libhardware_legacy雖然解決了與Linux內(nèi)核的耦合問題,但是直接函數(shù)接口的訪問搔弄,終究還是靈活性不夠幅虑。以libhardware_legacy實(shí)現(xiàn)HAL的動態(tài)鏈接庫,必須被直接鏈接到Framework的實(shí)現(xiàn)里顾犹,通過JNI進(jìn)行直接訪問倒庵,不具備動態(tài)性,不能支持同一份Android可執(zhí)行環(huán)境支持不同硬件平臺炫刷。從設(shè)計(jì)角度來說擎宝,任何抽象都是由另一次間接調(diào)用來實(shí)現(xiàn),于是浑玛,我們在硬件訪問接口里再加入一層抽象绍申,這就是libhardware.so。Framework并不直接訪問具體的動態(tài)鏈接庫實(shí)現(xiàn)顾彰,而是通過libhardware.so里實(shí)現(xiàn)的通用接口來向下訪問极阅,并且也只會調(diào)用到預(yù)定義好的一些訪問接口,而HAL實(shí)現(xiàn)的涨享,是以Stub方式來提供到系統(tǒng)里一些功能訪問的具體實(shí)現(xiàn)筋搏。如果不好理解的,可以認(rèn)為是系統(tǒng)提供頭文件厕隧,也就是我們右圖中的<>拆又,這些只是接口類定義儒旬。而實(shí)現(xiàn)上,并不是直接通過頭文件來實(shí)現(xiàn)帖族,而是通過實(shí)現(xiàn)一個具有一定特性的hardware_module_t的數(shù)據(jù)結(jié)構(gòu)栈源,來向上提供具體的函數(shù)調(diào)用接口,與頭文件里所需要的接口相對應(yīng)竖般。為什么叫它Stub呢甚垦?是因?yàn)樵趌ibhardware這種模式里,把接口定義與具體的實(shí)現(xiàn)抽離開來涣雕,雖然不一定會使用面向?qū)ο笳Z言來實(shí)現(xiàn)(一般是通過C來實(shí)現(xiàn)的)艰亮,但提供了Interface+Stub這樣面向?qū)ο笫降膶?shí)現(xiàn),所以libhardware有時(shí)也被稱為stub模式實(shí)現(xiàn)的HAL挣郭。

使用libhardware之后迄埃,HAL層實(shí)現(xiàn)上的可復(fù)用性與運(yùn)行時(shí)的靈活性則被大大增強(qiáng)了。在libhardware框架下兑障,F(xiàn)ramework都不再直接調(diào)用HAL層侄非,而是通過hw_get_module()方法來在/system/lib/hw和/system/vendor/lib/hw這兩個目錄里循環(huán)尋找合適的.so實(shí)現(xiàn),比如針對sensor流译,會有sensor.default.so逞怨,sensor.goldfish.so,sensor.xxx.so福澡,會有不同種實(shí)現(xiàn)叠赦,以用于加載合適的實(shí)現(xiàn)。雖然這些只會在開機(jī)時(shí)執(zhí)行一次革砸,但通過簡單方式至少也實(shí)現(xiàn)了用同一份二進(jìn)制代碼提供多種硬件平臺的支持除秀。使用libhardware實(shí)現(xiàn)的HAL,當(dāng)我們的實(shí)現(xiàn)的HAL有問題時(shí)算利,我們可以刪掉有問題的HAL册踩,此時(shí)啟動時(shí)會使用一個xxx.default.so的純軟件實(shí)現(xiàn)的不進(jìn)行任何硬件訪問的.so文件,讓我們還是可以繞開啟動時(shí)的HAL加載錯誤笔时。

Android這種強(qiáng)化可移植性的設(shè)計(jì)棍好,最終使Android的移植移植過程變得相對比較簡單。如果是做Android的手機(jī)或是平板的開發(fā)允耿,也許我們需要做的只是提供板塊相關(guān)的配置文件借笙,從而可以改變一些編譯時(shí)的配置參數(shù)。如果我們的硬件平臺跟Android源代碼時(shí)使用的標(biāo)準(zhǔn)平臺(比如Google的“親兒子”手機(jī)Nexus系列的產(chǎn)品较锡,或是pandaboard這樣作為參考設(shè)計(jì)的產(chǎn)品)业稼,對于移植過程而言,我們可能什么都不需要做蚂蕴,直接可以編譯運(yùn)行低散,然后再做產(chǎn)品化的微調(diào)俯邓;如果我們使用的硬件平臺跟某些產(chǎn)商提供的開源項(xiàng)目的硬件結(jié)構(gòu)一樣,比如Qualcomm提供的codeaurora.org熔号,TI的Omapedia稽鞭,還有各大廠商都涌躍參與的linaro.org項(xiàng)目等等,這時(shí)需要完成的移植工作也會類似的很幸鳌朦蕴;如果我們的提供的硬件平臺跟Android這些已有的開源資源很不一樣,這時(shí)弟头,我們需要完成的移植工作也不會很大吩抓,只需要根據(jù)特定的硬件平臺實(shí)現(xiàn)HAL,這一過程所需要的工作量遠(yuǎn)小于其他平臺的移植過程赴恨。

Android的移植過程疹娶,基本上分為:

Bootloader與Linux內(nèi)核的移植

Repo環(huán)境(Android源代碼基于repo管理,最后使用repo)

交叉編譯器伦连、Bionic C庫與Dalvik虛擬機(jī)的移植(如果不是ARM雨饺、X86和MIPS這三種基本構(gòu)架)

提供板卡支持所需要的配置

實(shí)現(xiàn)所需要使用的HAL

Android產(chǎn)品化,完成界面或是功能上的定制

這些移植過程的步驟如下圖所示:

對于我們做Android移植與系統(tǒng)級開發(fā)而言除师,可能我們所需要花的代碼并不是那么大沛膳。像Bootloader與Linux內(nèi)核的移植扔枫,這一般都在進(jìn)行Android系統(tǒng)移植時(shí)早就會就緒的汛聚,比如我們?nèi)ミx購某個產(chǎn)商的主芯片時(shí)(Application Processor,術(shù)語為AP)短荐,這些Android之前的支持大都已經(jīng)就緒倚舀。而作為產(chǎn)業(yè)的霸主,我們除非極其特殊的情況忍宋,我們也不需要接觸交叉編譯器和Dalvik虛擬機(jī)的移植痕貌。所以一般情況下,我們的Android移植是從建立repo源代碼管理環(huán)境開始糠排,然后再進(jìn)行板卡相關(guān)的配置舵稠,然后實(shí)現(xiàn)HAL。而Android的產(chǎn)品化這個步驟入宦,F(xiàn)ramework的細(xì)微調(diào)整與編寫自己平臺上特殊的應(yīng)用程序哺徊,嚴(yán)格意義上來說,F(xiàn)ramework也不屬于Android移植工作范圍內(nèi)的乾闰,我們一般把它定位于Android產(chǎn)品化或是Android定制化這個步驟里落追。Android移植相對來說非常簡單,而真正完成Android產(chǎn)品化則會是一個比較耗時(shí)耗人力的過程涯肩。

所謂的板卡的配置文件轿钠,一般是放在一個專門的目錄里巢钓,在2.3以前,是發(fā)在vendor目錄下疗垛,從2.3開始症汹,vendor目錄只存放二進(jìn)制代碼,配置文件移到了device目錄贷腕。在這一目錄里烈菌,會以“產(chǎn)商名/設(shè)備名”的命名方式來規(guī)范配置的目錄結(jié)構(gòu)。比如是產(chǎn)商名是ti花履,設(shè)備名是panda芽世,則會以“device/ti/panda”來存放這些配置文件,再在這個目錄里放置平臺相關(guān)的配置項(xiàng)诡壁。配置文件济瓢,則是會幾個關(guān)鍵文件構(gòu)成:

} vendorsetup.sh,使用add_lunch_combo將配置項(xiàng)導(dǎo)入編譯環(huán)境

} AndroidProducts.mk妹卿,這是會被編譯系統(tǒng)掃描的文件旺矾,通過在這一文件里再導(dǎo)入具體的編譯配置文件,比如ti_panda.mk

} ti_panda.mk夺克,在這一文件里定義具體的產(chǎn)品名箕宙,設(shè)備名這些關(guān)鍵變量,這些變量是在Android編譯過程里起關(guān)鍵配置作用的變量铺纽。一般說來柬帕,這個文件不會很復(fù)雜,主要依賴導(dǎo)入一些其他的配置文件來完成所有的配置狡门,比如語言配置等陷寝。而設(shè)備特殊的設(shè)置,則一般是使用同目錄下的device.mk文件來進(jìn)行定制化的設(shè)置其馏。

} device.mk凤跑,在這一文件會使用一些更加復(fù)雜一些配置,包含一些需要編譯的子工程叛复,設(shè)置某些特殊的編譯參數(shù)仔引,以及進(jìn)行系統(tǒng)某些特性的定制化,比如需要自定義怎樣的顯示效果褐奥、配置文件等

} BoardConfig.mk咖耘,在這一文件則是板子相關(guān)的一些定制項(xiàng),以宏的方式傳入到編譯過程里抖僵,比如BOARD_SYSTEMIMAGE_PARTITION_SIZE來控制system分區(qū)的大小, TARGET_CPU_SMP來控制是否需要使用SMP(對稱多處理器)支持等鲤看。一般,對于同一個板卡環(huán)境耍群,這些參數(shù)可以照抄义桂,勿須修改找筝。

所有的這些配置文件,并不是必須的慷吊,只不過是建議性的袖裕,在這一點(diǎn)上也常會透露出產(chǎn)商在開源文化的素質(zhì)。畢竟是開源的方案溉瓶,如果都使用約定俗成的解決方案急鳄,則大家都會不用看也知道怎么改。但一些在開源做得不好的廠商堰酿,對這些的配置環(huán)境都喜歡自己搞一套東西出來疾宏,要顯得自己與眾不同,所以對于配置文件的寫法與移植過程触创,也需要具體情況具體對待坎藐。

當(dāng)我們完成了這些配置上的工作后,可以先將這些配置上傳到repo的服務(wù)器管理起來哼绑,剩下的移植工作就是實(shí)現(xiàn)所需要的HAL了岩馍。在Android移植過程里,很多HAL的實(shí)現(xiàn)抖韩,是可以大量復(fù)用的蛀恩,可以找一個類似的配置復(fù)制過來,然后再進(jìn)行細(xì)微調(diào)整茂浮,比如使用ALSA ASoC框架的音頻支持双谆,基本功能都是通用的,只需要在Audio Path和HiJack功能上進(jìn)行微調(diào)即可励稳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佃乘,一起剝皮案震驚了整個濱河市囱井,隨后出現(xiàn)的幾起案子驹尼,更是在濱河造成了極大的恐慌,老刑警劉巖庞呕,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件新翎,死亡現(xiàn)場離奇詭異,居然都是意外死亡住练,警方通過查閱死者的電腦和手機(jī)地啰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讲逛,“玉大人亏吝,你說我怎么就攤上這事≌祷欤” “怎么了蔚鸥?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵惜论,是天一觀的道長。 經(jīng)常有香客問我止喷,道長馆类,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任弹谁,我火速辦了婚禮乾巧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘预愤。我一直安慰自己沟于,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布植康。 她就那樣靜靜地躺著社裆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪向图。 梳的紋絲不亂的頭發(fā)上泳秀,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音榄攀,去河邊找鬼嗜傅。 笑死,一個胖子當(dāng)著我的面吹牛檩赢,可吹牛的內(nèi)容都是我干的吕嘀。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼贞瞒,長吁一口氣:“原來是場噩夢啊……” “哼偶房!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起军浆,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤棕洋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乒融,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰盘,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年赞季,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愧捕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡申钩,死狀恐怖次绘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤邮偎,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布罗洗,位于F島的核電站,受9級特大地震影響钢猛,放射性物質(zhì)發(fā)生泄漏伙菜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一命迈、第九天 我趴在偏房一處隱蔽的房頂上張望贩绕。 院中可真熱鬧,春花似錦壶愤、人聲如沸淑倾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娇哆。三九已至,卻和暖如春勃救,著一層夾襖步出監(jiān)牢的瞬間碍讨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工蒙秒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勃黍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓晕讲,卻偏偏與公主長得像覆获,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓢省,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容