第十章 Service與BroadcastReceiver

Service是Android四大組件中與Activity最相似的組件,他們都代表可執行的程序,Service與Activity的區別在于:Service一直在后臺運行,他沒有用戶界面,所以絕不會到前臺來,一旦Service被啟動起來,他就與Activity一樣,他完全具有自己的生命周期。關于程序中Activity和Service的選擇標準是:如果某個程序需要在運行時向用戶呈現某種界面,或者該程序需要與用戶交互,就需要使用Activity;否則就應該考慮使用Service了。

開發者開發Service的步驟與開發Activity的步驟很像,開發Service組件需要先開發一個Service子類,然后在AndroidManifest.xml文件中配置該Service,配置時通過<intent-filter...../>。元素指定它可被那些Intent啟動。

Android系統本身提供了大量的Service組件,開發者可通過這些系統Service來操作Android系統本身。

本章還將向讀者介紹BroadcastReceiver組件。BroadcastReceiver組件就像一個全局的事件監聽器,只不過用于監聽系統Broadcast。通過是BroadcastReceiver,即可在不同應用程序之間通訊。

10.1 Service簡介

Service組件也是可執行得讓程序,他有自己的生命周期。創建、配置Service與創建、配置Activity的過程基本相似,下面詳細介紹Android Service的開發。

10.1 創建、配置Service

就像開發Activity需要兩個步驟:

(1)開發Activity子類;

2、在AndroidManifest.xml文件中配置Activity。開發Service也需要兩個步驟。

(1)定義一個繼承Service的子類。

(2)在AndroidManifest.xml文件中配置該Service

Service與Activity還有一些相似之處,它們都是從Content派生出來的,因此它們都可以調用Content里定義的如getResources、getContentResolver()等方法。

與Activity相似的是,Service中也定義了一系列生命周期方法,如下所示:

1、 IBinder onBind(Intent intent):該方法是Service之類必須實現的方法。該方法返回一個IBinder對象,應用程序可通過該對象與Service組件通訊。

2、 void onCreat():在該Service第一次被創建后立即回調該方法。

3、 void onDestroy():在該Service第一次被創建后將立即回調該方法。

4、 void onStartCommand(Intent intent,int flags,int starId):該方法的在其版本是Void onStart(Intent intent,int startId),每次客服端調用startService(Intent)方法啟動該Service時都會回調該方法。

5、 boolean onUnbind(Intent intent):當該Service上綁定的所有客戶端都斷開鏈接時將會回調該方法。

下面的類定義了一個Service

提示:

上面這個Service什么也沒干,但實際上它是Service組建的框架,如果希望Service組件做某些事情,那么只要在onCreat()或onStartCommand()方法中定義相關業務代碼即可。

定義了上面的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沒有關聯,即使訪問者退出了,Service也仍然運行。

2、通過Content的bindService()方法:使用該方法啟動Service,訪問者與Service綁定在一起,訪問者退出了,Service也就終止了。

下面先示范第一種方式運行Service

從上面程序中的粗字體代碼不難看出,啟動、關閉Service十分簡單,調用Content里定義的startService、stopService()方法即可啟動、關閉Service。

提示:

可能讀者會注意到本程序使用了顯示Intent(直接指定要啟動的目標組件實現類)來啟動Service,單本書第二版使用了隱士Intent啟動Service。這是因為從Android5.0開始,Google要求必須使用顯示Intent啟動Service組件。


運行該程序,通過程序界面先啟動Service,在關閉Service,將可以在AS的Logcat面板看到如上圖所示的輸出。

如果不關閉Service的情況下,連續三次單擊“啟動Service”按鈕,

從上圖可以看出,每當Service被創建時會回調onCreat()方法,每次Service被啟動時都會回調onStartCommand()方法-----多次啟動一個已有的Service組件將不會在回調onCreat()方法,但每次啟動都會回調onStartCommand()。

10.1.3 綁定本地Service并與之通訊

當程序通過starService()和stopService()啟動、關閉Service時,Service與訪問者之間基本上不存在太多的關聯,因此Service和訪問者之間也無法進行通訊、交換數據。

如果Service和調用者之間需要數據交換,則應該使用bindService和unbindService()方法啟動和關閉Service。

Content的bindService()方法完整的方法簽名是:bindService(Intent service,ServiceConnection conn,int flags),該方法的三個參數解釋如下。

1、Service:該參數通過Intent指定要啟動的Service。

2、conn:該參數是一個Serviceconnection對象,該對象用于監聽訪問者與Service之間的連接情況。當訪問者與Service之間連接成功時將回調該ServiceConnection對象的onServiceConnected(CompontName name,Ibinder service)方法;當Service所在的宿主進程由于異常終止或其他原因終止,導致該Service與訪問者之間斷開連接時回調該ServiceConnection對象的onServiceDisconnected(ComponentName name)方法。

注意:

當調用者主動通過unBindService()方法斷開與Service的連接時,ServiceConnection對象的onSrviceDisconnected(ComponentName name)方法并不會被調用。

flags:指定綁定時是否自動創建Service(如果Service還未創建)。該參數可指定為0(不自動創建)或BIND_AUTO_CREATE(自動創建)。

注意到Serviceconnection對象的onServiceConnected()方法中有一個IBinder對象,該對象即可實現與被綁定Service之間的通訊。

當開發Service類時,該service必須提供一個IBinder onBind(Intent intent)方法,在綁定本地Service情況下,onBind(Intent intent)方法返回的IBinder對象將會傳給ServiceConnection對象里onServiceConnected(CompontName name,Ibinder service)方法的Service參數,這樣訪問者就可通過該IBinder對象與Service進行通訊了。

提示:

IBinder對象相當于Service組件的內部鉤子,該鉤子關聯到綁定的Service組件,當其他程序組件綁定該Service時,Service將會把IBinder對象返回給其他程序組件,其他程序組件通過該IBinder對象即可與Service組件進行實時通訊。

實際上開發時通常會采用繼承Binder(IBinder的實現類)的方式實現自己的IBinder對象。下面的程序示范了如何在Activity中綁定本地Service,并獲取Service的運行狀態。該程序的Service類需要“真正”實現onBind()方法,并讓該方法返回一個有效的IBinder對象。該Service類的代碼如下。

上面Service類的粗體字代碼實現了onBind()方法,該方法返回了一個可訪問該Service狀態數據(count值)的Binder對象,該對象被傳給該Service的訪問者。

上面程序中的通過繼承Binder雷山縣了一個IBinder對象,這個MyBinder類是Service的內部類,這對于本地綁定Service并與之通訊的場景的一種常見情形。

接下來定義一個Activity來綁定該Service,并在該Activity中通過MyBinder對象訪問Service內部狀態。該Activity的界面上包含三個按鈕,第一個按鈕用于綁定Service;第二個按鈕用于接觸綁定;第三個按鈕則用于獲取Service的運行狀態。該Activity的代碼如下。

上面程序中

這段代碼用于在該Activity與Service鏈接成功時獲取Service的onBind()方法所返回的MyBinder對象;

這段代碼即可通過myBinder對象來訪問Service的運行狀態了。

運行該程序,單擊程序界面中的綁定"Service"按鈕,即可看到Android Studio的logcat有如圖10.3所示的輸出。

在該Activity中綁定Service之后,該Activity還可通過MyBinder對象來獲取Service的運行狀態,如果用戶單擊程序界面上的"獲取Service狀態"按鈕,即可看到如圖10.4所示的輸出

圖 10.4

從圖10.4所示的輸出可以看到,該Activity可以非常方便地訪問到Service的運行狀態。雖然本程序只是一個簡單的示例,該Activity只是訪問了Service的一個簡單count值,但實際上完全可以讓Mybinder去操作Service中更多的數據-----到底需要訪問Service的多少數據,完全取決于實際業務需要。

提示

對于Servicer的onBind()方法所返回的IBinder對象來說,它可以被當成該Service組件所返回的代理對象,Service允許客戶端通過該IBinder對象來訪問內部的數據,這樣即可實現客戶端與Service之間的通訊。

如果我們單擊程序界面上的"解除綁定"按鈕,即可在AS的logCat中看到如圖10.5所示輸出

圖10.5(解除綁定)

正如10.5中所示,當程序調用unBindService()方法解除對某個Service的綁定時,系統會先回調該Service的onUnbinde()方法,然后調用onDestry()方法。

與多次調用startService()方法啟動Service()方法啟動Service不同的是,多次調用bindService()方法并不會執行重復綁定。對于前一個示例程序,用戶每單擊"啟動"按鈕一次,系統就會回調Service的onStartCommand()方法一次;對于這個示例程序,不管用戶單擊"綁定Service”按鈕多少次,系統只會回調Service的onBiond()方法一次。

10.1.4 Service的生命周期

通過前面兩個示例,讀者應該大致明白Service的生命周期了。隨著應用程序啟動Service方式不同,Service的生命周期也略有不同。

如果應用程序通過startService()方法來啟動Service,Service的生命周期如圖10.6左邊所示。如果應用程序通過bindService方法來啟動Service的生命周期如右圖所示:

Service的生命周期還有一個特殊的情形--------如果Service已由某個客戶端通過startService()方法啟動了。接下來其他客戶端調用bindService()方法綁定該Service之后,再調用unbinService()方法解除綁定,最后有調用binService()方法再次綁定到Service,這個過程所觸發的生命周期方法如下所示onCreat()---- onStartCommand() ?---- onBind() ---- onUnbind()(重寫該方法時返回了true) ---- onRebind(),在上面這個觸發過程中,onCreat()是創建該Service后立即調用的,只有當該service被創建時才會被調用;onStartCommand()方法則是有客戶端調用startService()方法時觸發的。圖10.7所示的logcat顯示了上面生命周期的輸出。

圖10.7

在圖10.7所示的輸出中,可以看到Service的onRebind()方法被回調了。如果希望該方法被回調,除了需要該Service是由Activity的startService()方法啟動之外,還需要Service子類重寫onUnbind()方法時返回true。

注意:

在圖10.7所示輸出中,并沒有發現Service回調onDestroy()方法,這是因為該Service并不是由Activity通過bindService()方法啟動的(該Service事先已由Activity通過startActivity()方法啟動了),因此當Activity調用unBindService方法取消與該Service的綁定時,該Service也不會終止。

10.1.5 使用IntentService

IntentService是Service的子類,因此它不是普通的Service,它比普通的Service增加了額外的功能。

先看Service本身存在的兩個問題。

1、Service不會專門啟動一個單獨的進程,Service與它塑造應用位于一個進程中。

2、Service不是一條新的線程,因此不應該在Service中直接處理耗時的任務。

提示:

如果開發者需要在Service中處理耗時任務,建議在Service中另外啟動一條新的線程來處理該耗時任務。就像在前面BindService中所看到的,程序在BindService的onCreate()方法中啟動了一條新線程來處理耗時任務??赡苡凶x者感到疑惑:直接在其他程序組件中啟動子線程來處理耗時任務不行嗎?這種方式也不可靠,由于Activity可能會被用戶退出,而BraodcastReceiver的生命周期本身就很短。可能出現的情況是:在子線程還沒結束的情況下,Activity已經被用戶退出了,或者BroadcastReceiver已經結束了。在Activity已經退出、BroadcastReceiver已經結束的情況下,此時他們所在的進程就變成了空進程(沒有任何活動組件的進程),系統需要內存時可能會優先終止該進程,那么進程內的所有子線程也會被終止,這樣就可能導致子線程無法執行完成。

而IntentService正好可以彌補Service的上述兩個不足:IntentService將會使用隊列來管理請求Intent,每當客戶端代碼通過Intent請求啟動IntentService時,IntentServiec會將該Intent加入隊列中,然后開啟一條新的worker線程來處理該IntentService。對于異步的startService()請求,IntentService會次序依次處理隊列中的Intent,該線程保證同一時刻只處理一個Intent。由于IntentService使用新的orker線程處理Intent請求,因此IntentService不會阻塞主線程,所以IntentService自己就可以處理耗時任務。

歸納起來,IntentService具有如下特請求征:

1、IntentService會創建單獨的worker線程來處理所有的Intent請求

2、IntentService匯創建單獨的worker線程來處理onHandleIntent方法的實現代碼,因此開發者無需處多線程的問題。

3、當所有請求處理完成后。IntentService會自動停止,因此開發者無須調用stopSelf()方法來停止該Service。

4、為Service的onBind()方法提供了默認實現,默認實現的onBind()方法返回null。

5、為Service的onStartCommand()方法提供了默認實現,該實現將請求Intent添加到隊列中,

從上面的介紹可以看出,擴展IntentService實現Service無須重寫onBind()、onStartCommand方法,只要重寫onHandleIntent()方法即可。

下面的示例程序界面中包含了兩個按鈕,分別用于啟動普通Service和IntentService,兩個Service都需要處理耗時任務。該程序的界面布局代碼很簡單,這里不再給出。主程序Activity代碼:

上面Activity的兩個事件處理方法中分別啟動了MyService和MyIntentService,其中MyService是繼承service的子類,而MyIntentService則是繼承IntentService的子類。

下面是MyService類的代碼。

上面MySerevice在onStartCommand()方法中使用線程暫停的方式模擬了耗時任務,該線程暫停了20秒,相當于該耗時任務需要執行20秒,由于普通Service的執行會阻塞主線程,因此啟動該線程將會導致程序出現ANR(Application Not Responding)異常。

下面是MyIntentService類的代碼

從上面的代碼可以看出,MyIntentService繼承了IntentService,并不需要實現onBind()、onStartCommand()方法,只要實現onHandleIntent()方法即可,在該方法中定義該Service需要完成的認為誒。本示例的onHandleIntent()方法也用線程暫停的方式模擬了耗時任務,線程同樣暫停了20秒。但由于IntentService會使用單獨的線程來完成該耗時任務,因此啟動MyIntentService不會阻塞前臺線程

運行該示例,如果單擊界面上的“啟動普通Service”按鈕,竟會激發startService()方法,該方法竟會啟動MyService去執行耗時任務,此時將會導致程序UI線程被阻塞(程序界面失去響應),而且由于阻塞時間太長,因此將會看到如圖10.8所示的ANR異常。

相反,如果調用“啟動IntentService”來啟動MyIntentService,雖然MyIntentService也需要執行耗時任務,但由于MyService會使用單獨的worker線程,因此MyIntentService不會阻塞前臺的UI線程,所以程序界面不會失去響應。

10.2 電話管理器(TelephonyManager)

TelephonyManager是一個管理手機通話狀態、電話網絡的服務類,該類提供了大量的getXxx()方法來獲取電話網絡的相關信息。

在程序中獲取TelephonyManager十分簡單,只要調用如下代碼即可:

接下來就可以通過TelephonyManager獲取相關信息或者進行相關操作了。

實例:獲取網絡和SIM卡信息

通過TelephonyManager提供的一系列方法即可獲取手機網絡、SIM卡的相關信息,該程序使用了一個ListView來顯示網絡和SIM卡的相關信息。

該程序代碼如下:

由于該應用需要獲取手機位置和手機狀態,因此程序還需要在AndroidManifest.xml文件中增加如下配置:

10.3 短信管理器(SmsManager)

SmsManager是Android提供的的另一個非常常見的服務,SmsManager提供了一系列sendXxxMessage()方法用于發送短信,不過就現在實際應用來看,短信通常是普通的文本內容,也就是調用sendTextMessage()方法進行發送即可。

實例:發送短信

本實例程序十分簡單,程序提供了文本框讓用戶輸入收件人號碼,一個文本框讓用戶輸入短信內容,接下來單擊“發送”按鈕即可短信發送出去。

從上面程序可以看出,使用SmsManager發送短信十分簡單,簡單地調用sendTextMessage()方法即可發送短信。

上面程序中用到了一個PendingIntent對象,PendingIntent是對Intent的包裝,一般通過調用PendingIntent的getActivity()、getService()、getBroadcastReceiver()靜態方法來獲取PpendingIntent對象。與Intent對象不同的是,PendingIntent通?;貍鹘o其他應用組件,從而有其他應用程序來執行PendingIntent所包裝的Intent。

該程序需要調用SMsManager來發送短信,因此還需要授予程序發送短信的權限,也就是在AndroidManifest.xml文件中增加如下代碼:

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容