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文件中增加如下代碼: