Android里面的一些設(shè)計思想感悟
一切皆文件
一切皆文件不是Android的首創(chuàng)蜡镶,是Linux/Unix的首創(chuàng)。Linux一個非常好的設(shè)計就是一切皆文件的概念促煮,怎么理解呢拨匆,可以從兩個方面理解。
一個是應(yīng)用程序使用者琐鲁,一個是具體程序?qū)崿F(xiàn)卫旱,Linux一切皆文件的概念主要是對驅(qū)動來說的,應(yīng)用程序需要使用設(shè)備围段,設(shè)備千差萬別顾翼,標準也是各不相同,是不是每種設(shè)備都需要單獨去寫奈泪,使用它的接口也是不同的呢适贸?當然不能這樣,否則涝桅,這樣的程序無混亂不堪拜姿,根本無法移植。
Linux把所有的設(shè)備都抽象成文件冯遂,不管你是訪問打印機蕊肥,使用鍵盤,鼠標债蜜,耳機晴埂,訪問網(wǎng)絡(luò)究反,創(chuàng)建一個socket連接,進程間的通信等儒洛,他們都是一個文件精耐,應(yīng)用程序需要訪問的就是訪問一個文件,按照訪問文件的標準流程來就可以了琅锻,打開文件卦停,讀寫文件,最后關(guān)閉恼蓬。編寫的使用設(shè)備文件的邏輯就被簡化為訪問一個文件惊完,使用者不用寫大量的代碼即可實現(xiàn)自己的業(yè)務(wù),非常簡單处硬。
對于設(shè)備程序的實現(xiàn)者來說小槐,也很簡單,他只需要實現(xiàn)一個文件的具體業(yè)務(wù)邏輯即可荷辕。只需要實現(xiàn)文件的打開凿跳,讀,寫疮方,輪詢等這些邏輯控嗜。實現(xiàn)者也可以不用寫非常多的代碼即可實現(xiàn)自己的邏輯。
框架是什么骡显,框架就是方便程序的使用者和具體的實現(xiàn)者疆栏,框架就是給開發(fā)者,實現(xiàn)者統(tǒng)一的接口惫谤,非常簡潔的實現(xiàn)壁顶,可以做到傻瓜式編程,甚者石挂,有一些框架把開發(fā)者變成了配置工程師博助,只需要配置就實現(xiàn)了自己的業(yè)務(wù)邏輯险污。
看看Linux具體是怎樣設(shè)計實現(xiàn)的痹愚。
總線,驅(qū)動蛔糯,設(shè)備
Linux外圍設(shè)備驅(qū)動拯腮,是通過bus,driver蚁飒,device來進行管理的动壤,任何程序想要跑起來都需要cpu給它分配時間片,外設(shè)都是通過總線來和cpu通信的淮逻。Linux已經(jīng)有一套完整的總線琼懊,設(shè)備的完整框架阁簸,驅(qū)動工程師只需要正確地注冊自己的驅(qū)動,實現(xiàn)自己的設(shè)備文件程序即可哼丈。我們慢慢看看總線启妹,驅(qū)動,設(shè)備是如何協(xié)同工作的醉旦。
總線相當于驅(qū)動和設(shè)備的總管饶米,具體的一個設(shè)備實現(xiàn)就是一個設(shè)備,而連接到總線的設(shè)備就是一個驅(qū)動车胡。
總線在軟件層面主要是負責管理設(shè)備和驅(qū)動檬输。
設(shè)備要讓系統(tǒng)感知自己的存在,設(shè)備需要向總線注冊自己匈棘;同樣地丧慈,驅(qū)動要讓系統(tǒng)感知自己的存在,也需要向總線注冊自己主卫。設(shè)備和驅(qū)動在初始化時必須要明確自己是哪種總線的伊滋,I2C設(shè)備和驅(qū)動不能向USB總線注冊吧。
多個設(shè)備和多個驅(qū)動都注冊到同一個總線上队秩,那設(shè)備怎么找到最適合自己的驅(qū)動呢笑旺,或者說驅(qū)動怎么找到其所支持的設(shè)備呢?
這個也是由總線負責馍资,總線就像是一個紅娘筒主,負責在設(shè)備和驅(qū)動中牽線。
設(shè)備會向總線提出自己對驅(qū)動的條件(最簡單的也是最精確的就是指定對方的名字了)鸟蟹,而驅(qū)動也會向總線告知自己能夠支持的設(shè)備的條件(一般是型號ID等乌妙,最簡單的也可以是設(shè)備的名字)。
設(shè)備在注冊的時候建钥,總線就會遍歷注冊在它上面的驅(qū)動藤韵,找到最適合這個設(shè)備的驅(qū)動,然后填入設(shè)備的結(jié)構(gòu)成員中熊经;驅(qū)動注冊的時候泽艘,總線也會遍歷注冊在其之上的設(shè)備,找到其支持的設(shè)備(可以是多個镐依,驅(qū)動和設(shè)備的關(guān)系是1:N)匹涮,并將設(shè)備填入驅(qū)動的支持列表中。我們稱總線這個牽線的行為是match槐壳。牽好線之后然低,設(shè)備和驅(qū)動之間的交互紅娘可不管了。
總線在匹配設(shè)備和驅(qū)動之后驅(qū)動要考慮一個這樣的問題,設(shè)備對應(yīng)的軟件數(shù)據(jù)結(jié)構(gòu)代表著靜態(tài)的信息雳攘,真實的物理設(shè)備此時是否正常還不一定带兜,因此驅(qū)動需要探測這個設(shè)備是否正常。我們稱這個行為為probe吨灭,至于如何探測鞋真,那是驅(qū)動才知道干的事情,總線只管吩咐得了沃于。
device和driver綁定
當增加新device的時候涩咖,bus 會輪循它的驅(qū)動列表來找到一個匹配的驅(qū)動,它們是通過device id和 driver的id_table來進行 ”匹配”的繁莹,主要是在 driver_match_device()[drivers/base/base.h]通過 bus->match() 這個callback來讓驅(qū)動判斷是否支持該設(shè)備檩互,一旦匹配成功,device的driver字段會被設(shè)置成相應(yīng)的driver指針:
really_probe()
{
???dev->driver = drv;
??? if(dev->bus->probe) {
??????? ret =dev->bus->probe(dev);
??????? ...
??? } else if(drv->probe) {
??????? ret =drv->probe(dev);
??????? ...
??? }
}
然后 callback 該 driver 的 probe 或者 connect 函數(shù)咨演,進行一些初始化操作闸昨。
同理,當增加新的driver時薄风,bus也會執(zhí)行相同的動作饵较,為驅(qū)動查找設(shè)備。因此遭赂,綁定發(fā)生在兩個階段:
1: 驅(qū)動找設(shè)備循诉,發(fā)生在driver向bus系統(tǒng)注冊自己時候,函數(shù)調(diào)用鏈是:
driver_register --> bus_add_driver -->
driver_attach() [dd.c] -- 將輪循device鏈表撇他,查找匹配的device茄猫。
2: 設(shè)備查找驅(qū)動,發(fā)生在設(shè)備增加到總線的的時候困肩,函數(shù)調(diào)用鏈是:
device_add --> bus_probe_device -->
device_initial_probe --> device_attach --將輪循driver鏈表划纽,查找匹配的driver。
匹配成功后锌畸,系統(tǒng)繼續(xù)調(diào)用 driver_probe_device() 來 callback 'drv->probe(dev)' 或者 'bus->probe(dev)
-->drv->connect()勇劣,在probe或者connect函數(shù)里面,驅(qū)動開始實際的初始化操作潭枣。因此比默,probe() 或者 connect() 是真正的驅(qū)動'入口'。
對驅(qū)動開發(fā)者而言卸耘,最基本是兩個步驟:
定義device id table.
probe()或connect()開始具體的初始化工作退敦。
驅(qū)動實現(xiàn)者需要做的事情
驅(qū)動只需要注冊了驅(qū)動粘咖,實現(xiàn)file_operations里面相應(yīng)的操作即可
struct file_operations??
{?
?? int (*open)(struct inode *, struct file *);?
?? int (*ioctl)(struct inode *, struct file *, ...);?
?? ssize_t(*read) (struct file *, char __user *,...);?
?? ssize_t(*write) (struct file *, const char __user *, ...);?
?? unsignedint(*?? poll )(struct file *, structpoll_table_struct *);
?? …?
}
而驅(qū)動probe成功之后蚣抗,通過sysfs文件系統(tǒng)、uevent事件通知機制、后臺應(yīng)用服務(wù)mdev程序翰铡,三者的配合钝域,在/dev目錄創(chuàng)建對應(yīng)的設(shè)備文件。
而應(yīng)用層只需要知道相應(yīng)的這個節(jié)點锭魔,直接打開這個文件例证,進行相應(yīng)的讀寫即可。
所有的事情就簡化了迷捧,驅(qū)動實現(xiàn)者就需要向總線注冊驅(qū)動织咧,注冊設(shè)備,實現(xiàn)設(shè)備file_operations里面的需要的操作漠秋,生成對應(yīng)的文件節(jié)點笙蒙,而應(yīng)用層來訪問,只需要打開這個文件庆锦,讀寫文件捅位。使用者的代碼全都簡化為對文件的訪問,實現(xiàn)者就只是需要實現(xiàn)文件的open, write, read, ioctl等搂抒。
文件大多數(shù)的數(shù)據(jù)是同步的艇搀,打開文件,直接讀寫求晶,馬上得到結(jié)果焰雕,但是有一些并不是數(shù)據(jù)馬上準備好的,那么就需要等待芳杏,poll函數(shù)是提供給上層進行輪詢淀散,數(shù)據(jù)是否已經(jīng)準備好了,它的應(yīng)用主要是配合上層的I/O多路復用機制蚜锨。
簡單粗暴的說Linux將整個在Linux環(huán)境上的編程簡化為了文件的讀寫档插,將復雜的代碼編寫簡化為文件的讀寫,文件的讀寫也會有復雜的情況亚再,如果有異步郭膛,如果有并發(fā),如教科書上說的饑餓氛悬,死鎖這些并發(fā)問題则剃,也就是傳說中的讀寫者問題,如何避免掉這些問題如捅,Linux有一套完整的方案來解決并發(fā)讀寫的問題棍现。
Linux上的一個讀寫者程序 I/O多路復用
select和poll機制的原理非常相近,主要是一些數(shù)據(jù)結(jié)構(gòu)的不同镜遣,最終到驅(qū)動層都會執(zhí)行f_op->poll()己肮,執(zhí)行__pollwait()把自己掛入等待隊列。 一旦有事件發(fā)生時便會喚醒等待隊列上的進程。比如監(jiān)控的是可寫事件谎僻,則會在write()方法中調(diào)用wakeup方法喚醒相對應(yīng)的等待隊列上的進程娄柳。這一切都是基于底層文件系統(tǒng)作為基石來完成I/O多路復用的事件監(jiān)控功能。
他們的套路都是監(jiān)聽相應(yīng)的文件上的事件f_op->poll()艘绍,當事件發(fā)生喚醒對應(yīng)事件里面的等待隊列赤拒,喚醒等待隊列上相應(yīng)的進程。喚醒后得進程就可以去讀寫诱鞠。這個主要是對那些異步的讀寫操作挎挖。
這套機制在Android上又被復用了,我們看看Android的讀寫者程序Handler機制
一切皆Context
Android為了最大簡化應(yīng)用程序的編寫航夺,對Android的組件肋乍,重要接口做了一次封裝,做成了Context敷存,只要有Context就可以調(diào)用組件墓造。
Android框架是一個C/S結(jié)構(gòu),也就是設(shè)計模式里面的代理模式锚烦,Service端就是Android服務(wù)觅闽,而Manager端是客戶端,提供給應(yīng)用層各種接口涮俄,Manager端和Service端通過進程間通信蛉拙。Android有很多服務(wù),對應(yīng)各自的硬件彻亲,但是四大組件就一個Context提供給應(yīng)用程序孕锄,可以調(diào)用框架的所有功能。
Android的Context設(shè)計跟Linux的驅(qū)動文件有異曲同工之妙苞尝,寫Android應(yīng)用程序畸肆,只要拿到Context就可以操作四大組件,訪問磁盤宙址,訪問資源文件轴脐,進行權(quán)限校驗等,幾乎所有Android功能都有抡砂。這個和Linux的一切皆文件很相似大咱,Linux對外提供的驅(qū)動都是文件,不管是管道注益,socket碴巾,設(shè)備節(jié)點,進程間的通信丑搔,全都是文件厦瓢,所有的操作就是對文件的操作提揍。
Context的設(shè)計在設(shè)計模式上來說是裝飾者模式,主要是為了防止子類數(shù)量爆炸式增長旷痕√夹猓看看它的類圖顽冶。
??? 一個典型的Decorator模式欺抗,基類Context是一個借口定義了各種接口,主要是四大組件使用的各種接口(獲取强重,啟動四大組件绞呈,訪問資源文件,訪問磁盤等)间景,ContextImpl負責實現(xiàn)接口的具體功能佃声。對外提供使用時,ContextImpl需要被包裝(Wrapper)一下倘要,這就有了ContextWrapper這個修飾器圾亏。修飾器一般只是一個傳遞者,修飾器所有的方法實現(xiàn)都是調(diào)用具體的實現(xiàn)類ContextImpl封拧,所以修飾器ContextWrapper需要持有一個ContextImpl的引用志鹃。
???????Application啟動之后會綁定一個相應(yīng)的Context,
看如下幾處關(guān)鍵代碼
// ActivityThread.handleBindApplication()
private voidhandleBindApplication(AppBindData data) {
???...
???Application app = data.info.makeApplication(data.restrictedBackupMode,null);
???mInitialApplication = app;
???...
???try {
???????mInstrumentation.callApplicationOnCreate(app);
??? }catch (Exception e) {...}
}
// LoadedApk.makeApplication()
public Application makeApplication(booleanforceDefaultAppClass, Instrumentation instrumentation) {
???...
???if (forceDefaultAppClass || (appClass == null)) {
???????appClass = "android.app.Application";
??? }
???try {
???????java.lang.ClassLoader cl = getClassLoader();
???????ContextImpl appContext = ContextImpl.createAppContext(mActivityThread,this);
???????app = mActivityThread.mInstrumentation.newApplication(
??????????????? cl, appClass, appContext);
???????appContext.setOuterContext(app);
??? }catch (Exception e) {...}
???...
}
// ContextImpl.createAppContext()
static ContextImplcreateAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
???if (packageInfo == null) throw newIllegalArgumentException("packageInfo");
???return new ContextImpl(null, mainThread,
???????????packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}
// ContextImpl.constructor()
private ContextImpl(ContextImpl container,ActivityThread mainThread,
???????LoadedApk packageInfo, IBinder activityToken, UserHandle user, intflags,
???????Display display, Configuration overrideConfiguration, int createDisplayWithId){
???mOuterContext = this; //外圍包裝器,暫時用
???...
???mMainThread = mainThread; //主線程
???mActivityToken = activityToken; //關(guān)聯(lián)到系統(tǒng)進程的ActivityRecord
???mFlags = flags;
???...
???mPackageInfo = packageInfo; // LoadedApk對象
???mResourcesManager = ResourcesManager.getInstance();
???...
???Resources resources = packageInfo.getResources(mainThread);
???...
???mResources = resources; //通過各種計算得到的資源
???...
???mContentResolver = new ApplicationContentResolver(this, mainThread,user); //訪問ContentProvider的接口
}
???????構(gòu)建Application對象完成后,便會調(diào)用其attach()函數(shù)綁定一個Context暮屡,這一綁定就相當于在ContextWrapper中關(guān)聯(lián)了一個ContextImpl鲤竹,這一層Decorator的修飾包裝關(guān)系這就么套上了∑飞剑回顧一下時序圖,ActivityThread中發(fā)起Application對象的創(chuàng)建操作,然后創(chuàng)建一個真實的ContextImpl對象(AppContext)评甜,最后將AppContext包裝進Application對象中,才完成整個的修飾動作仔涩,在這之后蜕着,Application便可作為一個真正的Context使用,可以回調(diào)其生命周期的onCreate()方法了红柱。應(yīng)用程序有了Context之后承匣,就可以執(zhí)行Android的幾乎所有操作了,startActivity啟動Activity锤悄,bindService韧骗,sendBroadcast,getContentResolver零聚,獲取andorid目錄袍暴,資源文件等些侍。
???????Activity,Service的創(chuàng)建過程也類似政模。
??????Android的應(yīng)用層接口沒有設(shè)計成一堆組件岗宣,一堆類,一堆接口淋样,分成一大堆包耗式,調(diào)用時需要引入一大堆包,調(diào)用一大堆類趁猴,非常的不利于代碼管理刊咳。 Android Context設(shè)計的意義,只要有Android環(huán)境Context儡司,就可以使用Android的任何東西娱挨,極大地方便Android應(yīng)用程序的開發(fā)。
???????PMS捕犬,四大組件的管家
???????PMS的設(shè)計主要是作為四大組件的管家跷坝,專門用于解析應(yīng)用的四大組件及權(quán)限,解析后每個組件都有一個緩存碉碉。當應(yīng)用程序有了Context柴钻,配合著Intent,幾乎可以執(zhí)行Android層的操作誉裆。當應(yīng)用發(fā)起了調(diào)用顿颅,Android會到PMS的組件緩存中進行查詢,如果有對應(yīng)組件切權(quán)限滿足足丢,則允許調(diào)用粱腻,否則失敗。由于這些組件的信息都在內(nèi)存中斩跌,所以響應(yīng)很快绍些。而PMS在開機完成之前已經(jīng)對各個組件的唯一性,是否合法等都做了檢查耀鸦,不會導致組件的沖突柬批,不符合規(guī)范等。
???????Context主要是提供給外部應(yīng)用程序使用袖订,只要應(yīng)用程序拿到一個Context氮帐,配合著Intent,就可以使用Android的組件洛姑。而PMS主要是內(nèi)部自己查詢組件使用上沐,當有應(yīng)用程序發(fā)起調(diào)用,PMS負責查詢與之匹配的組件楞艾,滿足條件就返回参咙,給予啟動龄广。
Android服務(wù)
分離良好的代碼結(jié)構(gòu)
良好的代碼結(jié)構(gòu),一定是各個功能分離完全的代碼蕴侧,每部分只是自己模塊的功能择同,不會受到其他部分的影響,會非常方便調(diào)試净宵,便于移植敲才。我們接下來看看分離良好的代碼是怎樣的。
接口塘娶,轉(zhuǎn)接归斤,實現(xiàn)痊夭。滿足這種結(jié)構(gòu)的代碼可以稱得上是代碼結(jié)構(gòu)良好的代碼刁岸,怎么說呢,接口是提供給其他模塊調(diào)用的她我,所以虹曙,這部分一定要抽出來,作為獨立的一部分番舆,有完整的接口說明酝碳,包括返回值,參數(shù)恨狈。這樣調(diào)用者不用關(guān)心內(nèi)部具體的實現(xiàn)疏哗,只需要按照接口說明直接使用即可。
轉(zhuǎn)接禾怠,轉(zhuǎn)接部分代碼也要作為一個獨立的部分獨立出來返奉,轉(zhuǎn)接的代碼是連接接口和實現(xiàn)的部分,并沒有實際的功能吗氏,但是有了這部分之后代碼結(jié)構(gòu)會更清晰芽偏,特別是代碼在修改和移植的時候,轉(zhuǎn)接部分的代碼就相當于插樁直接移植弦讽,不需要任何修改污尉。而修改部分只要接口不修改,轉(zhuǎn)接的代碼也不需要修改往产,只需要修改具體的實現(xiàn)被碗。
具體實現(xiàn),具體的實現(xiàn)可能是千差萬別仿村,但是最終都是對具體接口的實現(xiàn)锐朴,一定要滿足接口的定義。一般程序出了bug奠宜,大多數(shù)都是具體實現(xiàn)部分的問題包颁,只需要修改出問題的實現(xiàn)部分即可瞻想,代碼就不會大量的改動。移植的時候也只需要適配具體的實現(xiàn)娩嚼,而接口和轉(zhuǎn)接部分不會變化蘑险,易于移植。
大家看看Android的框架代碼岳悟,Manager接口和Service實現(xiàn)的分離就使整個代碼結(jié)構(gòu)非常清晰佃迄。
???????? 如何看代碼?按照如上的寫法贵少,首先應(yīng)該看代碼處有沒有特別定義自己的實體呵俏,類,接口說明這些滔灶,按照這些說明普碎,再去看它的調(diào)用關(guān)系,最后看它的實現(xiàn)录平。這樣基本上就可以理清代碼了麻车。
??????? 怎么區(qū)分一個程序它的客戶端和服務(wù)端呢?不只是網(wǎng)絡(luò)程序這種斗这,服務(wù)器機器上的程序是服務(wù)端动猬,客戶端機器上的是客戶端,其實大多數(shù)程序都有客戶端和服務(wù)端表箭×蘖客戶端就是用戶調(diào)用的地方,服務(wù)端就是代碼的具體實現(xiàn)的地方免钻,就如同main函數(shù)里面的調(diào)用子函數(shù)彼水,而被調(diào)用的子函數(shù)就是服務(wù)端。
??????? 如果一個程序再加強伯襟,在服務(wù)端對客戶端的調(diào)用做一些限制呢猿涨,不讓客戶端輕易地去調(diào)用服務(wù)端的內(nèi)容,必須是指定的接口姆怪,得到的也是指定的數(shù)據(jù)叛赚。甚至連服務(wù)端除了接口之外其他一無所知,那么久必須做成是一個C/S結(jié)構(gòu)稽揭。
??????? Android Binder就是一個很好的例子俺附,Binder把具體的實現(xiàn)和調(diào)用接口進行分離,調(diào)用端必須通過特定的接口才可以訪問服務(wù)端內(nèi)容溪掀,而且必須要有相應(yīng)的權(quán)限才可以事镣,客戶端是沒法隨意引用服務(wù)端的任何代碼的【疚福客戶端就不能隨意的調(diào)用服務(wù)端內(nèi)容璃哟,隱藏自己的細節(jié)氛琢,保證程序的安全性。試想一下随闪,客戶端和服務(wù)端不做嚴格的區(qū)分阳似,混在一起,所有的程序可以隨意調(diào)用铐伴,代碼到處交叉撮奏,任何一個調(diào)用的地方都有最大的權(quán)限,整個程序肯定很快就亂套当宴,穩(wěn)定性畜吊,安全性會是一個極大的問題。
??????? 服務(wù)是怎么組織起來的呢户矢?打個比方玲献,就是你要先服務(wù)中心進行登記注冊,你可以來進行查詢是否有這個服務(wù)逗嫡,然后可以通過特定的方式獲取這個服務(wù)青自。服務(wù)里面的數(shù)據(jù)就是一個提供服務(wù)的一個隊列株依,要想獲得這個服務(wù)驱证,就得先在這個隊列里面找到這個具體的服務(wù),然后拿到它恋腕。
??????? 一般的服務(wù)就是一個資源管理程序抹锄。通過接口對外提供數(shù)據(jù),服務(wù)里面的數(shù)據(jù)是服務(wù)至關(guān)重要的資源荠藤,服務(wù)對外提供的接口有哪幾種方式呢伙单?
??????? 同步,輪詢哈肖,異步
??????? 接口最多的方式就是同步接口吻育,調(diào)用直接得到結(jié)果。這是簡單高效的方式淤井,是絕大多數(shù)的實現(xiàn)方式布疼。如果數(shù)據(jù)在調(diào)用的時候沒有準備好,那么直接調(diào)用可能就不行了币狠,需要去定期查詢游两,如果數(shù)據(jù)準備好,則通過接口獲取數(shù)據(jù); 另外一種方式是通過一個監(jiān)聽漩绵,當數(shù)據(jù)準備好了直接回調(diào)贱案,也就是異步的方式。
??????? 輪詢的方式需要定期地去調(diào)用接口止吐,會頻繁地消耗cpu資源宝踪,導致性能下降侨糟,這種方案是一種下等方案。
??????? Handler機制瘩燥。
??????? Handler機制算是一個典型的生產(chǎn)者-消費者模型
?????????異步方式
??????? 最直接的方式就是觀察者模式
??????? 觀察者模式定義了對象間的一種一對多的依賴關(guān)系粟害,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知颤芬,并自動更新悲幅。觀察者模式屬于行為型模式,行為型模式關(guān)注的是對象之間的通訊站蝠,觀察者模式就是觀察者和被觀察者之間的通訊汰具。
???????但是,這種方式的代碼結(jié)構(gòu)太過于簡單菱魔,觀察者和被觀察者之間是直接綁定留荔,直接通信的,耦合非常緊密澜倦。當其中任何一個地方需要改動代碼聚蝶,必然會導致綁定的多個部分都要進行相應(yīng)的修改。沒有完全達到簡潔代碼的要求藻治。
??????? 訂閱-發(fā)布模式
??????? 訂閱-發(fā)布模式可以算是觀察者模式的一個去掉了代碼耦合的觀察者模式碘勉。訂閱者和發(fā)布者不直接通信,他們是通過通信通道間接通信的桩卵,通信通道對訂閱和發(fā)布的消息進行調(diào)度和轉(zhuǎn)發(fā)验靡,訂閱者只需要去訂閱自己感興趣的事件即可,而發(fā)布者把事件發(fā)布到通信通道雏节,事件通道將事件轉(zhuǎn)發(fā)給訂閱者胜嗓,從而達到代碼的去耦合。對比下觀察者模式和訂閱-發(fā)布模式的區(qū)別钩乍。
??????? Android的廣播算是一個標準的訂閱-發(fā)布模式辞州。廣播的接收者不和具體的廣播發(fā)送者進行通信,它只需要注冊自己感興趣的廣播就好寥粹,做好廣播接收處理邏輯即可变过。而廣播發(fā)送者也不用關(guān)心有哪些接收了自己的廣播,只需要將廣播發(fā)出去排作,交給廣播系統(tǒng)進行調(diào)度牵啦。廣播系統(tǒng)把廣播的類型分為有序廣播,并行廣播妄痪,并按相應(yīng)的類型進行調(diào)度哈雏。當廣播觸發(fā)時,直接將廣播轉(zhuǎn)發(fā)給注冊的接收者。
?????? 簡略的看看廣播機制
???????每一個應(yīng)用都持有一個LoadedApk實例裳瘪,LoadedApk實例中包含多個Context實例(一個進程對應(yīng)多個Activity和Service以及一個Application),每個Context實例可能創(chuàng)建了多個BroadcastReceiver實例土浸,每個BroadcastReceiver實例在動態(tài)注冊的時候都會生成一個對應(yīng)的ReceiverDispatcher實例,每個ReceiverDispatcher實例內(nèi)部又會由InnerReceiver類生成一個IIntentReceiver實例彭羹。這個IIntentReceiver實例在動態(tài)注冊BroadcastReceiver的時候會被傳遞給AMS黄伊,AMS會為每個IIntentReceiver實例創(chuàng)建一個ReceiverList實例,每個ReceiverList實例中保存了多個BroadcastFilter實例派殷,而這個BroadcastFilter實例里面包含了具體的IntentFilter和ReceiverList等相關(guān)信息还最。Android Broadcast之間的數(shù)據(jù)就是這樣建立起了聯(lián)系。
???????廣播的發(fā)送接收
??????廣播的發(fā)送接收毡惜,其實就是上面關(guān)系圖的一個反向查找的過程拓轻。應(yīng)用端調(diào)用系統(tǒng)服務(wù)(AMS)發(fā)送廣播,AMS會去廣播解析器IntentResolver中查詢哪些BroadcastFilter跟這個廣播有關(guān)聯(lián)经伙,然后把相關(guān)信息封裝成 BroadcastRecord類的實例添加到廣播發(fā)送序列BroadcastQueue中逐個廣播扶叉。在BroadcastQueue中廣播的時候會從BroadcastRecord中獲得BroadcastFilter進而獲得對應(yīng)的ReceiverList,ReceiverList中包含了對應(yīng)的IIntentReceiver實例帕膜,通過這個IIntentReceiver實例就可以找到對應(yīng)的BroadcastReceiver枣氧,調(diào)用其BroadcastReceiver.OnReceive方法把廣播傳遞給對應(yīng)的BroadcastReceiver。
???????廣播的接收者垮刹,發(fā)送者达吞,根本就不知道相互之間的存在,也不直接進行調(diào)用通信等危纫,接收者直接接收想要的廣播宗挥,而發(fā)送者直接發(fā)送相應(yīng)的廣播,廣播系統(tǒng)將相應(yīng)的廣播轉(zhuǎn)發(fā)給接收者
??????服務(wù)的管控
??????應(yīng)用程序需要通過Android的接口訪問Android服務(wù)的資源种蝶,那么Android的所有資源都是有限的,服務(wù)是需要對應(yīng)用程序的資源訪問進行限制的瞒大,除了常規(guī)的權(quán)限限制螃征,Android還提供了速度響應(yīng),內(nèi)存方面的限制透敌。其實對應(yīng)用程序?qū)Ψ?wù)的資源訪問應(yīng)該是有限制的盯滚,特別是寫應(yīng)用的童鞋,訪問資源時長應(yīng)該有限制(比如不要一直持有wake lock)酗电,資源大小有限制(比如不能占用太大的內(nèi)存)魄藕,訪問頻度有限制(比如不能頻繁的喚醒系統(tǒng)),否則Android就要出殺手锏來殺掉這些服務(wù)使用過度的程序撵术,最好的寫應(yīng)用方式是寫一個資源池背率,指定它的大小,超出則等待,否則過多或者過頻的使用系統(tǒng)資源必然會導致資源緊張而耗電寝姿,卡頓交排,甚至不穩(wěn)定。
??????Low Memory Killer
??????Low memory killer是Android內(nèi)存清理機制饵筑,因移動端設(shè)備的內(nèi)存埃篓、性能、電量等因素Android內(nèi)核維護一套內(nèi)存清理機制根资,就是LMK機制架专,會定期檢查應(yīng)用內(nèi)存使用情況、殺死一些進程來釋放內(nèi)存玄帕,Low memory killer 主要通過進程oom_adj來判定進程重要度胶征,這個值越小程序越重要桨仿,被殺死的可能性越低睛低。
??? 原理和概念
Low memory killer根據(jù)兩個原則,進程的重要性和釋放這個進程可獲取的空閑內(nèi)存數(shù)量服傍,來決定釋放的進程钱雷。
(1) 進程分類,后面的數(shù)字為oom_adj值吹零,每個進程都有oom_adj值罩抗,越小越重要,被殺的可能性越低灿椅,在相同oom_adj下內(nèi)存占用大的優(yōu)先被回收套蒂。
??? 名稱??????????? oom_adj值?????????? 解釋?
FOREGROUD_APP????????? 0??????? 前臺程序,可以理解為你正在使用的程序
VISIBLE_APP??????????? 1??????? 用戶可見的程序
SECONDARY_SERVER?????? 2???????后臺服務(wù)茫蛹,比如微信會在后臺運行服務(wù)
HOME_APP?????????????? 4??????? HOME操刀,就是主界面
HIDDEN_APP???????????? 7??????? 被隱藏的程序
CONTENT_PROVIDER?????? 14??????內(nèi)容提供者
EMPTY_APP????????????? 15?????? 空程序婴洼,既不提供服務(wù)柬采,也不提供內(nèi)容
(2) Android有兩個數(shù)組粉捻,lowmem_adj和lowmen_minfree肩刃,lowmem_adj存放著oom_adj的閾值杏头,lowmen_minfree存放minfree的警戒值浅碾,單位為頁(一頁4K)大州,通過這兩個數(shù)組計算需要回收的進程。
oom_adj???警戒值
?0??????? 1536
?1??????? 2048
?2??????? 4096
?7??????? 5120
?14?????? 5632
?15?????? 6144
(3) LMK檢查的時候基于多個標準來給每個進程評分垂谢,對adj高于多少(min_adj)的進程進行分析是否釋放厦画,評分最高的被選中并Kill。
2.進程oom_adj配置
進程的oom_adj是可以配置的滥朱,進程的類型在ActivityManagerService中可以看到根暑。
進程類型:
static final int EMPTY_APP_ADJ;
static final int HIDDEN_APP_MAX_ADJ;
static final int HIDDEN_APP_MIN_ADJ;
static final int HOME_APP_ADJ;
static final int BACKUP_APP_ADJ;
static final int SECONDARY_SERVER_ADJ;
static final int HEAVY_WEIGHT_APP_ADJ;
static final int PERCEPTIBLE_APP_ADJ;
static final int VISIBLE_APP_ADJ;
static final int FOREGROUND_APP_ADJ;
static final int CORE_SERVER_ADJ = -12;
static final int SYSTEM_ADJ = -16;
其中SYSTEM_ADJ代表著系統(tǒng)進程,CORE_SERVER_ADJ為系統(tǒng)核心服務(wù)徙邻,這類進程永遠不會被殺死排嫌,EMPTY_APP、CONTENT_PROVIDER 只類的最容易被殺死缰犁,F(xiàn)OREGROUND的進程很難被殺死
??????Application Not Response
??????在應(yīng)用程序響應(yīng)方面淳地,Android也有一套自己的控制機制,比如一個應(yīng)用寫得太爛帅容,總是在界面上執(zhí)行一大堆很重的操作颇象,經(jīng)城睬卡住,體驗非常差晃听,那么Android就會報應(yīng)用程序無響應(yīng)(ANR),彈出一個彈框,要么等待,要么殺掉重來锋八。
???????緩存
????????如果要隨時調(diào)試服務(wù)狀態(tài)的話紊服,最好所有的值都可以立即dump出來,便于實時知道服務(wù)數(shù)據(jù)里面的每個狀態(tài)痒谴,例如AMS怎顾,WMS里面的每種狀態(tài)株灸。如果要長久保存,那么久需要寫入文件了品追,寫入文件不能一有改變就寫入文件哪雕,應(yīng)該有一個定時時長,提高效率,例如BatteryStats耗電日志壤躲。
Android硬件抽象層
Linux上的Unix環(huán)境編程
Android是構(gòu)建在Linux之上的,在Linux上面就是Unix環(huán)境編程,當然寫的應(yīng)用程序不能直接訪問Linux脊奋,需要一個中間層來過渡巫延。
在Android誕生之初,有很多人都希望不通過java層直接訪問so玻熙,或者直接訪問到Linux的文件枚尼,他們這樣做就是想繞開Android所做的各種權(quán)限限制概而,能夠直接有效地使用和掌控手機的各項資源。但是這樣會擾亂系統(tǒng)的資源分配踩娘,可能會導致卡死,崩潰胶惰,有一些程序得不到有效地資源分配中捆,安全問題等匿级。
Android最初的Legacy策略
linux共享庫思路,把硬件接口都打包到libhardware_legacy.so,Android的JNI在要調(diào)用到這個庫的硬件接口函數(shù)時,只要將Android.mk中的LOCAL_SHARED_LIBRARIES增加libhardware_legacy就行,這樣就會到共享庫中獲取接口轻腺。so庫并不會自動運行仰美,但是當多個進程使用時 知纷,如果其他進程加載了它伍绳,那么so里面的數(shù)據(jù)就會映射到對應(yīng)進程空間中去,這樣會造成內(nèi)存使用增大乍桂。另外一個so庫本應(yīng)該完成一類功能,就應(yīng)該只有一個進程去加載放問它共缕,因為沒有限制,任何進程都有可能去加載,造成整個程序的混亂,滋生出安全問題拂封。
Stub策略
一個好的策略應(yīng)該是一個進程對應(yīng)一個hal琳水,滿足最低權(quán)限原則,即當前進程只能訪問當下所必須的資源店溢,如下圖所示沛硅。
我們看下Android是如何設(shè)計實現(xiàn)HAL的
HAL使用hw_module_t結(jié)構(gòu)體描述一類硬件抽象模塊鸡典。每個硬件抽象模塊都對應(yīng)一個動態(tài)鏈接庫,一般是由廠商提供的,這個動態(tài)鏈接庫必須尊重HAL的命名規(guī)范才能被HAL加載到版保,我們后面會看到。
每一類硬件抽象模塊又包含多個獨立的硬件設(shè)備夫否,HAL使用hw_device_t結(jié)構(gòu)體描述硬件模塊中的獨立硬件設(shè)備彻犁。
因此,hw_module_t和hw_device_t是HAL中的核心數(shù)據(jù)結(jié)構(gòu)凰慈,這2個結(jié)構(gòu)體代表了HAL對硬件設(shè)備的抽象邏輯汞幢。
typedef structhw_module_t {
/** tag must beinitialized to HARDWARE_DEVICE_TAG */
??? uint32_t tag;
??? uint16_t module_api_version;
#defineversion_major module_api_version
??? uint16_t hal_api_version;
#defineversion_minor hal_api_version
??? /** Identifier of module */
??? const char *id;
??? /** Name of this module */
??? const char *name;
??? /** Author/owner/implementor of the module*/
??? const char *author;
??? /** Modules methods */
??? struct hw_module_methods_t* methods;
??? /** module's dso */
??? void* dso;
#ifdef __LP64__
??? uint64_t reserved[32-7];
#else
??? /** padding to 128 bytes, reserved forfuture use */
??? uint32_t reserved[32-7];
#endif
} hw_module_t;
typedef structhw_device_t {
??? /** tag must be initialized toHARDWARE_DEVICE_TAG */
??? uint32_t tag;
??? uint32_t version;
??? /** reference to the module this devicebelongs to */
??? struct hw_module_t* module;
??? /** padding reserved for future use */
#ifdef __LP64__
??? uint64_t reserved[12];
#else
??? uint32_t reserved[12];
#endif
??? /** Close this device */
??? int (*close)(struct hw_device_t* device);
} hw_device_t;
C語言中并沒有繼承的概念,那它是如何用hw_device_t微谓,hw_module_t去實現(xiàn)不同模塊的具體功能的呢森篷?
C語言通過組合,指針強制轉(zhuǎn)換實現(xiàn)類似C++的繼承和多態(tài)行為豺型,我們接下來看看仲智,以vibrator為例,梳理一下vibrator的加載過程姻氨。
Vibrator的數(shù)據(jù)結(jié)構(gòu)定義vibrator.h文件中
/**
?* The id of this module
?*/
#defineVIBRATOR_HARDWARE_MODULE_ID "vibrator"
/**
?* The id of the main vibrator device
?*/
#defineVIBRATOR_DEVICE_ID_MAIN "main_vibrator"
structvibrator_device;
typedef structvibrator_device {
??? /**
???? * Common methods of the vibratordevice.? This *must* be the first memberof
???? * vibrator_device as users of thisstructure will cast a hw_device_t to
???? * vibrator_device pointer in contextswhere it's known the hw_device_t references a
???? * vibrator_device.
???? */
??? struct hw_device_t common;
??? /** Turn on vibrator
???? *
???? * This function must only be called afterthe previous timeout has expired or
???? * was canceled (through vibrator_off()).
???? *
???? * @param timeout_ms number of millisecondsto vibrate
???? *
???? * @return 0 in case of success, negativeerrno code else
???? */
??? int (*vibrator_on)(struct vibrator_device*vibradev, unsigned int timeout_ms);
??? /** Turn off vibrator
???? *
???? * Cancel a previously-started vibration,if any.
???? *
???? * @return 0 in case of success, negativeerrno code else
???? */
??? int (*vibrator_off)(struct vibrator_device*vibradev);
}vibrator_device_t;
static inlineint vibrator_open(const struct hw_module_t* module, vibrator_device_t** device)
{
??? return module->methods->open(module,VIBRATOR_DEVICE_ID_MAIN, TO_HW_DEVICE_T_OPEN(device));
}
Vibrator.c文件中vibrator的具體實現(xiàn)钓辆。
static constchar THE_DEVICE[] = "/sys/class/timed_output/vibrator/enable";
static booldevice_exists(const char *file) {
??? int fd;
??? fd = TEMP_FAILURE_RETRY(open(file,O_RDWR));
??? if(fd < 0) {
??????? return false;
??? }
??? close(fd);
??? return true;
}
static boolvibra_exists() {
??? return device_exists(THE_DEVICE);
}
static intwrite_value(const char *file, const char *value)
{
??? int to_write, written, ret, fd;
??? fd = TEMP_FAILURE_RETRY(open(file,O_WRONLY));
??? if (fd < 0) {
??????? return -errno;
??? }
??? to_write = strlen(value) + 1;
??? written = TEMP_FAILURE_RETRY(write(fd,value, to_write));
??? if (written == -1) {
??????? ret = -errno;
??? } else if (written != to_write) {
??????? /* even though EAGAIN is an errno valuethat could be set
?????????? by write() in some cases, none ofthem apply here.? So, this return
?????????? value can be clearly identified whendebugging and suggests the
?????????? caller that it may try to callvibrator_on() again */
??????? ret = -EAGAIN;
??? }else {
??????? ret = 0;
??? }
??? errno = 0;
??? close(fd);
??? return ret;
}
static intsendit(unsigned int timeout_ms)
{
??? char value[TIMEOUT_STR_LEN]; /* largeenough for millions of years */
??? snprintf(value, sizeof(value),"%u", timeout_ms);
??? return write_value(THE_DEVICE, value);
}
static intvibra_on(vibrator_device_t* vibradev __unused, unsigned int timeout_ms)
{
??? /* constant on, up to maximum allowed time*/
??? return sendit(timeout_ms);
}
static intvibra_off(vibrator_device_t* vibradev __unused)
{
??? return sendit(0);
}
static constchar LED_DEVICE[] = "/sys/class/leds/vibrator";
static intwrite_led_file(const char *file, const char *value)
{
??? char file_str[50];
??? snprintf(file_str, sizeof(file_str),"%s/%s", LED_DEVICE, file);
??? return write_value(file_str, value);
}
static boolvibra_led_exists()
{
??? char file_str[50];
??? snprintf(file_str, sizeof(file_str),"%s/%s", LED_DEVICE, "activate");
??? return device_exists(file_str);
}
static intvibra_led_on(vibrator_device_t* vibradev __unused, unsigned int timeout_ms)
{
??? int ret;
??? char value[TIMEOUT_STR_LEN]; /* largeenough for millions of years */
??? ret = write_led_file("state","1");
??? if (ret)
??????? return ret;
??? snprintf(value, sizeof(value),"%u\n", timeout_ms);
??? ret = write_led_file("duration",value);
??? if (ret)
??????? return ret;
??? return write_led_file("activate","1");
}
static intvibra_led_off(vibrator_device_t* vibradev __unused)
{
??? return write_led_file("activate","0");
}
static intvibra_close(hw_device_t *device)
{
??? free(device);
??? return 0;
}
static intvibra_open(const hw_module_t* module, const char* id __unused,
????????????????????? hw_device_t** device__unused) {
??? bool use_led;
??? if (vibra_exists()) {
??????? ALOGD("Vibrator usingtimed_output");
??????? use_led = false;
??? } else if (vibra_led_exists()) {
??????? ALOGD("Vibrator using LEDtrigger");
??????? use_led = true;
??? } else {
??????? ALOGE("Vibrator device does notexist. Cannot start vibrator");
??????? return -ENODEV;
??? }
??? vibrator_device_t *vibradev = calloc(1,sizeof(vibrator_device_t));
??? if (!vibradev) {
??????? ALOGE("Can not allocate memory forthe vibrator device");
??????? return -ENOMEM;
??? }
??? vibradev->common.tag =HARDWARE_DEVICE_TAG;
??? vibradev->common.module = (hw_module_t*) module;
??? vibradev->common.version =HARDWARE_DEVICE_API_VERSION(1,0);
??? vibradev->common.close = vibra_close;
??? if (use_led) {
??????? vibradev->vibrator_on =vibra_led_on;
??????? vibradev->vibrator_off =vibra_led_off;
??? } else {
??????? vibradev->vibrator_on = vibra_on;
??????? vibradev->vibrator_off = vibra_off;
??? }
??? *device = (hw_device_t *) vibradev;
??? return 0;
}
/*===========================================================================*/
/* Defaultvibrator HW module interface definition?????????????????????????? */
/*===========================================================================*/
static structhw_module_methods_t vibrator_module_methods = {
??? .open = vibra_open,
};
structhw_module_t HAL_MODULE_INFO_SYM = {
??? .tag = HARDWARE_MODULE_TAG,
??? .module_api_version = VIBRATOR_API_VERSION,
??? .hal_api_version =HARDWARE_HAL_API_VERSION,
??? .id = VIBRATOR_HARDWARE_MODULE_ID,
??? .name = "Default vibrator HAL",
??? .author = "The Android Open SourceProject",
??? .methods = &vibrator_module_methods,
};
Vibrator在frameworks/base/services/core/jni/com_android_server_VibratorService.cpp文件中加載,看看它的初始化
staticvoid vibratorInit(JNIEnv /* env */, jobject /* clazz */)
{
??? if (gVibraModule != NULL) {
??????? return;
??? }
??? int err =hw_get_module(VIBRATOR_HARDWARE_MODULE_ID, (hw_module_tconst**)&gVibraModule);
??? if (err) {
??????? ALOGE("Couldn't load %s module(%s)", VIBRATOR_HARDWARE_MODULE_ID, strerror(-err));
??? } else {
??????? if (gVibraModule) {
??????????? vibrator_open(gVibraModule,&gVibraDevice);
??????? }
??? }
}
hw_get_module(VIBRATOR_HARDWARE_MODULE_ID,
(hw_module_t const**)&gVibraModule)具體是怎樣加載到vibrator對應(yīng)的so庫呢肴焊?
通過hw_get_module()函數(shù)以VIBRATOR_HARDWARE_MODULE_ID 參數(shù)獲得camera_module_t 指針來初始化和調(diào)用VIBRATOR 我們再看hw_get_module()的實現(xiàn)
inthw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
而hw_get_module()又是通過hw_get_module_by_class():
inthw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
.................核心看......................
returnload(class_id, path, module);
}
staticint load(const char *id,const char *path,const struct hw_module_t **pHmi)
{
..........................................................................................
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path,err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
if (strcmp(id, hmi->id) != 0) {
ALOGE("load: id=%s != hmi->id=%s", id,hmi->id);
status = -EINVAL;
goto done;
}
.................................................................................
}
有l(wèi)oad函數(shù)可發(fā)現(xiàn)岩馍,它是通過dlopen()加載vibrator.so庫,通過dlsym()查詢HAL_MODULE_INFO_SYM_AS_STR全局變量的地址抖韩,通過強制指針轉(zhuǎn)換可以獲得HAL_MODULE_INFO_SYM_AS_STR變量,并檢查傳進來的id和獲得id是否一致疫铜。HAL_MODULE_INFO_SYM_AS_STR是個宏定義如下:
#define HAL_MODULE_INFO_SYM_AS_STR"HMI"
而HAL_MODULE_INFO_SYM在vibrator.c中定義如下:
structhw_module_t HAL_MODULE_INFO_SYM = {
??? .tag = HARDWARE_MODULE_TAG,
??? .module_api_version = VIBRATOR_API_VERSION,
??? .hal_api_version =HARDWARE_HAL_API_VERSION,
??? .id = VIBRATOR_HARDWARE_MODULE_ID,
??? .name = "Default vibrator HAL",
??? .author = "The Android Open SourceProject",
??? .methods = &vibrator_module_methods,
};
其關(guān)鍵在于load函數(shù)中的下面兩行代碼:
??? const char *sym =HAL_MODULE_INFO_SYM_AS_STR;
??? hmi = (struct hw_module_t *)dlsym(handle,sym);
??????在打開的.so中查找HMI符號的地址茂浮,并保存在hmi中。至此壳咕,.so中的hw_module_t已經(jīng)被成功獲取席揽,從而可以根據(jù)它獲取別的相關(guān)接口。
HAL通過hw_get_module函數(shù)獲取hw_module_t
HAL通過hw_module_t->methods->open獲取hw_device_t指針谓厘,并在此open函數(shù)中初始化hw_device_t的包裝結(jié)構(gòu)中的函數(shù)及hw_device_t中的close函數(shù)幌羞,如vibrator_device_open。
三個重要的數(shù)據(jù)結(jié)構(gòu):
?struct hw_device_t:表示硬件設(shè)備竟稳,存儲了各種硬件設(shè)備的公共屬性和方法
?struct
hw_module_t: 可用hw_get_module進行加載的module
?struct
hw_module_methods_t: 用于定義操作設(shè)備的方法属桦,其中只定義了一個打開設(shè)備的方法open.
Vibrator HAL的代碼看起來確實是有點繞熊痴。好了我們不太細究這個細節(jié),領(lǐng)會其思想才是最關(guān)鍵的聂宾,就是定義自己的vibrator_device_t果善,定義自己的HAL_MODULE_INFO_SYM,定義自己的vibrator id VIBRATOR_HARDWARE_MODULE_ID系谐,實現(xiàn)vibrator的具體功能巾陕,vibrator
on,vibrator off纪他,這樣vibrator就可以運轉(zhuǎn)起來鄙煤,中間,hal的加載茶袒,需找特定的so庫這些HAL框架都已經(jīng)做好了梯刚。而且只有Vibrator JNI才可以加載自己,只能是Vibrator的Service才可以使用弹谁,避免了上層多個進程可以隨意的加載Vibrator so庫的混亂乾巧,保證程序的安全,資源浪費预愤。
為何不把Android HAL做到內(nèi)核中去沟于?
Linux是由自己的協(xié)議的,使用Linux植康,修改Linux相關(guān)的代碼都需要遵守協(xié)議旷太,要公開代碼,所以Android就在Linux之上搞了一個HAL層销睁,各個廠商可以自由實現(xiàn)自己的邏輯供璧,不用開源,保護自己的技術(shù)成果冻记。
Android UI上的一些異步設(shè)計
異步機制Handler
寫UI程序的時候常常會碰到異步處理睡毒,非常棘手,需要一套非常好的異步機制來完成冗栗,看看Handler
Message:消息演顾,由MessageQueue統(tǒng)一隊列,然后交由Handler處理隅居。
MessageQueue:消息隊列钠至,用來存放Handler發(fā)送過來的Message棉钧,并且按照先入先出的規(guī)則執(zhí)行涕蚤。
Handler:處理者宪卿,負責發(fā)送和處理Message愧捕。
Looper:消息輪詢器奢驯,不斷的從MessageQqueue中抽取Message并執(zhí)行次绘。
如下圖:
Handler的大致結(jié)構(gòu)是這樣管跺,Handler禾进,Message艇拍,MessageQueue這些都比較直接明了卸夕,我們看看消息輪詢器Looper是如何工作的快集。它的具體工作是在native層完成的。
Looper的native層核心是一個管道院溺,當獲取MessageQueue下一個消息時會執(zhí)行epoll_wait的方式等待事件發(fā)生(管道中有數(shù)據(jù)寫入)覆获,當執(zhí)行sendMessage之后消息隊列有了數(shù)據(jù)之后會觸發(fā)喚醒nativeWake,就向管道中寫入1,這樣epoll_wait等待的事件發(fā)生返回馒胆,MessageQueue就可以拿到下一個消息祝迂,從而完成了消息的循環(huán)型雳。(詳細過程可以追蹤一下Android的源碼)沿量,這就是教科書上經(jīng)典的讀寫者模式朴则。
Android主要用Handler來完成異步模式乌妒,將一些耗時過程移入到子線程中完成。
多線程異步AsyncTask
看看這個抽象類里面有哪些接口
public abstractclass AsyncTask {
onPreExecute()
//此方法會在后臺任務(wù)執(zhí)行前被調(diào)用拴魄,用于進行一些準備工作
doInBackground(Params...
params) //此方法中定義要執(zhí)行的后臺任務(wù),在這個方法中可以調(diào)用publishProgress來更新任務(wù)進度(publishProgress內(nèi)部會調(diào)用onProgressUpdate方法)
onProgressUpdate(Progress...
values) //由publishProgress內(nèi)部調(diào)用顶捷,表示任務(wù)進度更新
onPostExecute(Result
result) //后臺任務(wù)執(zhí)行完畢后服赎,此方法會被調(diào)用,參數(shù)即為后臺任務(wù)的返回結(jié)果
onCancelled() //此方法會在后臺任務(wù)被取消時被調(diào)用
調(diào)用AsyncTask缺厉,啟動執(zhí)行是用execute()接口
??? @MainThread
??? public final AsyncTask execute(Params... params) {
??????? returnexecuteOnExecutor(sDefaultExecutor, params);
??? }
我們再來看看executeOnExecutor具體怎么執(zhí)行的
??? @MainThread
??? public final AsyncTask executeOnExecutor(Executor exec,
??????????? Params... params) {
??????? if (mStatus != Status.PENDING) {
??????????? switch (mStatus) {
??????????????? case RUNNING:
??????????????????? throw newIllegalStateException("Cannot execute task:"
??????????????????????????? + " the taskis already running.");
??????????????? case FINISHED:
??????????????????? throw newIllegalStateException("Cannot execute task:"
??????????????????????????? + " the taskhas already been executed "
??????????????????????????? + "(a task canbe executed only once)");
??????????? }
??????? }
??????? mStatus = Status.RUNNING;
??????? onPreExecute();
??????? mWorker.mParams = params;
??????? exec.execute(mFuture);
? ??????return this;
??? }
executeOnExecutor先是執(zhí)行onPreExecute() 已就是上面提到的接口中說的在執(zhí)行前的一些準備工作命爬,接著將參數(shù)傳給 mWorker. Mparams饲宛,mWorker是一個WorkerRunnable,用于和Future练链,Executor來完成異步返回的媒鼓。
看看他們具體怎樣執(zhí)行
??? public AsyncTask(@Nullable LoopercallbackLooper) {
??????? mHandler = callbackLooper == null ||callbackLooper == Looper.getMainLooper()
??????????? ? getMainHandler()
??????????? : new Handler(callbackLooper);
??????? mWorker = new WorkerRunnable() {
??????????? public Result call() throws Exception{
??????????????? mTaskInvoked.set(true);
??????????????? Result result = null;
??????????????? try {
???????????????????Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
??????????????????? //noinspection unchecked
??????????????????? result =doInBackground(mParams);
???????????????????Binder.flushPendingCommands();
??????????????? } catch (Throwable tr) {
??????????????????? mCancelled.set(true);
??????????????????? throw tr;
??????????????? } finally {
??????????????????? postResult(result);
??????????????? }
??????????????? return result;
??????????? }
??????? };
??????? mFuture = newFutureTask(mWorker) {
??????????? @Override
??????????? protected void done() {
??????????????? try {
??????????????????? postResultIfNotInvoked(get());
??????????????? } catch (InterruptedExceptione) {
??????????????????? android.util.Log.w(LOG_TAG,e);
??????????????? } catch (ExecutionException e){
??????????????????? throw newRuntimeException("An error occurred while executing doInBackground()",
??????????????????????????? e.getCause());
??????????????? } catch (CancellationExceptione) {
???????????????????postResultIfNotInvoked(null);
??????????????? }
??????????? }
??????? };
??? }
在WorkerRunnable里面就doInBackground(), 就是接口里面的執(zhí)行后臺任務(wù)暂氯。執(zhí)行完之后痴施,需要將結(jié)果Result投遞到需要它的地方动遭。
??? private Result postResult(Result result) {
???????@SuppressWarnings("unchecked")
??????? Message message =getHandler().obtainMessage(MESSAGE_POST_RESULT,
??????????????? newAsyncTaskResult(this, result));
??????? message.sendToTarget();
??????? return result;
??? }
其實就是發(fā)一個消息到對應(yīng)線程的Handler去
??? private void finish(Result result) {
??????? if (isCancelled()) {
??????????? onCancelled(result);
??????? } else {
??????????? onPostExecute(result);
??????? }
???? ???mStatus = Status.FINISHED;
??? }
最后執(zhí)行到onPostExecute里面去厘惦,也就是接口中的結(jié)果更新。
其實AsyncTask是Android中的一個多線程異步羡玛,Android有Handler和Thread稼稿,為何還要這呢?其實AsyncTask幫我們實現(xiàn)了一個線程池是越,是一個串行執(zhí)行的。開始就初始化了一部分縣城天梧,免去線程的經(jīng)常開啟呢岗,銷毀的開銷。它同時提供了異步返回的能力挫酿,將耗時任務(wù)放入后臺子線程早龟,一個線程執(zhí)行完畢后再去取下一個線程,當后臺任務(wù)結(jié)束后將結(jié)果返回來更新翘悉。開發(fā)者只需要按照接口說明,耗時任務(wù)放到doInBackground制市,更新結(jié)果放入onPostExecute祥楣,不用管線程责鳍,不用管并發(fā)導致的問題历葛。
Android對Java集合類改進
ArrayMap
SpraseArray
先不管這兩個類,看看原生的Android集合類咒程,數(shù)組,鏈表卖宠,樹,哈希表刺洒。這些有和差異,有何優(yōu)劣因俐。
數(shù)組,存儲效率高澳眷,基本沒有附加的空間钳踊,沒有浪費缴罗,查詢的時候效率高,只要知道下標就可以拿到數(shù)據(jù)。但是要刪除添加的時候航罗,為了保證數(shù)據(jù)的有序,需要挪動數(shù)據(jù)位置复亏,保證數(shù)據(jù)的有序,會導致效率的下降耕突。
鏈表,由于鏈表需要存儲上一份數(shù)據(jù)上祈,下一份數(shù)據(jù)的地址,所以會有空間浪費,在查詢的時候要依據(jù)當前指針位置一個一個地移動掉蔬,效率偏低女轿。但是在增加刪除時傅寡,只需要改掉當前數(shù)據(jù)的前后指針位置就可以捂贿,刪除,添加效率是很高的屯耸。
樹,用得比較多的就是紅黑樹和完全二叉樹了多矮。這兩種數(shù)據(jù)結(jié)構(gòu)用得最多的是紅黑樹,它是一種近似的完全二叉樹患雏。
樹的查詢效率是很高的,完全是對數(shù)級別,也就是常數(shù)級別吓肋,非撤粑瑁快。但是在有數(shù)據(jù)添加和刪除時篙顺,需要旋轉(zhuǎn),保證樹是平衡的,會有一定的性能開銷。所以它的增加刪除效率不是很好。
哈希表普气,其實Java的哈希表是用數(shù)組加鏈表的方式實現(xiàn)的,數(shù)組可以實現(xiàn)數(shù)據(jù)的高效存儲仔沿,當對應(yīng)數(shù)組位置的數(shù)據(jù)存在沖突時,就用鏈表來存儲,一般鏈表上的數(shù)據(jù)不會太多奴艾,保證存儲效率像啼。之前說了鏈表在查找的時候效率偏低。而且存儲效率不高,因為存有下一份數(shù)據(jù)的地址振诬。哈希表的數(shù)組可以避免因為數(shù)據(jù)的刪除,增加而改動位置導致的性能開銷,因為它是根據(jù)哈希算法來存取的放闺,它是常數(shù)級別的,直接存,直接取艳悔。而鏈表是用來處理哈希沖突很钓,只是查詢效率一般,刪除董栽,增加效率很好码倦。做兩個極端假設(shè),哈希如果沒有沖突锭碳,就完全是一個數(shù)組,當然擒抛,這個申請的內(nèi)存空間會很大推汽。如果沖突太多补疑,這時候近似是一個鏈表了,查詢的效率就會很差了歹撒。
Android SpraseArray ArrayMap
我在看了哈希表的具體實現(xiàn)之后就提出過莲组,用數(shù)組+二叉樹的形式來做哈希表,數(shù)組可以提高存儲效率暖夭,而在查找的時候锹杈,在二叉樹上,它的查找效率是對數(shù)級別迈着,效率也很高竭望。SpraseArray,ArrayMap就是這樣思路實現(xiàn)的裕菠,它的key是個有序數(shù)組咬清,當要查詢時,通過折半查找找到(類似二叉樹)奴潘,而value是通過哈希算法存取的旧烧,效率也是常數(shù)級別,同時沒有空間浪費画髓。這樣可以做到查詢掘剪,添加,刪除效率的同時提高雀扶。
代碼控制和減少耦合
我們看看Spring里面的控制反轉(zhuǎn)和依賴注入
通過實例理解控制反轉(zhuǎn)的概念
???賀歲大片在中國已經(jīng)形成了一個傳統(tǒng),每到年底總有多部賀歲大片紛至沓來讓人應(yīng)接不暇肆汹。在所有賀歲大片中愚墓,張之亮的《墨攻》算是比較出彩的一部。該片講述了戰(zhàn)國時期墨家人革離幫助梁國反抗趙國侵略的個人英雄主義故事昂勉,恢宏壯闊浪册、渾雄凝重的歷史場面相當震撼。其中有一個場景:當劉德華所飾演的墨者革離到達梁國都城下岗照,城上梁國守軍問到:“來者何人村象?”劉德華回答:“墨者革離!”我們不妨通過一個Java類為這個“城門叩問”的場景進行編劇攒至,并借此理解IoC的概念:
代碼清單3-1?MoAttack:通過演員安排劇本
public class MoAttack {?
??public void cityGateAsk(){?
???????//①演員直接侵入劇本?
??????LiuDeHua ldh = new LiuDeHua();?
??????ldh.responseAsk("墨者革離厚者!");?
??}?
}?
?? 我們會發(fā)現(xiàn)以上劇本在①處,作為具體角色飾演者的劉德華直接侵入到劇本中迫吐,使劇本和演員直接耦合在一起(圖3-1)库菲。
?? 一個明智的編劇在劇情創(chuàng)作時應(yīng)圍繞故事的角色進行,而不應(yīng)考慮角色的具體飾演者志膀,這樣才可能在劇本投拍時自由地遴選任何適合的演員熙宇,而非綁定在劉德華一人身上鳖擒。通過以上的分析,我們知道需要為該劇本主人公革離定義一個接口:
代碼清單3-2?MoAttack:引入劇本角色
public class MoAttack {?
??public void cityGateAsk()?
??{?
???????//①引入革離角色接口?
??????GeLi geli = new LiuDeHua();??
???????//②通過接口開展劇情?
??????geli.responseAsk("墨者革離烫止!");???
??}?
}?
?? 在①處引入了劇本的角色——革離蒋荚,劇本的情節(jié)通過角色展開,在拍攝時角色由演員飾演馆蠕,如②處所示期升。因此墨攻、革離荆几、劉德華三者的類圖關(guān)系如圖 3 2所示:
?? 可是吓妆,從圖3 2中,我們可以看出MoAttack同時依賴于GeLi接口和LiuDeHua類吨铸,并沒有達到我們所期望的劇本僅依賴于角色的目的行拢。但是角色最終必須通過具體的演員才能完成拍攝,如何讓LiuDeHua和劇本無關(guān)而又能完成GeLi的具體動作呢诞吱?當然是在影片投拍時舟奠,導演將LiuDeHua安排在GeLi的角色上,導演將劇本房维、角色沼瘫、飾演者裝配起來(圖3-3)。
通過引入導演咙俩,使劇本和具體飾演者解耦了耿戚。對應(yīng)到軟件中,導演像是一個裝配器阿趁,安排演員表演具體的角色膜蛔。
?? 現(xiàn)在我們可以反過來講解IoC的概念了。IoC(Inverse of Control)的字面意思是控制反轉(zhuǎn)脖阵,它包括兩個內(nèi)容:
其一是控制
其二是反轉(zhuǎn)
? 那到底是什么東西的“控制”被“反轉(zhuǎn)”了呢皂股?對應(yīng)到前面的例子,“控制”是指選擇GeLi角色扮演者的控制權(quán)命黔;“反轉(zhuǎn)”是指這種控制權(quán)從《墨攻》劇本中移除呜呐,轉(zhuǎn)交到導演的手中。對于軟件來說悍募,即是某一接口具體實現(xiàn)類的選擇控制權(quán)從調(diào)用類中移除蘑辑,轉(zhuǎn)交給第三方?jīng)Q定。
?? 因為IoC確實不夠開門見山坠宴,因此業(yè)界曾進行了廣泛的討論以躯,最終軟件界的泰斗級人物Martin
Fowler提出了DI(依賴注入:Dependency Injection)的概念用以代替IoC,即讓調(diào)用類對某一接口實現(xiàn)類的依賴關(guān)系由第三方(容器或協(xié)作類)注入,以移除調(diào)用類對某一接口實現(xiàn)類的依賴忧设〉蟊辏“依賴注入”這個名詞顯然比“控制反轉(zhuǎn)”直接明了、易于理解址晕。
IoC的類型
? 從注入方法上看膀懈,主要可以劃分為三種類型:構(gòu)造函數(shù)注入、屬性注入和接口注入谨垃。Spring支持構(gòu)造函數(shù)注入和屬性注入启搂。下面我們繼續(xù)使用以上的例子說明這三種注入方法的區(qū)別。
構(gòu)造函數(shù)注入
在構(gòu)造函數(shù)注入中刘陶,我們通過調(diào)用類的構(gòu)造函數(shù)胳赌,將接口實現(xiàn)類通過構(gòu)造函數(shù)變量傳入,如代碼清單3-3所示:
代碼清單3-3?MoAttack:通過構(gòu)造函數(shù)注入革離扮演者
public class MoAttack {?
??private GeLi geli;?
??//①注入革離的具體扮演者?
??public MoAttack(GeLi geli){??
??????this.geli = geli;?
??}?
???public void cityGateAsk(){?
??????geli.responseAsk("墨者革離匙隔!");?
??}?
}?
???MoAttack的構(gòu)造函數(shù)不關(guān)心具體是誰扮演革離這個角色疑苫,只要在①處傳入的扮演者按劇本要求完成相應(yīng)的表演即可。角色的具體扮演者由導演來安排纷责,如代碼清單3-4所示:
代碼清單3-4?Director:通過構(gòu)造函數(shù)注入革離扮演者
public class Director {?
??public void direct(){?
???????//①指定角色的扮演者?
??????GeLi geli = new LiuDeHua();???
???????//②注入具體扮演者到劇本中?
??????MoAttack moAttack = newMoAttack(geli);??
??????moAttack.cityGateAsk();?
??}?
}?
?? 在①處捍掺,導演安排劉德華飾演革離的角色,并在②處再膳,將劉德華“注入”到墨攻的劇本中挺勿,然后開始“城門叩問”劇情的演出工作。
屬性注入
?? 有時喂柒,導演會發(fā)現(xiàn)不瓶,雖然革離是影片《墨攻》的第一主角,但并非每個場景都需要革離的出現(xiàn)灾杰,在這種情況下通過構(gòu)造函數(shù)注入相當于每時每刻都在革離的飾演者在場蚊丐,可見并不妥當,這時可以考慮使用屬性注入吭露。屬性注入可以有選擇地通過Setter方法完成調(diào)用類所需依賴的注入吠撮,更加靈活方便:
代碼清單3-5?MoAttack:通過Setter方法注入革離扮演者
public class MoAttack {?
???private GeLi geli;?
????//①屬性注入方法?
???public void setGeli(GeLi geli) {???
???????this.geli = geli;?
???}?
???public void cityGateAsk() {?
???????geli.responseAsk("墨者革離");?
???}?
}?
??MoAttack在①處為geli屬性提供一個Setter方法尊惰,以便讓導演在需要時注入geli的具體扮演者讲竿。
代碼清單3-6?Director:通過Setter方法注入革離扮演者
public class Director {?
??public void direct(){?
??????GeLi geli = new LiuDeHua();?
??????MoAttack moAttack = new MoAttack();?
???????//①調(diào)用屬性Setter方法注入?
??????moAttack.setGeli(geli);??
??????moAttack.cityGateAsk();?
??}?
}?
?? 和通過構(gòu)造函數(shù)注入革離扮演者不同,在實例化MoAttack劇本時弄屡,并未指定任何扮演者题禀,而是在實例化MoAttack后,在需要革離出場時膀捷,才調(diào)用其setGeli()方法注入扮演者迈嘹。按照類似的方式,我們還可以分別為劇本中其他諸如梁王、巷淹中等角色提供注入的Setter方法秀仲,這樣融痛,導演就可以根據(jù)所拍劇段的不同,注入相應(yīng)的角色了神僵。
接口注入
?? 將調(diào)用類所有依賴注入的方法抽取到一個接口中雁刷,調(diào)用類通過實現(xiàn)該接口提供相應(yīng)的注入方法。為了采取接口注入的方式保礼,必須先聲明一個ActorArrangable接口:
public interface ActorArrangable {?
?? voidinjectGeli(GeLi geli);?
}?
?? 然后沛励,MoAttack實現(xiàn)ActorArrangable接口提供具體的實現(xiàn):
代碼清單3-7?MoAttack:通過接口方法注入革離扮演者
public class MoAttack implementsActorArrangable {?
???private GeLi geli;?
????//①實現(xiàn)接口方法?
???public void injectGeli (GeLi geli) {???
?? ?????this.geli = geli;????????
???}?
???public void cityGateAsk() {?
???????geli.responseAsk("墨者革離");?
???}?
}?
????Director通過ActorArrangable的injectGeli()方法完成扮演者的注入工作。
代碼清單3-8?Director:通過接口方法注入革離扮演者
public class Director {?
??public void direct(){?
??????GeLi geli = new LiuDeHua();?
??????MoAttack moAttack = new MoAttack();?
??????moAttack. injectGeli (geli);?
??????moAttack.cityGateAsk();?
??}?
}?
???由于通過接口注入需要額外聲明一個接口炮障,增加了類的數(shù)目目派,而且它的效果和屬性注入并無本質(zhì)區(qū)別,因此我們不提倡采用這種方式胁赢。
一份好的代碼企蹭,首先是分離良好的,客戶端服務(wù)端分離徘键,代碼要有控制部分练对,就如同拍電影一樣,導演吹害,演員和劇本是解藕的螟凭,只要合適誰都可以來演,誰都可以做導演它呀,加入控制部分導演拍電影螺男,理解角色,選擇演員纵穿,控制劇情下隧。不會因為換導演,換演員導致劇本劇情跟著改動谓媒。directMovie()算是整個代碼的控制中心淆院,可以稱之為導演部,它能夠選定導演句惯,演員土辩,控制劇情,拍攝節(jié)奏抢野。這一切的一切是因為拷淘,劇本,角色指孤,導演启涯,角色分離的很好才可以做到贬堵。
很多時候我們談到繼承,組合结洼,組合優(yōu)先于繼承黎做。其實,我們也可以多用用依賴注入松忍,減少代碼的耦合引几,這些邏輯是在代碼運行時進行控制。一般挽铁,控制反轉(zhuǎn)和依賴注入一一起使用的伟桅,解決掉代碼的耦合,更好的控制代碼邏輯叽掘。
???????服務(wù)端與客戶端
???????我們寫代碼的時候楣铁,一定要隨時有服務(wù)端與客戶端的思想「猓客戶端不僅僅是網(wǎng)絡(luò)程序訪問端盖腕,在寫代碼的時候main函數(shù)的地方,或者發(fā)起調(diào)用的地方我們都稱之為客戶端浓镜,而具體實現(xiàn)的地方溃列,被調(diào)用的地方我們都稱之為服務(wù)端√叛Γ客戶端與服務(wù)端要盡量分離听隐,抽離出服務(wù)端的接口作為調(diào)用之用,因為客戶端可能會因為需求經(jīng)常變動哄啄,常常改代碼雅任,而服務(wù)端的代碼一旦固定就不輕易改動,所以要抽離出接口咨跌,接口是不變的沪么,服務(wù)端也不用改動。避免客戶端的改動而導致整個服務(wù)端也跟著改動锌半∏莩担客戶端是具體需求的地方,經(jīng)常改動刊殉,所以盡量要抽離出一個控制部分出來殉摔,控制部分可以控制整個的功能邏輯,修改需求冗澈,甚至刪掉需求慎陵,有了控制部分我們就可以做到對需求的整體把控址愿,改動也可以做到最小。
寫在最后
作為一名工程師难审,我們按照別人設(shè)計好的框架去寫代碼,而不去想想捌归,或者看看別人是怎樣設(shè)計的肛响,久而久之會對這些框架形成依賴,變得懶惰惜索,甚至不會去思考了特笋。框架就是讓開發(fā)工程師做傻瓜式編程巾兆,真的會讓人變傻猎物。
所以,我們還是要突破這種限制角塑,跳出這個包圍蔫磨,看看框架是怎樣設(shè)計的,有什么考量圃伶。這樣一個工程師才會進階堤如。