Service是Android四大組件中與Activity最相似的組件,他們都代表可執(zhí)行的程序,Service與Activity的區(qū)別在于:Service一直在后臺運行,他沒有用戶界面,所以絕不會到前臺來,一旦Service被啟動起來,他就與Activity一樣,他完全具有自己的生命周期。關(guān)于程序中Activity和Service的選擇標準是:如果某個程序需要在運行時向用戶呈現(xiàn)某種界面,或者該程序需要與用戶交互,就需要使用Activity;否則就應(yīng)該考慮使用Service了。
開發(fā)者開發(fā)Service的步驟與開發(fā)Activity的步驟很像,開發(fā)Service組件需要先開發(fā)一個Service子類,然后在AndroidManifest.xml文件中配置該Service,配置時通過<intent-filter...../>。元素指定它可被那些Intent啟動。
Android系統(tǒng)本身提供了大量的Service組件,開發(fā)者可通過這些系統(tǒng)Service來操作Android系統(tǒng)本身。
本章還將向讀者介紹BroadcastReceiver組件。BroadcastReceiver組件就像一個全局的事件監(jiān)聽器,只不過用于監(jiān)聽系統(tǒng)Broadcast。通過是BroadcastReceiver,即可在不同應(yīng)用程序之間通訊。
10.1 Service簡介
Service組件也是可執(zhí)行得讓程序,他有自己的生命周期。創(chuàng)建、配置Service與創(chuàng)建、配置Activity的過程基本相似,下面詳細介紹Android Service的開發(fā)。
10.1 創(chuàng)建、配置Service
就像開發(fā)Activity需要兩個步驟:
(1)開發(fā)Activity子類;
2、在AndroidManifest.xml文件中配置Activity。開發(fā)Service也需要兩個步驟。
(1)定義一個繼承Service的子類。
(2)在AndroidManifest.xml文件中配置該Service
Service與Activity還有一些相似之處,它們都是從Content派生出來的,因此它們都可以調(diào)用Content里定義的如getResources、getContentResolver()等方法。
與Activity相似的是,Service中也定義了一系列生命周期方法,如下所示:
1、 IBinder onBind(Intent intent):該方法是Service之類必須實現(xiàn)的方法。該方法返回一個IBinder對象,應(yīng)用程序可通過該對象與Service組件通訊。
2、 void onCreat():在該Service第一次被創(chuàng)建后立即回調(diào)該方法。
3、 void onDestroy():在該Service第一次被創(chuàng)建后將立即回調(diào)該方法。
4、 void onStartCommand(Intent intent,int flags,int starId):該方法的在其版本是Void onStart(Intent intent,int startId),每次客服端調(diào)用startService(Intent)方法啟動該Service時都會回調(diào)該方法。
5、 boolean onUnbind(Intent intent):當該Service上綁定的所有客戶端都斷開鏈接時將會回調(diào)該方法。
下面的類定義了一個Service
提示:
上面這個Service什么也沒干,但實際上它是Service組建的框架,如果希望Service組件做某些事情,那么只要在onCreat()或onStartCommand()方法中定義相關(guān)業(yè)務(wù)代碼即可。
定義了上面的Service之后,接下來需要在AndroidManifest.xml文件中配置該Service,配置Service使用
與配置Activity相似的是,配置Service時也可以為<service.../>元素配置<intent-filter.../>子文件,用于說明該Service可被那些Intent啟動。
從上面的配置片段不難看出,配置Service與配置Activity的差別并不大,只是配置Service使用<Service.../>,而且無需指定android:label屬性-------因為Service沒有界面,總是位于后臺運行,為該Service指定標簽沒有太大的意義。
1、通過Content的startService()方法:通過該方法啟動Service,訪問者與Service沒有關(guān)聯(lián),即使訪問者退出了,Service也仍然運行。
2、通過Content的bindService()方法:使用該方法啟動Service,訪問者與Service綁定在一起,訪問者退出了,Service也就終止了。
下面先示范第一種方式運行Service
從上面程序中的粗字體代碼不難看出,啟動、關(guān)閉Service十分簡單,調(diào)用Content里定義的startService、stopService()方法即可啟動、關(guān)閉Service。
提示:
可能讀者會注意到本程序使用了顯示Intent(直接指定要啟動的目標組件實現(xiàn)類)來啟動Service,單本書第二版使用了隱士Intent啟動Service。這是因為從Android5.0開始,Google要求必須使用顯示Intent啟動Service組件。
運行該程序,通過程序界面先啟動Service,在關(guān)閉Service,將可以在AS的Logcat面板看到如上圖所示的輸出。
如果不關(guān)閉Service的情況下,連續(xù)三次單擊“啟動Service”按鈕,
從上圖可以看出,每當Service被創(chuàng)建時會回調(diào)onCreat()方法,每次Service被啟動時都會回調(diào)onStartCommand()方法-----多次啟動一個已有的Service組件將不會在回調(diào)onCreat()方法,但每次啟動都會回調(diào)onStartCommand()。
10.1.3 綁定本地Service并與之通訊
當程序通過starService()和stopService()啟動、關(guān)閉Service時,Service與訪問者之間基本上不存在太多的關(guān)聯(lián),因此Service和訪問者之間也無法進行通訊、交換數(shù)據(jù)。
如果Service和調(diào)用者之間需要數(shù)據(jù)交換,則應(yīng)該使用bindService和unbindService()方法啟動和關(guān)閉Service。
Content的bindService()方法完整的方法簽名是:bindService(Intent service,ServiceConnection conn,int flags),該方法的三個參數(shù)解釋如下。
1、Service:該參數(shù)通過Intent指定要啟動的Service。
2、conn:該參數(shù)是一個Serviceconnection對象,該對象用于監(jiān)聽訪問者與Service之間的連接情況。當訪問者與Service之間連接成功時將回調(diào)該ServiceConnection對象的onServiceConnected(CompontName name,Ibinder service)方法;當Service所在的宿主進程由于異常終止或其他原因終止,導(dǎo)致該Service與訪問者之間斷開連接時回調(diào)該ServiceConnection對象的onServiceDisconnected(ComponentName name)方法。
注意:
當調(diào)用者主動通過unBindService()方法斷開與Service的連接時,ServiceConnection對象的onSrviceDisconnected(ComponentName name)方法并不會被調(diào)用。
flags:指定綁定時是否自動創(chuàng)建Service(如果Service還未創(chuàng)建)。該參數(shù)可指定為0(不自動創(chuàng)建)或BIND_AUTO_CREATE(自動創(chuàng)建)。
注意到Serviceconnection對象的onServiceConnected()方法中有一個IBinder對象,該對象即可實現(xiàn)與被綁定Service之間的通訊。
當開發(fā)Service類時,該service必須提供一個IBinder onBind(Intent intent)方法,在綁定本地Service情況下,onBind(Intent intent)方法返回的IBinder對象將會傳給ServiceConnection對象里onServiceConnected(CompontName name,Ibinder service)方法的Service參數(shù),這樣訪問者就可通過該IBinder對象與Service進行通訊了。
提示:
IBinder對象相當于Service組件的內(nèi)部鉤子,該鉤子關(guān)聯(lián)到綁定的Service組件,當其他程序組件綁定該Service時,Service將會把IBinder對象返回給其他程序組件,其他程序組件通過該IBinder對象即可與Service組件進行實時通訊。
實際上開發(fā)時通常會采用繼承Binder(IBinder的實現(xiàn)類)的方式實現(xiàn)自己的IBinder對象。下面的程序示范了如何在Activity中綁定本地Service,并獲取Service的運行狀態(tài)。該程序的Service類需要“真正”實現(xiàn)onBind()方法,并讓該方法返回一個有效的IBinder對象。該Service類的代碼如下。
上面Service類的粗體字代碼實現(xiàn)了onBind()方法,該方法返回了一個可訪問該Service狀態(tài)數(shù)據(jù)(count值)的Binder對象,該對象被傳給該Service的訪問者。
上面程序中的通過繼承Binder雷山縣了一個IBinder對象,這個MyBinder類是Service的內(nèi)部類,這對于本地綁定Service并與之通訊的場景的一種常見情形。
接下來定義一個Activity來綁定該Service,并在該Activity中通過MyBinder對象訪問Service內(nèi)部狀態(tài)。該Activity的界面上包含三個按鈕,第一個按鈕用于綁定Service;第二個按鈕用于接觸綁定;第三個按鈕則用于獲取Service的運行狀態(tài)。該Activity的代碼如下。
上面程序中
這段代碼用于在該Activity與Service鏈接成功時獲取Service的onBind()方法所返回的MyBinder對象;
這段代碼即可通過myBinder對象來訪問Service的運行狀態(tài)了。
運行該程序,單擊程序界面中的綁定"Service"按鈕,即可看到Android Studio的logcat有如圖10.3所示的輸出。
在該Activity中綁定Service之后,該Activity還可通過MyBinder對象來獲取Service的運行狀態(tài),如果用戶單擊程序界面上的"獲取Service狀態(tài)"按鈕,即可看到如圖10.4所示的輸出
圖 10.4
從圖10.4所示的輸出可以看到,該Activity可以非常方便地訪問到Service的運行狀態(tài)。雖然本程序只是一個簡單的示例,該Activity只是訪問了Service的一個簡單count值,但實際上完全可以讓Mybinder去操作Service中更多的數(shù)據(jù)-----到底需要訪問Service的多少數(shù)據(jù),完全取決于實際業(yè)務(wù)需要。
提示
對于Servicer的onBind()方法所返回的IBinder對象來說,它可以被當成該Service組件所返回的代理對象,Service允許客戶端通過該IBinder對象來訪問內(nèi)部的數(shù)據(jù),這樣即可實現(xiàn)客戶端與Service之間的通訊。
如果我們單擊程序界面上的"解除綁定"按鈕,即可在AS的logCat中看到如圖10.5所示輸出
圖10.5(解除綁定)
正如10.5中所示,當程序調(diào)用unBindService()方法解除對某個Service的綁定時,系統(tǒng)會先回調(diào)該Service的onUnbinde()方法,然后調(diào)用onDestry()方法。
與多次調(diào)用startService()方法啟動Service()方法啟動Service不同的是,多次調(diào)用bindService()方法并不會執(zhí)行重復(fù)綁定。對于前一個示例程序,用戶每單擊"啟動"按鈕一次,系統(tǒng)就會回調(diào)Service的onStartCommand()方法一次;對于這個示例程序,不管用戶單擊"綁定Service”按鈕多少次,系統(tǒng)只會回調(diào)Service的onBiond()方法一次。
10.1.4 Service的生命周期
通過前面兩個示例,讀者應(yīng)該大致明白Service的生命周期了。隨著應(yīng)用程序啟動Service方式不同,Service的生命周期也略有不同。
如果應(yīng)用程序通過startService()方法來啟動Service,Service的生命周期如圖10.6左邊所示。如果應(yīng)用程序通過bindService方法來啟動Service的生命周期如右圖所示:
Service的生命周期還有一個特殊的情形--------如果Service已由某個客戶端通過startService()方法啟動了。接下來其他客戶端調(diào)用bindService()方法綁定該Service之后,再調(diào)用unbinService()方法解除綁定,最后有調(diào)用binService()方法再次綁定到Service,這個過程所觸發(fā)的生命周期方法如下所示onCreat()---- onStartCommand() ?---- onBind() ---- onUnbind()(重寫該方法時返回了true) ---- onRebind(),在上面這個觸發(fā)過程中,onCreat()是創(chuàng)建該Service后立即調(diào)用的,只有當該service被創(chuàng)建時才會被調(diào)用;onStartCommand()方法則是有客戶端調(diào)用startService()方法時觸發(fā)的。圖10.7所示的logcat顯示了上面生命周期的輸出。
圖10.7
在圖10.7所示的輸出中,可以看到Service的onRebind()方法被回調(diào)了。如果希望該方法被回調(diào),除了需要該Service是由Activity的startService()方法啟動之外,還需要Service子類重寫onUnbind()方法時返回true。
注意:
在圖10.7所示輸出中,并沒有發(fā)現(xiàn)Service回調(diào)onDestroy()方法,這是因為該Service并不是由Activity通過bindService()方法啟動的(該Service事先已由Activity通過startActivity()方法啟動了),因此當Activity調(diào)用unBindService方法取消與該Service的綁定時,該Service也不會終止。
10.1.5 使用IntentService
IntentService是Service的子類,因此它不是普通的Service,它比普通的Service增加了額外的功能。
先看Service本身存在的兩個問題。
1、Service不會專門啟動一個單獨的進程,Service與它塑造應(yīng)用位于一個進程中。
2、Service不是一條新的線程,因此不應(yīng)該在Service中直接處理耗時的任務(wù)。
提示:
如果開發(fā)者需要在Service中處理耗時任務(wù),建議在Service中另外啟動一條新的線程來處理該耗時任務(wù)。就像在前面BindService中所看到的,程序在BindService的onCreate()方法中啟動了一條新線程來處理耗時任務(wù)。可能有讀者感到疑惑:直接在其他程序組件中啟動子線程來處理耗時任務(wù)不行嗎?這種方式也不可靠,由于Activity可能會被用戶退出,而BraodcastReceiver的生命周期本身就很短。可能出現(xiàn)的情況是:在子線程還沒結(jié)束的情況下,Activity已經(jīng)被用戶退出了,或者BroadcastReceiver已經(jīng)結(jié)束了。在Activity已經(jīng)退出、BroadcastReceiver已經(jīng)結(jié)束的情況下,此時他們所在的進程就變成了空進程(沒有任何活動組件的進程),系統(tǒng)需要內(nèi)存時可能會優(yōu)先終止該進程,那么進程內(nèi)的所有子線程也會被終止,這樣就可能導(dǎo)致子線程無法執(zhí)行完成。
而IntentService正好可以彌補Service的上述兩個不足:IntentService將會使用隊列來管理請求Intent,每當客戶端代碼通過Intent請求啟動IntentService時,IntentServiec會將該Intent加入隊列中,然后開啟一條新的worker線程來處理該IntentService。對于異步的startService()請求,IntentService會次序依次處理隊列中的Intent,該線程保證同一時刻只處理一個Intent。由于IntentService使用新的orker線程處理Intent請求,因此IntentService不會阻塞主線程,所以IntentService自己就可以處理耗時任務(wù)。
歸納起來,IntentService具有如下特請求征:
1、IntentService會創(chuàng)建單獨的worker線程來處理所有的Intent請求
2、IntentService匯創(chuàng)建單獨的worker線程來處理onHandleIntent方法的實現(xiàn)代碼,因此開發(fā)者無需處多線程的問題。
3、當所有請求處理完成后。IntentService會自動停止,因此開發(fā)者無須調(diào)用stopSelf()方法來停止該Service。
4、為Service的onBind()方法提供了默認實現(xiàn),默認實現(xiàn)的onBind()方法返回null。
5、為Service的onStartCommand()方法提供了默認實現(xiàn),該實現(xiàn)將請求Intent添加到隊列中,
從上面的介紹可以看出,擴展IntentService實現(xiàn)Service無須重寫onBind()、onStartCommand方法,只要重寫onHandleIntent()方法即可。
下面的示例程序界面中包含了兩個按鈕,分別用于啟動普通Service和IntentService,兩個Service都需要處理耗時任務(wù)。該程序的界面布局代碼很簡單,這里不再給出。主程序Activity代碼:
上面Activity的兩個事件處理方法中分別啟動了MyService和MyIntentService,其中MyService是繼承service的子類,而MyIntentService則是繼承IntentService的子類。
下面是MyService類的代碼。
上面MySerevice在onStartCommand()方法中使用線程暫停的方式模擬了耗時任務(wù),該線程暫停了20秒,相當于該耗時任務(wù)需要執(zhí)行20秒,由于普通Service的執(zhí)行會阻塞主線程,因此啟動該線程將會導(dǎo)致程序出現(xiàn)ANR(Application Not Responding)異常。
下面是MyIntentService類的代碼
從上面的代碼可以看出,MyIntentService繼承了IntentService,并不需要實現(xiàn)onBind()、onStartCommand()方法,只要實現(xiàn)onHandleIntent()方法即可,在該方法中定義該Service需要完成的認為誒。本示例的onHandleIntent()方法也用線程暫停的方式模擬了耗時任務(wù),線程同樣暫停了20秒。但由于IntentService會使用單獨的線程來完成該耗時任務(wù),因此啟動MyIntentService不會阻塞前臺線程
運行該示例,如果單擊界面上的“啟動普通Service”按鈕,竟會激發(fā)startService()方法,該方法竟會啟動MyService去執(zhí)行耗時任務(wù),此時將會導(dǎo)致程序UI線程被阻塞(程序界面失去響應(yīng)),而且由于阻塞時間太長,因此將會看到如圖10.8所示的ANR異常。
相反,如果調(diào)用“啟動IntentService”來啟動MyIntentService,雖然MyIntentService也需要執(zhí)行耗時任務(wù),但由于MyService會使用單獨的worker線程,因此MyIntentService不會阻塞前臺的UI線程,所以程序界面不會失去響應(yīng)。
10.2 電話管理器(TelephonyManager)
TelephonyManager是一個管理手機通話狀態(tài)、電話網(wǎng)絡(luò)的服務(wù)類,該類提供了大量的getXxx()方法來獲取電話網(wǎng)絡(luò)的相關(guān)信息。
在程序中獲取TelephonyManager十分簡單,只要調(diào)用如下代碼即可:
接下來就可以通過TelephonyManager獲取相關(guān)信息或者進行相關(guān)操作了。
實例:獲取網(wǎng)絡(luò)和SIM卡信息
通過TelephonyManager提供的一系列方法即可獲取手機網(wǎng)絡(luò)、SIM卡的相關(guān)信息,該程序使用了一個ListView來顯示網(wǎng)絡(luò)和SIM卡的相關(guān)信息。
該程序代碼如下:
由于該應(yīng)用需要獲取手機位置和手機狀態(tài),因此程序還需要在AndroidManifest.xml文件中增加如下配置:
10.3 短信管理器(SmsManager)
SmsManager是Android提供的的另一個非常常見的服務(wù),SmsManager提供了一系列sendXxxMessage()方法用于發(fā)送短信,不過就現(xiàn)在實際應(yīng)用來看,短信通常是普通的文本內(nèi)容,也就是調(diào)用sendTextMessage()方法進行發(fā)送即可。
實例:發(fā)送短信
本實例程序十分簡單,程序提供了文本框讓用戶輸入收件人號碼,一個文本框讓用戶輸入短信內(nèi)容,接下來單擊“發(fā)送”按鈕即可短信發(fā)送出去。
從上面程序可以看出,使用SmsManager發(fā)送短信十分簡單,簡單地調(diào)用sendTextMessage()方法即可發(fā)送短信。
上面程序中用到了一個PendingIntent對象,PendingIntent是對Intent的包裝,一般通過調(diào)用PendingIntent的getActivity()、getService()、getBroadcastReceiver()靜態(tài)方法來獲取PpendingIntent對象。與Intent對象不同的是,PendingIntent通常回傳給其他應(yīng)用組件,從而有其他應(yīng)用程序來執(zhí)行PendingIntent所包裝的Intent。
該程序需要調(diào)用SMsManager來發(fā)送短信,因此還需要授予程序發(fā)送短信的權(quán)限,也就是在AndroidManifest.xml文件中增加如下代碼: