深入理解Android組件間通信機(jī)制對(duì)面向?qū)ο筇匦缘挠绊懺斀?/h1>
更新時(shí)間:2013年05月21日 11:34:59 作者:
本篇文章是對(duì)Android組件間通信機(jī)制對(duì)面向?qū)ο筇匦缘挠绊戇M(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
組件的特點(diǎn)
對(duì)于Android的四大組件Activity, Service, ContentProvider和Service,不能有Setter和Getter,也不能給組件添加接口。原因是組件都是給系統(tǒng)框架調(diào)用的,開發(fā)者只能實(shí)現(xiàn)其規(guī)定的回調(diào)接口,組件的創(chuàng)建與銷毀都是由系統(tǒng)框架控制的,開發(fā)者不能強(qiáng)行干預(yù),更沒有辦法獲取組件的對(duì)象。比如Activity,Service,BroadcastReceiver,你沒有辦法去創(chuàng)建一個(gè)Activity,Service或BroadcastReceiver,然后像使用其他類那樣的調(diào)用其上的接口與其通信,用Setters和Getters改變屬性等等。這也決定了,組件之間通信只能用系統(tǒng)支持的Intent。而Intent只能傳遞基本數(shù)據(jù)類型和Uri等一些常見的數(shù)據(jù)類型。Intent只支持傳遞內(nèi)置類型和一些限制類型,這就導(dǎo)致了組件之間的數(shù)據(jù)傳遞必須都是基本類型,所以枚舉類型無(wú)法使用。
多態(tài)無(wú)法實(shí)現(xiàn)
比如你有一個(gè)Service用于在后臺(tái)執(zhí)行UI中發(fā)來(lái)的請(qǐng)求,這些請(qǐng)求有些是做數(shù)據(jù)請(qǐng)求,有些是做數(shù)據(jù)分析,等等。這里可以用多態(tài),定義一個(gè)統(tǒng)一的Transaction類,然后再為每種特定的Transaction類型,Transaction中統(tǒng)一接口process()用于實(shí)際的處理,理想的情況是,Service接收一個(gè)Transaction對(duì)象,然后調(diào)用其process(),沒有必要知道具體的類型,UI創(chuàng)建具體的一個(gè)類型對(duì)象然后交由Service來(lái)處理。但是這在Android當(dāng)中是無(wú)法實(shí)現(xiàn)的,因?yàn)镮ntent通信機(jī)制所限,因?yàn)樗荒苤苯觽鬟fTransaction對(duì)象。所以,Service必須要知道具體的類型。原生應(yīng)用Mms中就有如此的現(xiàn)象,在transaction包中TransactionService是處理服務(wù),UI發(fā)送到Service的只是區(qū)別不同Transaction的Id(一個(gè)整數(shù)),Service查看不同的Id創(chuàng)建不同的Transaction對(duì)象,然后調(diào)用process()對(duì)其處理。
建議:自己實(shí)現(xiàn)一個(gè)類似Service的服務(wù)類,在其內(nèi)用Handler,Thread和Looper讓其長(zhǎng)時(shí)間運(yùn)行。這樣就沒有組件間通信的限制,你可以像正常使用Java對(duì)象那樣來(lái)使用這個(gè)服務(wù)類,向其傳遞自定義的處理請(qǐng)求:
復(fù)制代碼 代碼如下:
public class TransactionServer extends HandlerThread {
public TransactionServer() {
start();
}
public void onLooperPrepared() {
mHandler = new Handler(getLooper(), new Handler.Callback() {
@Override
public void handleMessage(Message msg) {
Transaction request = (Transaction) msg.obj;
request.process();
}
}
}
public void execute(Transaction request) {
if (mHandler == null) {
return;
}
Message msg = Message.obtain();
msg.obj = request;
mHandler.sendMessage(msg);
}
}
在Activity中就可以創(chuàng)建此Server的對(duì)象,然后使用它:
復(fù)制代碼 代碼如下:
TransactionServer server = new TransactionServer();
Transaction updateRequest = new UpdateTransaction();
server.execute(updateRequest);
另外,用AIDL與Service通信,雖可以獲取Service的對(duì)象引用,可以直接調(diào)用Service的方法,但這個(gè)也有限制,對(duì)于AIDL的接口,所有的參數(shù)和返回類型都必須是基本數(shù)據(jù)
據(jù)類型,不能有對(duì)象。原因也好理解,因?yàn)锳IDL也是要通過(guò)IPC的,即便Service與Activity在同一個(gè)進(jìn)程內(nèi),所以本質(zhì)上它與Intent通信機(jī)制無(wú)區(qū)別。
封裝性被破壞
組件間的通信機(jī)制決定了Android的封裝性,先來(lái)看一些實(shí)例:
復(fù)制代碼 代碼如下:
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(uri, "text/html");
startActivity(i);
這在Android當(dāng)中是再常見不過(guò)的了。
Intent和IntentFilter的使用讓封裝性受到大大的破壞,因?yàn)槟惚仨毎炎执瑓?shù)等直接寫入到Intent或IntentFilter當(dāng)中。例如:
復(fù)制代碼 代碼如下:
Intent i = new Intent("android.contacts.action.MULTIPLECONTACTSLISTS");
i.setExtra("request_type", 3);
<intent-filter>
<action android:name="android.contacts.action.MULTIPLECONTACTSLISTS" />
</intent-filter>
當(dāng)然,可以再好一點(diǎn),就是:
Intent i = new Intent(Contacts.ACTION_GET_CONTACTS);
但是在AndroidManifest中的IntentFilter還是要寫字串常量(Literal Strings),這樣就有一個(gè)問(wèn)題,就是即使你寫錯(cuò)了,編譯器不會(huì)提醒你,直到你運(yùn)行的時(shí)候才會(huì)發(fā)現(xiàn)程序不正常工作,你調(diào)試啊,調(diào)試,最終發(fā)現(xiàn)是字串寫錯(cuò)了。或者,activity的name寫錯(cuò)了,編譯器同樣不會(huì)提醒你,但你運(yùn)行時(shí)卻因找不到類而報(bào)出RuntimeException。
建議:盡可能在所有作用域內(nèi)定義常量,以讓組件間接口保持一致性,特別是對(duì)于字串常量,一定要在二個(gè)組件都可見的作用域內(nèi)定義常量,否則將會(huì)有維護(hù)的麻煩。
例子:intent.putExtra("request_type", 3); --> intent.putExtra(TargetActivity.REQUEST_TYPE, TargetActivity.NO_BACKGROUND);否則,特別是當(dāng)目標(biāo)組件不在同一個(gè)包內(nèi),或距離很遠(yuǎn)時(shí),如果另一方改了,編譯時(shí)不會(huì)有錯(cuò),但程序不會(huì)正常工作,從而引發(fā)難以發(fā)覺的Bug。
無(wú)法在組件間傳遞自定義的數(shù)據(jù)結(jié)構(gòu)
如前面所述,因?yàn)闊o(wú)法獲取組件的對(duì)象的引用,所以你無(wú)法向其設(shè)置數(shù)據(jù),當(dāng)然,可以用靜態(tài)方法,但是不優(yōu)雅且難以維護(hù)(對(duì)于Service倒是可以通過(guò)AIDL方式獲取Service對(duì)象的引用,然后調(diào)用其方法來(lái)添加數(shù)據(jù))。又因?yàn)镮ntent只能攜帶基本的數(shù)據(jù)類型,所以對(duì)于自定義的數(shù)據(jù)結(jié)構(gòu)想要在組件間傳遞就特別的麻煩。當(dāng)然你可以以讓數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)Parcleable接口,但是用起來(lái)也相當(dāng)?shù)穆闊?BR>建議:
1. 盡可能的避免使用自定義數(shù)據(jù)結(jié)構(gòu),特別是除了Setters和Getters以外不具有其他行為的數(shù)據(jù)結(jié)構(gòu)
對(duì)于結(jié)構(gòu)化的數(shù)據(jù),為其定義ContentProvider,把數(shù)據(jù)寫入SQLite數(shù)據(jù)庫(kù),這樣數(shù)據(jù)庫(kù)表中的每行數(shù)據(jù)都相當(dāng)于是一個(gè)數(shù)據(jù)對(duì)象,每一列都是其屬性。因?yàn)锳ndroid的組件與SQLite數(shù)據(jù)庫(kù)的粘性很大,每個(gè)組件都可以很方便的從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),再通過(guò)Cursor等工具來(lái)操作數(shù)據(jù)。最最重要的是這很方便在組件間傳遞數(shù)據(jù),因數(shù)ContentProvider的訪問(wèn)都是通過(guò)Uri來(lái)實(shí)現(xiàn)的,而Uri又可以與Intent無(wú)縫接合,Uri可以方便的放入和從Intent中取出,每個(gè)組件又都可以直接訪問(wèn)ContentProvider用Uri讀取數(shù)據(jù),從而就可以實(shí)現(xiàn)組件間的無(wú)縫數(shù)據(jù)傳遞。
2. 盡可能的不要在組件之間傳遞數(shù)據(jù)
不要用太多的Activity,Service也能免則免,Activity+線程可能解決大部分問(wèn)題,當(dāng)然了,線程也不是那么好用的,特別是在Android里面。
3. 避免在組件之間傳遞自定義數(shù)據(jù)結(jié)構(gòu)
如前所述,組件之間最好直接傳遞基本數(shù)據(jù)和Intent支持的數(shù)據(jù)類型。對(duì)于自定義的數(shù)據(jù)結(jié)構(gòu),要么不定義數(shù)據(jù)結(jié)構(gòu),要么不要在組件間傳遞,否則會(huì)很麻煩,雖然可能以實(shí)現(xiàn)Parcelable接口,但是效率和操作的方便性上都會(huì)大打折扣。
關(guān)于枚舉和整數(shù)集
先前一篇<Android開發(fā)筆記之:用Enum(枚舉類型)取代整數(shù)集的應(yīng)用詳解>曾說(shuō)要盡量使用枚舉(enum)代替整數(shù)集(ints),而且很多編程書籍(Effective Java)也建議用枚舉代替整數(shù)集,這其中的好處就是降低出錯(cuò)率,把運(yùn)行時(shí)的檢查可以放到編譯時(shí),因?yàn)檎麛?shù)的范圍較大,你可傳遞任意的整數(shù),直到運(yùn)行時(shí)才會(huì)檢測(cè)或產(chǎn)生問(wèn)題,但是枚舉會(huì)在編譯時(shí)檢查類型,如果不是合法的枚舉,編譯器會(huì)報(bào)怨。
但是我們可以看到,在Android中的情況卻很差,Android中大量的使用了整數(shù)集,系統(tǒng)定義了大量的整數(shù)集,很多參數(shù)也都是整數(shù),雖然正確的方法都是向這些API傳遞其所定義的整數(shù)常量,但是你如果傳個(gè)Integer.MAX_VALUE或Integer.MIN_VALUE,起碼在編譯時(shí)不會(huì)出問(wèn)題。
既然這不是一個(gè)好的編程規(guī)范,為什么Android中還要大量的使用整數(shù)集呢?原因就在于組件間通信,組件之間要傳遞參數(shù),但是Intent又只能放入基本數(shù)據(jù)類型,也就是說(shuō)如果使用枚舉,那么將無(wú)法用Intent傳遞給其他的組件,因?yàn)槊杜e轉(zhuǎn)為整數(shù)很容易,但反過(guò)來(lái)整數(shù)轉(zhuǎn)成枚舉就不是那么容易了。
所以,如果你的常量不需要在組件間來(lái)回的傳遞,那么最好定義成為枚舉,否則,只能用整數(shù)集了。
關(guān)于組件一般的設(shè)計(jì)原則
1. 不要用組件實(shí)現(xiàn)某些接口,比如點(diǎn)擊接口,等等
因?yàn)榻M件是一個(gè)開銷非常巨大的對(duì)象,組件的繼承層次也非常的深,用組件實(shí)現(xiàn)接口,傳遞給調(diào)用者,就相當(dāng)于用一個(gè)火車去運(yùn)送一個(gè)小老鼠一樣,給了別人一個(gè)相當(dāng)大的對(duì)象,但是僅有一個(gè)或二個(gè)方法是別人需要的。特別是對(duì)于Activity,不要去實(shí)現(xiàn)一些公共的接口比如View.OnClickListener,除了前面的原因以外,另外一個(gè)就是你的onClick必須用條件來(lái)區(qū)分點(diǎn)擊的是哪個(gè)UI元素,這很難維護(hù),還有一個(gè)原因就是Activity的對(duì)象不是很穩(wěn)定,因?yàn)橄到y(tǒng)的某些事件如轉(zhuǎn)屏,語(yǔ)言切換等等會(huì)把Activity殺死并重新創(chuàng)建一個(gè)實(shí)例,所以有可能會(huì)引發(fā)問(wèn)題,雖然看起來(lái)Activity還在,但是并不同一個(gè)實(shí)例,如果某些東西與具體實(shí)例相關(guān),就會(huì)引發(fā)問(wèn)題,要么程序不正常工作,要么有RuntimeException。還有可能引發(fā)內(nèi)存泄漏,因?yàn)樗徒o使用者的接口對(duì)象都是Activity的實(shí)例引用,一旦某個(gè)引用超過(guò)Activity的生命周期,就會(huì)造成內(nèi)存泄漏。
推薦的做法是用匿名內(nèi)部類來(lái)實(shí)現(xiàn)接口,如果其他地方需要對(duì)此接口對(duì)象的操作,可以聲明一個(gè)成員變量或者一個(gè)內(nèi)部類,這樣也方便Activity來(lái)控制,以保證所有東西都生存在Activity的生存周期之內(nèi)。
2. 少用Service
組件Service并沒有傳說(shuō)中的好用,而且它還會(huì)讓你的程序退出頁(yè)面后仍然在后臺(tái)跑,占系統(tǒng)資源不說(shuō),還會(huì)被罵(看看這些文章吧),因?yàn)镾ervice的生命周期是由系統(tǒng)來(lái)控制,我們無(wú)法干預(yù),即使你確切的知道某些時(shí)候你已經(jīng)完全不用它了。用Activity和線程就可以完成絕大多數(shù)操作,而且你還能做到讓所有線程都在Activity的控制之內(nèi),讓它們都活在Activity的生命周期之內(nèi)。另外的原因就是,因?yàn)榫€程都屬于自建的類,或者普通的Java類,可以應(yīng)用面向?qū)ο?,因?yàn)闆]有了組件通信的限制。
3. 利用ContentProvider來(lái)做復(fù)雜數(shù)據(jù)結(jié)構(gòu)的通信工具
ContentProvider和SQLiteDatabase存儲(chǔ)的就是結(jié)構(gòu)化數(shù)據(jù),相當(dāng)于一個(gè)數(shù)據(jù)結(jié)構(gòu),它的引用就是它的Uri,任何組件通過(guò)Uri就可獲得此數(shù)據(jù)結(jié)構(gòu)。它有如下優(yōu)點(diǎn):
1. 可以方便的在組件間傳遞
因?yàn)閿?shù)據(jù)實(shí)際是在數(shù)據(jù)庫(kù)中,你在組件間僅傳遞其地址Uri即可,任何組件或任何持有Context的類都可以方便的獲取它,無(wú)論從實(shí)用性還是從效率上講,這比用Intent傳,或者實(shí)際傳送數(shù)據(jù)結(jié)構(gòu)對(duì)象來(lái)得快。
2. ContentProvider組件有自己的進(jìn)程和線程,不會(huì)有線程同步問(wèn)題
外部都是通過(guò)ContentResolver來(lái)訪問(wèn)ContentProvider,因此ContentProvider對(duì)外界來(lái)講是一樣的,訪問(wèn)方式相同,自然就不會(huì)有線程同步之類的問(wèn)題。
3. ContentProvider可以進(jìn)行封裝,從而使數(shù)據(jù)操作更加方便
因?yàn)镃ontentProvier提供統(tǒng)一的接口,你可以利用數(shù)據(jù)自身的特點(diǎn),在實(shí)現(xiàn)這些接口時(shí)進(jìn)行一些封裝,比如添加默認(rèn)值等等。
4. ContentProvider可以用作隊(duì)列或堆棧
因?yàn)槊恳恍卸际且粋€(gè)結(jié)構(gòu)化數(shù)據(jù),每一行的數(shù)據(jù)插入的順序又是按先后順序,所以這完全可以當(dāng)做一個(gè)隊(duì)列,或一個(gè)堆棧。
可以參考原生Mms中信息的發(fā)送流程,信息從用戶點(diǎn)擊發(fā)送就寫入數(shù)據(jù)庫(kù),然后一路把其Uri在各個(gè)組件間中傳遞,每個(gè)組件更新信息的狀態(tài),直到最后發(fā)送。還有DownloadProvider,Android中默認(rèn)的下載,應(yīng)用程序通過(guò)DownloadManager提交一個(gè)Request,但實(shí)際做下載的是DownloadService,而DownloadServer是在packages/provider/DownloadProvider中,是一個(gè)完全獨(dú)立的進(jìn)程。DownloadManager僅是把一個(gè)Request寫入DownloadProvider中,這個(gè)Request包含下載一個(gè)東西的相關(guān)信息如URL等。DownloadService僅是監(jiān)聽DownloadProvider的變化,一旦有新數(shù)據(jù)插入,就創(chuàng)建線程讀出此Request,然后開始下載。下載的同時(shí),也是把數(shù)據(jù)直接更新到DownloadProvider中,這樣UI就可以顯示進(jìn)度等信息。這一過(guò)程涉及二個(gè)進(jìn)程,至少三個(gè)組件:提交Request的用戶進(jìn)程和DownloadProvider進(jìn)程,DownloadManager(是一個(gè)公共API),DownloadService(單獨(dú)進(jìn)程,私有的package)和DownloadList(在DownloadProvider包內(nèi)部,用于顯示下載進(jìn)度的UI),這些組件之間沒有直接的通信,它們都是圍繞著ContentProvider。同時(shí)這里的ContentProvider也被用做下載請(qǐng)求的隊(duì)列,DownloadManager可以不斷的向其中加入請(qǐng)求,DownloadService會(huì)監(jiān)聽其變化從其中取出數(shù)據(jù)然后做下載。
別說(shuō)Android開發(fā)很簡(jiǎn)單
雖然Android上手很容易,但是要想寫出優(yōu)質(zhì)的代碼并不簡(jiǎn)單,分裂現(xiàn)象,碎片化,系統(tǒng)架構(gòu)等等都給很多事情加大了難度??梢钥匆幌略鷳?yīng)用中的主要的Activity代碼量都在5000行以上,它們的界面比較復(fù)雜,是主要核心業(yè)務(wù)邏輯所在,這些Activity控制的東西比較多,所以很臃腫。當(dāng)然這里的主要原因,還是未能進(jìn)行良好的設(shè)計(jì)和重構(gòu)。比如ICS中的Browser就做的好一些,它的BrowserActivity只有幾百行的代碼,但以前的代碼卻是6000多行,現(xiàn)在它把各種業(yè)務(wù)邏輯分別拆開,Activity只負(fù)責(zé)接收Frameworks層的回調(diào),所有的業(yè)務(wù)邏輯控制交由Controller來(lái)完成,而Controller只負(fù)責(zé)Tab的管理,菜單等的管理。具體的菜單和布局分辨率相關(guān)的東西又交由PhoneUi來(lái)處理。下載的處理由DownloadHandler來(lái)處理,等等。原來(lái)這些所有的事情都放在了BrowserActivity中的,可以想像原來(lái)它里面的邏輯會(huì)是多么的亂,維護(hù)起來(lái)會(huì)是多么的痛苦。當(dāng)然,現(xiàn)在的設(shè)計(jì)也還有待提高,因?yàn)轭愔g的耦合依然很大,比如Controller中持有PhoneUi對(duì)象,但是PhoneUi對(duì)象又持有Controller,等等現(xiàn)象。很多時(shí)候會(huì)出現(xiàn)相互調(diào)用的情況,這是相當(dāng)難以維護(hù)的,也破壞了相當(dāng)多的設(shè)計(jì)原則。
總之,凡是程序,如果要想寫的好,都需要投稿額外的精力,平臺(tái)雖然有優(yōu)劣但更重要的是對(duì)代碼投入的精力。但現(xiàn)在可悲的是,Android平臺(tái)贏利不理想,加之碎片化和浮躁的心理,使得很多應(yīng)用都在一二個(gè)月內(nèi)做出來(lái),所以整個(gè)Android生態(tài)系統(tǒng)中的應(yīng)用質(zhì)量都不高,更為嚴(yán)重的是反編譯和克隆,很多人都是把應(yīng)用抓下來(lái),反編譯然后改了改就是一個(gè)新的應(yīng)用,越是如此不關(guān)注質(zhì)量,用戶就越不買帳,開發(fā)者無(wú)法贏利,就越難投入精力做好應(yīng)用,如此進(jìn)入了一個(gè)惡性循環(huán)。
您可能感興趣的文章:- PHP入門教程之面向?qū)ο蟮奶匦苑治?繼承,多態(tài),接口,抽象類,抽象方法等)
- javascript面向?qū)ο蟪绦蛟O(shè)計(jì)高級(jí)特性經(jīng)典教程(值得收藏)
- C# 面向?qū)ο笕筇匦裕悍庋b、繼承、多態(tài)
- 淺談Lua的面向?qū)ο筇匦?/a>
- 利用javascript的面向?qū)ο蟮奶匦詫?shí)現(xiàn)限制試用期
- php學(xué)習(xí)筆記 php中面向?qū)ο笕筇匦灾籟封裝性]的應(yīng)用
- Javascript 面向?qū)ο筇匦?/a>
- javascript 的面向?qū)ο筇匦詤⒖?/a>
- 面向?qū)ο笕筇匦缘囊饬x講解
相關(guān)文章
-
Android Studio 4.0 正式發(fā)布在Ubuntu 20.04中安裝的方法
這篇文章主要介紹了Android Studio 4.0 正式發(fā)布如何在Ubuntu 20.04中安裝,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下 2020-06-06
-
Jetpack Compose 雙指拖拽實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Jetpack Compose 雙指拖拽實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪 2022-11-11
-
Android this與Activity.this的區(qū)別
這篇文章主要介紹了 Android this與Activity.this的區(qū)別的相關(guān)資料,需要的朋友可以參考下 2016-09-09
-
Android 使用地圖時(shí)的權(quán)限請(qǐng)求方法
今天小編就為大家分享一篇Android 使用地圖時(shí)的權(quán)限請(qǐng)求方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧 2018-07-07
-
Android原生項(xiàng)目集成Flutter解決方案
這篇文章主要介紹了Android原生項(xiàng)目集成Flutter解決方案,想了解Flutter的同學(xué)可以參考下 2021-04-04
-
Android開發(fā)手冊(cè)自定義Switch開關(guān)按鈕控件
這篇文章主要為大家介紹了Android開發(fā)手冊(cè)自定義Switch開關(guān)按鈕控件的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪 2022-06-06
-
android實(shí)現(xiàn)始終顯示overflow菜單的方法
這篇文章主要介紹了android實(shí)現(xiàn)始終顯示overflow菜單的方法,需要的朋友可以參考下 2014-07-07
-
Android自動(dòng)獲取輸入短信驗(yàn)證碼庫(kù)AutoVerifyCode詳解
這篇文章主要為大家詳細(xì)介紹了Android自動(dòng)獲取輸入短信驗(yàn)證碼庫(kù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2017-07-07
最新評(píng)論
組件的特點(diǎn)
對(duì)于Android的四大組件Activity, Service, ContentProvider和Service,不能有Setter和Getter,也不能給組件添加接口。原因是組件都是給系統(tǒng)框架調(diào)用的,開發(fā)者只能實(shí)現(xiàn)其規(guī)定的回調(diào)接口,組件的創(chuàng)建與銷毀都是由系統(tǒng)框架控制的,開發(fā)者不能強(qiáng)行干預(yù),更沒有辦法獲取組件的對(duì)象。比如Activity,Service,BroadcastReceiver,你沒有辦法去創(chuàng)建一個(gè)Activity,Service或BroadcastReceiver,然后像使用其他類那樣的調(diào)用其上的接口與其通信,用Setters和Getters改變屬性等等。這也決定了,組件之間通信只能用系統(tǒng)支持的Intent。而Intent只能傳遞基本數(shù)據(jù)類型和Uri等一些常見的數(shù)據(jù)類型。Intent只支持傳遞內(nèi)置類型和一些限制類型,這就導(dǎo)致了組件之間的數(shù)據(jù)傳遞必須都是基本類型,所以枚舉類型無(wú)法使用。
多態(tài)無(wú)法實(shí)現(xiàn)
比如你有一個(gè)Service用于在后臺(tái)執(zhí)行UI中發(fā)來(lái)的請(qǐng)求,這些請(qǐng)求有些是做數(shù)據(jù)請(qǐng)求,有些是做數(shù)據(jù)分析,等等。這里可以用多態(tài),定義一個(gè)統(tǒng)一的Transaction類,然后再為每種特定的Transaction類型,Transaction中統(tǒng)一接口process()用于實(shí)際的處理,理想的情況是,Service接收一個(gè)Transaction對(duì)象,然后調(diào)用其process(),沒有必要知道具體的類型,UI創(chuàng)建具體的一個(gè)類型對(duì)象然后交由Service來(lái)處理。但是這在Android當(dāng)中是無(wú)法實(shí)現(xiàn)的,因?yàn)镮ntent通信機(jī)制所限,因?yàn)樗荒苤苯觽鬟fTransaction對(duì)象。所以,Service必須要知道具體的類型。原生應(yīng)用Mms中就有如此的現(xiàn)象,在transaction包中TransactionService是處理服務(wù),UI發(fā)送到Service的只是區(qū)別不同Transaction的Id(一個(gè)整數(shù)),Service查看不同的Id創(chuàng)建不同的Transaction對(duì)象,然后調(diào)用process()對(duì)其處理。
建議:自己實(shí)現(xiàn)一個(gè)類似Service的服務(wù)類,在其內(nèi)用Handler,Thread和Looper讓其長(zhǎng)時(shí)間運(yùn)行。這樣就沒有組件間通信的限制,你可以像正常使用Java對(duì)象那樣來(lái)使用這個(gè)服務(wù)類,向其傳遞自定義的處理請(qǐng)求:
public class TransactionServer extends HandlerThread {
public TransactionServer() {
start();
}
public void onLooperPrepared() {
mHandler = new Handler(getLooper(), new Handler.Callback() {
@Override
public void handleMessage(Message msg) {
Transaction request = (Transaction) msg.obj;
request.process();
}
}
}
public void execute(Transaction request) {
if (mHandler == null) {
return;
}
Message msg = Message.obtain();
msg.obj = request;
mHandler.sendMessage(msg);
}
}
在Activity中就可以創(chuàng)建此Server的對(duì)象,然后使用它:
TransactionServer server = new TransactionServer();
Transaction updateRequest = new UpdateTransaction();
server.execute(updateRequest);
另外,用AIDL與Service通信,雖可以獲取Service的對(duì)象引用,可以直接調(diào)用Service的方法,但這個(gè)也有限制,對(duì)于AIDL的接口,所有的參數(shù)和返回類型都必須是基本數(shù)據(jù)
據(jù)類型,不能有對(duì)象。原因也好理解,因?yàn)锳IDL也是要通過(guò)IPC的,即便Service與Activity在同一個(gè)進(jìn)程內(nèi),所以本質(zhì)上它與Intent通信機(jī)制無(wú)區(qū)別。
封裝性被破壞
組件間的通信機(jī)制決定了Android的封裝性,先來(lái)看一些實(shí)例:
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(uri, "text/html");
startActivity(i);
這在Android當(dāng)中是再常見不過(guò)的了。
Intent和IntentFilter的使用讓封裝性受到大大的破壞,因?yàn)槟惚仨毎炎执瑓?shù)等直接寫入到Intent或IntentFilter當(dāng)中。例如:
Intent i = new Intent("android.contacts.action.MULTIPLECONTACTSLISTS");
i.setExtra("request_type", 3);
<intent-filter>
<action android:name="android.contacts.action.MULTIPLECONTACTSLISTS" />
</intent-filter>
當(dāng)然,可以再好一點(diǎn),就是:
Intent i = new Intent(Contacts.ACTION_GET_CONTACTS);
但是在AndroidManifest中的IntentFilter還是要寫字串常量(Literal Strings),這樣就有一個(gè)問(wèn)題,就是即使你寫錯(cuò)了,編譯器不會(huì)提醒你,直到你運(yùn)行的時(shí)候才會(huì)發(fā)現(xiàn)程序不正常工作,你調(diào)試啊,調(diào)試,最終發(fā)現(xiàn)是字串寫錯(cuò)了。或者,activity的name寫錯(cuò)了,編譯器同樣不會(huì)提醒你,但你運(yùn)行時(shí)卻因找不到類而報(bào)出RuntimeException。
建議:盡可能在所有作用域內(nèi)定義常量,以讓組件間接口保持一致性,特別是對(duì)于字串常量,一定要在二個(gè)組件都可見的作用域內(nèi)定義常量,否則將會(huì)有維護(hù)的麻煩。
例子:intent.putExtra("request_type", 3); --> intent.putExtra(TargetActivity.REQUEST_TYPE, TargetActivity.NO_BACKGROUND);否則,特別是當(dāng)目標(biāo)組件不在同一個(gè)包內(nèi),或距離很遠(yuǎn)時(shí),如果另一方改了,編譯時(shí)不會(huì)有錯(cuò),但程序不會(huì)正常工作,從而引發(fā)難以發(fā)覺的Bug。
無(wú)法在組件間傳遞自定義的數(shù)據(jù)結(jié)構(gòu)
如前面所述,因?yàn)闊o(wú)法獲取組件的對(duì)象的引用,所以你無(wú)法向其設(shè)置數(shù)據(jù),當(dāng)然,可以用靜態(tài)方法,但是不優(yōu)雅且難以維護(hù)(對(duì)于Service倒是可以通過(guò)AIDL方式獲取Service對(duì)象的引用,然后調(diào)用其方法來(lái)添加數(shù)據(jù))。又因?yàn)镮ntent只能攜帶基本的數(shù)據(jù)類型,所以對(duì)于自定義的數(shù)據(jù)結(jié)構(gòu)想要在組件間傳遞就特別的麻煩。當(dāng)然你可以以讓數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)Parcleable接口,但是用起來(lái)也相當(dāng)?shù)穆闊?BR>建議:
1. 盡可能的避免使用自定義數(shù)據(jù)結(jié)構(gòu),特別是除了Setters和Getters以外不具有其他行為的數(shù)據(jù)結(jié)構(gòu)
對(duì)于結(jié)構(gòu)化的數(shù)據(jù),為其定義ContentProvider,把數(shù)據(jù)寫入SQLite數(shù)據(jù)庫(kù),這樣數(shù)據(jù)庫(kù)表中的每行數(shù)據(jù)都相當(dāng)于是一個(gè)數(shù)據(jù)對(duì)象,每一列都是其屬性。因?yàn)锳ndroid的組件與SQLite數(shù)據(jù)庫(kù)的粘性很大,每個(gè)組件都可以很方便的從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),再通過(guò)Cursor等工具來(lái)操作數(shù)據(jù)。最最重要的是這很方便在組件間傳遞數(shù)據(jù),因數(shù)ContentProvider的訪問(wèn)都是通過(guò)Uri來(lái)實(shí)現(xiàn)的,而Uri又可以與Intent無(wú)縫接合,Uri可以方便的放入和從Intent中取出,每個(gè)組件又都可以直接訪問(wèn)ContentProvider用Uri讀取數(shù)據(jù),從而就可以實(shí)現(xiàn)組件間的無(wú)縫數(shù)據(jù)傳遞。
2. 盡可能的不要在組件之間傳遞數(shù)據(jù)
不要用太多的Activity,Service也能免則免,Activity+線程可能解決大部分問(wèn)題,當(dāng)然了,線程也不是那么好用的,特別是在Android里面。
3. 避免在組件之間傳遞自定義數(shù)據(jù)結(jié)構(gòu)
如前所述,組件之間最好直接傳遞基本數(shù)據(jù)和Intent支持的數(shù)據(jù)類型。對(duì)于自定義的數(shù)據(jù)結(jié)構(gòu),要么不定義數(shù)據(jù)結(jié)構(gòu),要么不要在組件間傳遞,否則會(huì)很麻煩,雖然可能以實(shí)現(xiàn)Parcelable接口,但是效率和操作的方便性上都會(huì)大打折扣。
關(guān)于枚舉和整數(shù)集
先前一篇<Android開發(fā)筆記之:用Enum(枚舉類型)取代整數(shù)集的應(yīng)用詳解>曾說(shuō)要盡量使用枚舉(enum)代替整數(shù)集(ints),而且很多編程書籍(Effective Java)也建議用枚舉代替整數(shù)集,這其中的好處就是降低出錯(cuò)率,把運(yùn)行時(shí)的檢查可以放到編譯時(shí),因?yàn)檎麛?shù)的范圍較大,你可傳遞任意的整數(shù),直到運(yùn)行時(shí)才會(huì)檢測(cè)或產(chǎn)生問(wèn)題,但是枚舉會(huì)在編譯時(shí)檢查類型,如果不是合法的枚舉,編譯器會(huì)報(bào)怨。
但是我們可以看到,在Android中的情況卻很差,Android中大量的使用了整數(shù)集,系統(tǒng)定義了大量的整數(shù)集,很多參數(shù)也都是整數(shù),雖然正確的方法都是向這些API傳遞其所定義的整數(shù)常量,但是你如果傳個(gè)Integer.MAX_VALUE或Integer.MIN_VALUE,起碼在編譯時(shí)不會(huì)出問(wèn)題。
既然這不是一個(gè)好的編程規(guī)范,為什么Android中還要大量的使用整數(shù)集呢?原因就在于組件間通信,組件之間要傳遞參數(shù),但是Intent又只能放入基本數(shù)據(jù)類型,也就是說(shuō)如果使用枚舉,那么將無(wú)法用Intent傳遞給其他的組件,因?yàn)槊杜e轉(zhuǎn)為整數(shù)很容易,但反過(guò)來(lái)整數(shù)轉(zhuǎn)成枚舉就不是那么容易了。
所以,如果你的常量不需要在組件間來(lái)回的傳遞,那么最好定義成為枚舉,否則,只能用整數(shù)集了。
關(guān)于組件一般的設(shè)計(jì)原則
1. 不要用組件實(shí)現(xiàn)某些接口,比如點(diǎn)擊接口,等等
因?yàn)榻M件是一個(gè)開銷非常巨大的對(duì)象,組件的繼承層次也非常的深,用組件實(shí)現(xiàn)接口,傳遞給調(diào)用者,就相當(dāng)于用一個(gè)火車去運(yùn)送一個(gè)小老鼠一樣,給了別人一個(gè)相當(dāng)大的對(duì)象,但是僅有一個(gè)或二個(gè)方法是別人需要的。特別是對(duì)于Activity,不要去實(shí)現(xiàn)一些公共的接口比如View.OnClickListener,除了前面的原因以外,另外一個(gè)就是你的onClick必須用條件來(lái)區(qū)分點(diǎn)擊的是哪個(gè)UI元素,這很難維護(hù),還有一個(gè)原因就是Activity的對(duì)象不是很穩(wěn)定,因?yàn)橄到y(tǒng)的某些事件如轉(zhuǎn)屏,語(yǔ)言切換等等會(huì)把Activity殺死并重新創(chuàng)建一個(gè)實(shí)例,所以有可能會(huì)引發(fā)問(wèn)題,雖然看起來(lái)Activity還在,但是并不同一個(gè)實(shí)例,如果某些東西與具體實(shí)例相關(guān),就會(huì)引發(fā)問(wèn)題,要么程序不正常工作,要么有RuntimeException。還有可能引發(fā)內(nèi)存泄漏,因?yàn)樗徒o使用者的接口對(duì)象都是Activity的實(shí)例引用,一旦某個(gè)引用超過(guò)Activity的生命周期,就會(huì)造成內(nèi)存泄漏。
推薦的做法是用匿名內(nèi)部類來(lái)實(shí)現(xiàn)接口,如果其他地方需要對(duì)此接口對(duì)象的操作,可以聲明一個(gè)成員變量或者一個(gè)內(nèi)部類,這樣也方便Activity來(lái)控制,以保證所有東西都生存在Activity的生存周期之內(nèi)。
2. 少用Service
組件Service并沒有傳說(shuō)中的好用,而且它還會(huì)讓你的程序退出頁(yè)面后仍然在后臺(tái)跑,占系統(tǒng)資源不說(shuō),還會(huì)被罵(看看這些文章吧),因?yàn)镾ervice的生命周期是由系統(tǒng)來(lái)控制,我們無(wú)法干預(yù),即使你確切的知道某些時(shí)候你已經(jīng)完全不用它了。用Activity和線程就可以完成絕大多數(shù)操作,而且你還能做到讓所有線程都在Activity的控制之內(nèi),讓它們都活在Activity的生命周期之內(nèi)。另外的原因就是,因?yàn)榫€程都屬于自建的類,或者普通的Java類,可以應(yīng)用面向?qū)ο?,因?yàn)闆]有了組件通信的限制。
3. 利用ContentProvider來(lái)做復(fù)雜數(shù)據(jù)結(jié)構(gòu)的通信工具
ContentProvider和SQLiteDatabase存儲(chǔ)的就是結(jié)構(gòu)化數(shù)據(jù),相當(dāng)于一個(gè)數(shù)據(jù)結(jié)構(gòu),它的引用就是它的Uri,任何組件通過(guò)Uri就可獲得此數(shù)據(jù)結(jié)構(gòu)。它有如下優(yōu)點(diǎn):
1. 可以方便的在組件間傳遞
因?yàn)閿?shù)據(jù)實(shí)際是在數(shù)據(jù)庫(kù)中,你在組件間僅傳遞其地址Uri即可,任何組件或任何持有Context的類都可以方便的獲取它,無(wú)論從實(shí)用性還是從效率上講,這比用Intent傳,或者實(shí)際傳送數(shù)據(jù)結(jié)構(gòu)對(duì)象來(lái)得快。
2. ContentProvider組件有自己的進(jìn)程和線程,不會(huì)有線程同步問(wèn)題
外部都是通過(guò)ContentResolver來(lái)訪問(wèn)ContentProvider,因此ContentProvider對(duì)外界來(lái)講是一樣的,訪問(wèn)方式相同,自然就不會(huì)有線程同步之類的問(wèn)題。
3. ContentProvider可以進(jìn)行封裝,從而使數(shù)據(jù)操作更加方便
因?yàn)镃ontentProvier提供統(tǒng)一的接口,你可以利用數(shù)據(jù)自身的特點(diǎn),在實(shí)現(xiàn)這些接口時(shí)進(jìn)行一些封裝,比如添加默認(rèn)值等等。
4. ContentProvider可以用作隊(duì)列或堆棧
因?yàn)槊恳恍卸际且粋€(gè)結(jié)構(gòu)化數(shù)據(jù),每一行的數(shù)據(jù)插入的順序又是按先后順序,所以這完全可以當(dāng)做一個(gè)隊(duì)列,或一個(gè)堆棧。
可以參考原生Mms中信息的發(fā)送流程,信息從用戶點(diǎn)擊發(fā)送就寫入數(shù)據(jù)庫(kù),然后一路把其Uri在各個(gè)組件間中傳遞,每個(gè)組件更新信息的狀態(tài),直到最后發(fā)送。還有DownloadProvider,Android中默認(rèn)的下載,應(yīng)用程序通過(guò)DownloadManager提交一個(gè)Request,但實(shí)際做下載的是DownloadService,而DownloadServer是在packages/provider/DownloadProvider中,是一個(gè)完全獨(dú)立的進(jìn)程。DownloadManager僅是把一個(gè)Request寫入DownloadProvider中,這個(gè)Request包含下載一個(gè)東西的相關(guān)信息如URL等。DownloadService僅是監(jiān)聽DownloadProvider的變化,一旦有新數(shù)據(jù)插入,就創(chuàng)建線程讀出此Request,然后開始下載。下載的同時(shí),也是把數(shù)據(jù)直接更新到DownloadProvider中,這樣UI就可以顯示進(jìn)度等信息。這一過(guò)程涉及二個(gè)進(jìn)程,至少三個(gè)組件:提交Request的用戶進(jìn)程和DownloadProvider進(jìn)程,DownloadManager(是一個(gè)公共API),DownloadService(單獨(dú)進(jìn)程,私有的package)和DownloadList(在DownloadProvider包內(nèi)部,用于顯示下載進(jìn)度的UI),這些組件之間沒有直接的通信,它們都是圍繞著ContentProvider。同時(shí)這里的ContentProvider也被用做下載請(qǐng)求的隊(duì)列,DownloadManager可以不斷的向其中加入請(qǐng)求,DownloadService會(huì)監(jiān)聽其變化從其中取出數(shù)據(jù)然后做下載。
別說(shuō)Android開發(fā)很簡(jiǎn)單
雖然Android上手很容易,但是要想寫出優(yōu)質(zhì)的代碼并不簡(jiǎn)單,分裂現(xiàn)象,碎片化,系統(tǒng)架構(gòu)等等都給很多事情加大了難度??梢钥匆幌略鷳?yīng)用中的主要的Activity代碼量都在5000行以上,它們的界面比較復(fù)雜,是主要核心業(yè)務(wù)邏輯所在,這些Activity控制的東西比較多,所以很臃腫。當(dāng)然這里的主要原因,還是未能進(jìn)行良好的設(shè)計(jì)和重構(gòu)。比如ICS中的Browser就做的好一些,它的BrowserActivity只有幾百行的代碼,但以前的代碼卻是6000多行,現(xiàn)在它把各種業(yè)務(wù)邏輯分別拆開,Activity只負(fù)責(zé)接收Frameworks層的回調(diào),所有的業(yè)務(wù)邏輯控制交由Controller來(lái)完成,而Controller只負(fù)責(zé)Tab的管理,菜單等的管理。具體的菜單和布局分辨率相關(guān)的東西又交由PhoneUi來(lái)處理。下載的處理由DownloadHandler來(lái)處理,等等。原來(lái)這些所有的事情都放在了BrowserActivity中的,可以想像原來(lái)它里面的邏輯會(huì)是多么的亂,維護(hù)起來(lái)會(huì)是多么的痛苦。當(dāng)然,現(xiàn)在的設(shè)計(jì)也還有待提高,因?yàn)轭愔g的耦合依然很大,比如Controller中持有PhoneUi對(duì)象,但是PhoneUi對(duì)象又持有Controller,等等現(xiàn)象。很多時(shí)候會(huì)出現(xiàn)相互調(diào)用的情況,這是相當(dāng)難以維護(hù)的,也破壞了相當(dāng)多的設(shè)計(jì)原則。
總之,凡是程序,如果要想寫的好,都需要投稿額外的精力,平臺(tái)雖然有優(yōu)劣但更重要的是對(duì)代碼投入的精力。但現(xiàn)在可悲的是,Android平臺(tái)贏利不理想,加之碎片化和浮躁的心理,使得很多應(yīng)用都在一二個(gè)月內(nèi)做出來(lái),所以整個(gè)Android生態(tài)系統(tǒng)中的應(yīng)用質(zhì)量都不高,更為嚴(yán)重的是反編譯和克隆,很多人都是把應(yīng)用抓下來(lái),反編譯然后改了改就是一個(gè)新的應(yīng)用,越是如此不關(guān)注質(zhì)量,用戶就越不買帳,開發(fā)者無(wú)法贏利,就越難投入精力做好應(yīng)用,如此進(jìn)入了一個(gè)惡性循環(huán)。
- PHP入門教程之面向?qū)ο蟮奶匦苑治?繼承,多態(tài),接口,抽象類,抽象方法等)
- javascript面向?qū)ο蟪绦蛟O(shè)計(jì)高級(jí)特性經(jīng)典教程(值得收藏)
- C# 面向?qū)ο笕筇匦裕悍庋b、繼承、多態(tài)
- 淺談Lua的面向?qū)ο筇匦?/a>
- 利用javascript的面向?qū)ο蟮奶匦詫?shí)現(xiàn)限制試用期
- php學(xué)習(xí)筆記 php中面向?qū)ο笕筇匦灾籟封裝性]的應(yīng)用
- Javascript 面向?qū)ο筇匦?/a>
- javascript 的面向?qū)ο筇匦詤⒖?/a>
- 面向?qū)ο笕筇匦缘囊饬x講解
相關(guān)文章
Android Studio 4.0 正式發(fā)布在Ubuntu 20.04中安裝的方法
這篇文章主要介紹了Android Studio 4.0 正式發(fā)布如何在Ubuntu 20.04中安裝,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Jetpack Compose 雙指拖拽實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Jetpack Compose 雙指拖拽實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android this與Activity.this的區(qū)別
這篇文章主要介紹了 Android this與Activity.this的區(qū)別的相關(guān)資料,需要的朋友可以參考下2016-09-09Android 使用地圖時(shí)的權(quán)限請(qǐng)求方法
今天小編就為大家分享一篇Android 使用地圖時(shí)的權(quán)限請(qǐng)求方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Android原生項(xiàng)目集成Flutter解決方案
這篇文章主要介紹了Android原生項(xiàng)目集成Flutter解決方案,想了解Flutter的同學(xué)可以參考下2021-04-04Android開發(fā)手冊(cè)自定義Switch開關(guān)按鈕控件
這篇文章主要為大家介紹了Android開發(fā)手冊(cè)自定義Switch開關(guān)按鈕控件的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06android實(shí)現(xiàn)始終顯示overflow菜單的方法
這篇文章主要介紹了android實(shí)現(xiàn)始終顯示overflow菜單的方法,需要的朋友可以參考下2014-07-07Android自動(dòng)獲取輸入短信驗(yàn)證碼庫(kù)AutoVerifyCode詳解
這篇文章主要為大家詳細(xì)介紹了Android自動(dòng)獲取輸入短信驗(yàn)證碼庫(kù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07