詳解Android Service 使用時(shí)的注意事項(xiàng)
最近有個(gè)項(xiàng)目剛好使用了Service,特別是AIDL遠(yuǎn)程服務(wù),經(jīng)過(guò)這次項(xiàng)目對(duì)Service有了更好的理解,在這里作個(gè)總結(jié)。
startService / bindService 混合使用
- 每一次調(diào)用 startService 都會(huì)回調(diào)onStartCommand,之后調(diào)用了stopService之后就會(huì) destroy Service。即使有多個(gè)client啟動(dòng)服務(wù),那調(diào)用一次stopService 就能 destroy Service 。通過(guò)這種方式還有一個(gè)好處就是Service可以通過(guò)調(diào)用 stopSelf 主動(dòng)退出。
- 第一次調(diào)用bindService 的時(shí)候才會(huì)回調(diào) onBind,如果有多個(gè)client連接服務(wù),在最后一個(gè)client調(diào)用unbindService時(shí)才會(huì)回調(diào) onUnbind,并destroy Service。
如果startService / bindService 混合使用 那Service的生命同期會(huì)怎樣呢,其實(shí)只要記住以上的思路,這種問(wèn)題很好理解。首先 startService 與 stopService 對(duì)應(yīng) ,沒(méi)有stopService 之前不會(huì) destroy Service , bindService 與 unbindService對(duì)應(yīng) ,沒(méi)有 unbindService 前也不會(huì)destroy Service。
為服務(wù)添加權(quán)限
相信大家做的服務(wù)都是公共的,即所有應(yīng)用都可以調(diào)用。但是如果我想我的服務(wù)只給特定的應(yīng)用調(diào)用,應(yīng)該如何設(shè)置呢?我們可以給服務(wù)添加權(quán)限。關(guān)于權(quán)限,Android系統(tǒng)給權(quán)限為了四個(gè)類別:
- 普通級(jí): 這些權(quán)限并不能真正傷害到用戶(比如更換壁紙),當(dāng)程序需要這些權(quán)限是,開(kāi)發(fā)者不需要指定程序會(huì)自動(dòng)賦予這些權(quán)限。
- 危險(xiǎn)級(jí): 這些權(quán)限可能會(huì)帶來(lái)真的傷害(比如打電話,打開(kāi)網(wǎng)絡(luò)鏈接等),如果要使用它們需要開(kāi)發(fā)者在AndroidManifest.xml中聲明對(duì)應(yīng)的權(quán)限。
- 簽名級(jí): 如果應(yīng)用使用的是相同的簽名證書時(shí),這些權(quán)限會(huì)自動(dòng)授予給聲明或者創(chuàng)建這些權(quán)限的程序。設(shè)計(jì)這一層級(jí)權(quán)限的目的是方便組件間數(shù)據(jù)共享。
- 簽名/系統(tǒng)級(jí): 和簽名級(jí)一樣,例外的是系統(tǒng)鏡像是自動(dòng)獲取這些權(quán)限的,這一層級(jí)是專為設(shè)備制造商設(shè)計(jì)的。
<uses-permission android:name="android.permission.custom.XXX"/>
如果我們想讓自己開(kāi)發(fā)的Service只能被特定的Client調(diào)用,那就可以添加自定義的權(quán)限。比如危險(xiǎn)級(jí),我們可以在AndroidManifest.xml中聲明對(duì)應(yīng)的權(quán)限,只有應(yīng)用也設(shè)置了這個(gè)權(quán)限,才能正常啟動(dòng)服務(wù)。
關(guān)于AIDL遠(yuǎn)程服務(wù)
所謂的AIDL遠(yuǎn)程服務(wù) 就是運(yùn)行在另一個(gè)進(jìn)程的服務(wù),平時(shí)我們調(diào)用的服務(wù)都運(yùn)行在主線程。要使用AIDL服務(wù)就必須寫AIDL接口,向外暴露接口就可以與遠(yuǎn)程服務(wù)進(jìn)行交互了。對(duì)于AIDL有如下幾個(gè)值得注意的地方:
- AIDL接口的函數(shù)都不支持重載,即函數(shù)名不能一樣,即使函數(shù)參數(shù)個(gè)數(shù)不一樣。
- AIDL接口傳遞的參數(shù)只有是基本數(shù)據(jù)類型、String 和CharSequence、List 和 Map、實(shí)現(xiàn)android.os.Parcelable 接口的類。
- 既然AIDL是在另一個(gè)進(jìn)程的服務(wù),那客戶端調(diào)用的AIDL接口是否堵塞? 答案是肯定的。如果不加 oneway 修飾符,那客戶端調(diào)用的接口都是堵塞的,但是oneway修飾符也有限制,就是oneway接口下面的方法都必須是返回void類型,不能返回其他類型的數(shù)據(jù)。
AIDL的接口如何升級(jí)?
在做一個(gè)比較大型的項(xiàng)目,那項(xiàng)目會(huì)不斷迭代,那就有可能增加、修改AIDL接口,那如何保證AIDL接口和老的接口不會(huì)混亂呢,根據(jù)我的經(jīng)驗(yàn)有如下總結(jié):
- 對(duì)于增刪參數(shù)的接口:AIDL函數(shù)的訪問(wèn)會(huì)檢測(cè)參數(shù),Client有參數(shù)的接口可以調(diào)用Service的接口(不管有無(wú)參數(shù)),反過(guò)來(lái),Client的接口沒(méi)參數(shù)就只能調(diào)用Service沒(méi)有參數(shù)的接口。比如我們的新接口定義函數(shù)添加了參數(shù),那client必須同時(shí)或提前修改,不然我們發(fā)布了新接口的Service應(yīng)用,Client就不能調(diào)用了,但是Client用新接口是可以去訪問(wèn)老接口的服務(wù)的。
- 對(duì)于增刪函數(shù)的接口:服務(wù)端增加函數(shù)并不影響客戶端,相反客戶端增加服務(wù)端沒(méi)有的接口就會(huì)訪問(wèn)無(wú)效果,如果客戶端增加接口有返回值就返回默認(rèn)值。
Service管理多個(gè)客戶端
如果Service有多個(gè)客戶端,如何安全地與它們通信呢?如何給各個(gè)客戶端回調(diào)結(jié)果呢? 在這里我要說(shuō)說(shuō)我在最近項(xiàng)目出現(xiàn)的一個(gè)問(wèn)題,我在項(xiàng)目中要做一個(gè)公共的服務(wù),類似于指紋解鎖,其它應(yīng)用通過(guò)調(diào)用我的服務(wù)來(lái)獲取結(jié)果,我設(shè)計(jì)了start(callback), stop()兩個(gè)接口,一開(kāi)始我就用單回調(diào)的方式,即在代碼中定義一個(gè)callback的屬性,誰(shuí)調(diào)用了start就把callback設(shè)置成誰(shuí),只有最后一個(gè)調(diào)用start的Client能夠獲得回調(diào),代碼如下 :
private Callbak mCallback; public void start(Callback callback) { this.mCallback = callback; } public void stop() { this.mCallback = null; }
這種方式,在單個(gè)應(yīng)用時(shí)是很有效的,在多個(gè)應(yīng)用時(shí),只要應(yīng)用能按順序執(zhí)行start、stop 那這個(gè)接口的設(shè)計(jì)也沒(méi)什么問(wèn)題。但是事情沒(méi)想象中那么簡(jiǎn)單,如果Client1調(diào)用了start,跟著Client2也調(diào)用了start,這時(shí)Client1 要stop,那會(huì)怎樣,那整個(gè)服務(wù)都stop了。這個(gè)就是我設(shè)計(jì)的服務(wù)中出現(xiàn)的大問(wèn)題,之后我想著為我的服務(wù)接口作一些改變,以適應(yīng)這種多應(yīng)用的不按順序的調(diào)用 。我第一個(gè)想法就是用register、unregister的方式,用一個(gè)list收集所有的callback ,回調(diào)時(shí)可以輪循,stop時(shí)也可以通過(guò)判斷l(xiāng)ist的個(gè)數(shù),如果是小于等于1,那就執(zhí)行stop :
private List<Callbak> mCallbacks; public void start(Callback callback) { mCallbacks.add(callback); } public void stop(Callback callback) { mCallbacks.remove(callback); if(list.size() >= 1) return; }
考慮到接口的升級(jí),這個(gè)改動(dòng)是最小的,只給stop添加了一個(gè)參數(shù)。但是這個(gè)方式也有毛病,我們服務(wù)對(duì)Callback的引用是強(qiáng)引用,如果Client異常退出了,那引用還在并且會(huì)越積越多,在回調(diào)的時(shí)候,也可能出現(xiàn)DeadObjectException的錯(cuò)誤。通過(guò)網(wǎng)絡(luò)查找資料,我找到了RemoteCallbackList,RemoteCallbackList也是一個(gè)列表,保存的是回調(diào)接口,使用Link-To-Death回調(diào) (在Sevice中接受到這個(gè)Binder對(duì)象,并且使用 binder.linkToDeath(),注冊(cè)一個(gè)DeathRecipient回調(diào);實(shí)現(xiàn)DeathRecipient。當(dāng)Client意外退出的時(shí)候,DeathRecipient.binderDied()將被回調(diào),我們可以在這里釋放相關(guān)的資源。)。最終代碼如下:
private RemoteCallbackList<Callback> mCallbacks = new RemoteCallbackList<>(); public void start(Callback callback) { mCallbacks.register(callback); } public void stop(Callback callback) { mCallbacks.unregister(callback); if(mCallbacks.getBroadcastItem() >= 1) return; } private void notifyResult(String result) { final int len = mCallbacks.beginBroadcast(); for (int i = 0; i < len; i++) { try { mCallbacks.getBroadcastItem(i).onResult(result); } catch (RemoteException e) { e.printStackTrace(); } } mCallbacks.finishBroadcast(); }
使用Messenger 實(shí)現(xiàn) Servie與Client端通信
Messenger是基于Handler的,通過(guò)為Messenger添加Handler來(lái)傳遞處理數(shù)據(jù),之后Service與Client的通信都是通過(guò)傳遞的Handler來(lái)進(jìn)行。用這種方式可以不需要定義AIDL接口,也就不出現(xiàn)因?yàn)樾薷腁IDl接口所造成的接口版本不對(duì)應(yīng)的麻煩。
Messenger的使用就是通過(guò) Handler傳遞消息, 客戶端send方法發(fā)送的是一個(gè)Message,這個(gè)Message.replyTo指向的是一個(gè)Messenger,Messenger又持有客戶端的一個(gè)Binder對(duì)象(MessengerImpl),服務(wù)端正是利用這個(gè)Binder對(duì)象做的與客戶端的通信。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android Studio 導(dǎo)入開(kāi)源項(xiàng)目的正確姿勢(shì)及注意事項(xiàng)
- Android Studio3.0升級(jí)后使用注意事項(xiàng)及解決方法
- Android 程序執(zhí)行Linux命令的解決方法及注意事項(xiàng)
- 將Eclipse工程轉(zhuǎn)Android Studio工程的步驟與注意事項(xiàng)
- AndroidStudio升級(jí)到3.0的新特性和注意事項(xiàng)小結(jié)
- Android 中ViewPager中使用WebView的注意事項(xiàng)
- Android TimerTask 的簡(jiǎn)單應(yīng)用及注意事項(xiàng)
- Android 如何使用log4j及注意事項(xiàng)
- Android集成百度地圖開(kāi)發(fā)流程和注意事項(xiàng)
- Android onNewIntent()觸發(fā)機(jī)制及注意事項(xiàng)
- Android Studio添加第三方庫(kù)的注意事項(xiàng)
- Android 程序申請(qǐng)權(quán)限注意事項(xiàng)
- Android Service自啟動(dòng)注意事項(xiàng)分析
- 關(guān)于Android WebView的loadData方法的注意事項(xiàng)分析
- Android NDK中socket的用法以及注意事項(xiàng)分析
- Android生存指南之:開(kāi)發(fā)中的注意事項(xiàng)
- Android 編程下字庫(kù)的使用及注意事項(xiàng)
- 60條Android開(kāi)發(fā)注意事項(xiàng)與經(jīng)驗(yàn)總結(jié)
相關(guān)文章
Android Handler,Message,MessageQueue,Loper源碼解析詳解
這篇文章主要介紹了Android Handler,Message,MessageQueue,Loper源碼解析詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09Android使用ImageView實(shí)現(xiàn)支持手勢(shì)縮放效果
這篇文章主要介紹了Android使用ImageView實(shí)現(xiàn)支持手勢(shì)縮放效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android移動(dòng)端touch實(shí)現(xiàn)下拉刷新功能
這篇文章主要介紹了移動(dòng)端touch實(shí)現(xiàn)下拉刷新功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02Intel HAXM為Android 模擬器加速解決模擬器運(yùn)行慢的問(wèn)題
Android 模擬器一直以運(yùn)行速度慢著稱, 本文介紹使用 Intel HAXM 技術(shù)為 Android 模擬器加速, 使模擬器運(yùn)行度媲美真機(jī), 徹底解決模擬器運(yùn)行慢的問(wèn)題,感興趣的朋友可以了解下哦2013-01-01Android使用TabLayout+Fragment實(shí)現(xiàn)頂部選項(xiàng)卡
本文通過(guò)實(shí)例代碼給大家介紹了Android使用TabLayout+Fragment實(shí)現(xiàn)頂部選項(xiàng)卡功能,包括TabLyout的使用,感興趣的朋友參考下本文吧2017-05-05Android編程實(shí)現(xiàn)canvas繪制餅狀統(tǒng)計(jì)圖功能示例【自動(dòng)適應(yīng)條目數(shù)量與大小】
這篇文章主要介紹了Android編程實(shí)現(xiàn)canvas繪制餅狀統(tǒng)計(jì)圖功能,可實(shí)現(xiàn)自動(dòng)適應(yīng)條目數(shù)量與大小的功能,涉及Android基于canvas的圖形繪制相關(guān)操作技巧,需要的朋友可以參考下2017-01-01Android端使用Modbus協(xié)議的簡(jiǎn)單方法
Modbus協(xié)議是全球第一個(gè)用于工業(yè)現(xiàn)場(chǎng)的總線協(xié)議,與外設(shè)交互可以采用串口通信,tcp等方式,這篇文章主要給大家介紹了關(guān)于Android端使用Modbus協(xié)議的簡(jiǎn)單方法,需要的朋友可以參考下2021-11-11