一個Service也是一種應用程序組件,它運行在後台以提供某種服務,通常不具有可見的用戶界面。其它的應用程序組件可以啟動一個Service,即使在用戶切換到另外一個應用程序後,這個Service還是一直會在後台運行。此外,一個應用程序也可以綁定到一個Service然後使用進程間通信(IPC)方式與Service之間發生交互。例如一個Service可以處理網絡事物,播放音樂,讀寫文件或者讀寫ContentProvider,所以這些都在後台運行。
一個Service可以以以下兩種形式存在:
· Started(啟動) 在一個應用程序以startService() 來啟動一個Service時,這個Service將處於“Started”狀態。一旦啟動,這個Service可以在後台一直運行下去,即使啟動它的應用程序已推出。通常,一個處於“started”狀態的Service完成某個功能而不會給啟動它的應用程序組件返回結果。比如,這個服務(Service)可能是上載或是下載一個文件,當任務完成後,服務自行退出。
· Bound (綁定) 當一個應用程序組件以bindService() 綁定一個Service時,這個Service處於“Bound”狀態。處於“Bound”狀態的Service提供了一個客戶/服務(C/S)調用接口支持其它應用程序組件和它交互,比如發生請求,返回結果,或者使用IPC完成跨進程間通信。一個處於“Bound”的Service只能和與其綁定的應用程序一起運行。多個應用程序組件可以綁定到同一個Service。當所有綁定的應用程序組件都退出綁定後,被“綁定”的Service將被銷毀。
對於一個Service來說,它可以是“Started”,“Bound”或者同時處於兩種狀態。其它任一應用程序組件(比如一個Activity)都可以使用這個Service,即使其它應用程序組件是在不同的應用程序中。當然你可以把Service定義為私有的,這樣其它應用程序就無法使用你定義的Service。
要注意的是,一個Service運行在其宿主進程的主線程中--服務不會自己創建新的線程也不運行在獨立的進程中(除非你特別指明)。這意味著,如果你的Service需要完成一些很耗CPU資源的操作(比如播放MP3,或者使用網絡),你應該在Service中創建新的線程來完成這些工作,從而降低出現程序無響應(ANR)的風險。
Service基礎
要創建一個Service,你必須從Service或是其某個子類派生子類。在你的Service子類實現中,你需要重載一些方法以支持Service重要的幾個生命周期函數和支持其它應用組件綁定的方法。下面給出幾個需要重載的重要方法:
· onStartCommand() Android系統在有其它應用程序組件使用startService()請求啟動Service時調用。一旦這個方法被調用,Service處於“Started”狀態並可以一直運行下去。如果你實現了這個方法,你需要在Service任務完成時調用stopSelf()或是stopService()來終止服務。如果你只支持“綁定”模式的服務,你可以不實現這個方法。
· onBind() Android系統中有其他應用程序組件使用bindService()來綁定你的服務時調用。在你實現這個方法時,你需要提供一個IBinder接口以支持客戶端和服務之間通信。你必須實現這個方法,如果你不打算支持“綁定”,返回Null即可。
· onCreate() Android系統中創建Service實例時調用,一般在這裡初始化一些只需單次設置的過程(在onStartCommand和onBind()之前調用),如果你的Service已在運行狀態,這個方法不會被調用。
· onDestroy() Android系統中Service不再需要,需要銷毀前調用。在你的實現中你需要釋放一些諸如線程,注冊過的listener,receiver等,這是Service被調用的最後一個方法。
如果一個Service是由startService()啟動的(這時 onStartCommand()將被調用),這個Service將一直運行直到調用stopSelf()或其它應用部件調用stopService()為止。
如果一個部件調用bindService()創建一個Service(此時onStartCommand()不會調用),這個Service運行的時間和綁定它的組件一樣長。一旦其他組件解除綁定,系統將銷毀這個Service。
Android系統只會在系統內存過低且不得不為當前活動的Activity恢復系統資源時才可能強制終止某個Service。如果這個Service綁定到一個活動的Activity,基本上不會被強制清除。如果一個Service被申明成“後台運行”,就幾乎沒有被銷毀的可能。否則的話,如果Service啟動後並長期運行,系統將隨著時間的增加降低其在後台任務中的優先級,其被殺死的可能性越大。如過你的Service是做為“Started”狀態運行,你必須設計後如果在系統重啟服務時優雅退出。如果系統殺死你的服務,系統將在系統資源恢復正常時重啟你的服務(當然這也取決於onStartCommand()的返回值)。
在Manifest中申明Service
和Activity一樣,你必須在Manifest文件中申明應用中使用到的Service。為了聲明一個Service,你需要定義<application>的子元素<service>,比如:
[html]
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
<service>還包括一些其它屬性,android:name是唯一一個必須定義的屬性,它定義了Service的類名。
和Activity一樣,你可以為Service定義Intent Filter以支持其它部件隱式調用該服務。如果你只打算在你自己的應用中使用某個Service,那麼就不要定義任何Intent Filter。在這種情況下,你必須明確指明Service的類名來啟動這個Service。此外,你可以通過設置android:exported屬性為false來明確說明該Service為私有,即使你為Service定義了Intent Filter ,其它應用也無法使用你的Service。
創建一個“Started”的Service
當其它應用程序組件使用startService()來啟動Service後,這個Service就成為“Started”的Service。這時Service的onStartCommand () 回調函數將會被調用。
當一個Service以startService()啟動後,處於“Started”狀態,其生命周期獨立於啟動這個Service的其它應用程序組件,並且可以在後台無限期運行,即使啟動它的其它應用程序組件已經退出。在這種情況下,Service可以調用stopSelf()或者其它程序部分調用stopService()來結束這個Service。
一個應用程序組件比如一個Activity可以通過傳入Intent使用startService()來啟動一個Service,這個Intent指明需要的服務並包含了Service需要的數據。Service在onStartCommand回調函數中可以訪問這個Intent。
要注意的是,Service缺省和申明它的應用程序使用同一個進程並且運行在主線程中,因此如果你的Service比較耗時的話,那麼這個Service會影響到應用的用戶響應性能。為避免這種現象,你應用在Service中創建新線程。
你可以使用下面兩個基類來創建一個“Started”的Service。
· Service 這是所有Service的基類,當你派生這個類時,使用新創建的線程來完成Service需要完成的工作非常重要,這是因為Service缺省也會使用你的應用程序的主線程,這可能會影響你應用程序的響應性能。
· IntentService 為Service的一個子類,它會使用一個工作線程來處理所有的請求,每次處理一個請求。這在你無需實現並行處理多個請求時是最好的選擇。你只需重載onHandleIntent()方法,這個方法接受每個請求,從而你可以在後台完成所需任務。
派生IntentService類
因為大部分“Started”的Service不需要並行處理多個請求,這時最好的選擇是派生自IntentService。
類IntentService可以完成以下工作:
· 創建一個工作線程來處理所有發送到onStartCommand()的請求(Intent),從而和應用的主線程分開。
· 創建一個工作隊列來逐個處理每個請求,從而無需考慮多線程編程。
· 在處理完所有請求後中止Service的運行,你無需調用stopSelf()來終止Service。
· 提供onBind()的缺省實現,返回null。
· 提供onStartCommand()的缺省實現,將接受到的Intent發送到工作隊列中,然後調用你的onHanleIntent()實現。
因此,對於你來說,只需要提供onHandleIntent()來處理發過來的Intent,當然你可能需要實現Service的一個簡單的構造函數。
下例為IntentService的一個簡單實現:
[java]
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must
* call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from
* the default worker thread with
* the intent that started the service.
* When this method returns,
* IntentService stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5 * 1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must
* call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from
* the default worker thread with
* the intent that started the service.
* When this method returns,
* IntentService stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5 * 1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
你需要實現的只是一個構造函數和重載onHandleIntent()。
如果你需要重載其它一些回調函數,比如onCreate(),onStartCommand()或是onDestroy(),注意調用其基類的實現。這樣IntentService才能正確管理工作線程的生命周期。
比如重載onStartCommand(),你必須返回基類的onStartCommand()的返回值,這樣Intent能夠傳遞到onHandleIntent()的原因:
[java]
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
除了onHandleIntent(),此外唯一可以不調用基類實現的回調函數為onBind(),你只要在你需要支持綁定服務時才需要實現該方法。
派生Service類
從上面可以看到使用IntentService可以讓實現“Started”的Service變的非常簡單,然而如果你需要支持使用多線程並行處理多個請求(而非使用工作隊列來依次處理請求),這時你就需要從Service類來派生。
作為比較,下面的例子使用Service實現和上面使用IntentService同樣的功能,也是使用一個工作線程來逐個處理每個請求,可以看成是IntentService的一個實現:
[java]
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
可以看出來,比使用IntentService多了很多工作。然而,由於你自己來處理onStartCommand(),你可以實現並行處理多個請求。當然這不是本例的實現,但是如果你願意,你可以為每個請求創建一個工作線程來立即處理請求而不使用工作隊列。
注意onStartCommand()方法必須返回一個整數,這個返回值定義了如果Android系統在殺死這個Service後又需要使用這個Service後的行為。這個返回值可以有下面幾種情況:
· START_NOT_STICKY 如果Android系統在onStartCommand() 返回後殺死了這個Service,系統不會重新創建這個Service,除非它是一個Pending Intent。這是在你的應支持簡單的重啟沒有完成的任務避免無必要的Service運行時ic最好的選項。
· START_STICKY 在Android系統在onStartCommand () 返回後,選擇重新創建這個Service並調用Service的onStartCommand () 方法,但不會重新發送上一次的請求。Android系統使用空Intent調用onStartCommand,除非它是一個PendingIntent。這種情況適用於一些媒體播放器服務。
· START_REDELIVER_INTENT在Android系統在onStartCommand() 返回後,選擇重新創建這個Service並調用Service的onStartCommand () 方法,並且重新發送上一次的請求。這適用一些比較活躍地進行某些工作的服務,需要立即恢復活動比如下載文件的服務。
啟動Service
在Activity或是其他應用程序組件可以通過傳遞Intent調用startService()方法來啟動一個Service。Android系統然後會調用Service的onStartCommand()方法並傳入這個Intent對象。
比如,一個Activity可以通過明確指明調用的服務來調用前面創建的HelloService:
[java]
Intent intent = new Intent(this, HelloService.class);
startService(intent);
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService() 調用後立即返回,Android系統調用Service的onStartCommand(),如果這個Service之前沒有運行,系統還將先調用Service的onCreate()方法,然後再調用onStartCommand()。
如果Service沒有提供綁定支持,那麼使用startService()是唯一可以啟動Service的方法。然而,如果你需要Service返回一個結果,那麼客戶端可以通過創建一個用於廣播的延遲Intent(PendingIntent)然後通過Intent發送請求到Service。Service可以利用這個廣播(Broadcast)返回結果。
多個啟動Service請求將導致多次調用Service的onStartCommand()方法。然而只要一個stopSelf或stopService來停止一個Service的運行。
停止Service
一個“Started”的Service必須管理自己的生命周期,也就是說,除非系統資源不足,Android系統不終止或停止一個Service。因此Service必須調用stopSelf () 方法來終止自身的運行,或者其它部件可以通過stopService () 方法來中止一個Service的運行。
一旦Service被請求終止,系統將盡快銷毀這個Service。
但是,如果你的Service需要在onStartCommand()並行處理多個請求,那麼你不應再處理完一個請求後就停止Service的運行,因為你可能還會接受到新的請求(在第一個請求處理完就結束則會終止第二個請求的執行)。為避免這種情況,你可以使用帶參數的stopSelf(int) 來確保終止Service的請求總是對應於最近的請求。也就是說,在調用stopSelf(int) 時,你通過傳入請求的Id (傳給onStartCommand 方法的 startId )來終止對應的請求處理。那麼如果Service在調用stopSelf (int) 前接受到一個新的請求,那麼Id將不會匹配,因此Service也就不會結束。
創建一個支持“綁定”的Service
一個支持“綁定”的Service是指運行其它應用程序之間通過調用bindService() 來綁定到它的Service。支持“綁定”的Service在設計時通常不允許直接使用startService() 來啟動它。
在Activity或其它組件需要和一個Service發生交互時,你應該創建一個“綁定”的Service,在這個Service中你可以通過進程間通信接口(IPC)來提供可供其它應用程序組件使用的功能接口。
為了創建一個支持“綁定”的Service,你需要實現onBind() 回調函數並返回一個IBind接口對象,這個對象提供了其它部分可以訪問這個Service的服務接口。其它應用程序組件然後可以通過bindService () 方法獲得Service的服務接口對象,然後使用這個對象的方法來調用服務。通常“綁定”的Service的運行周期和綁定它的其它應用程序組件的生命周期是一樣的。因此如果這個Service不再有應用程序組件與之綁定,Android系統會自動銷毀這個Service,而你自己無需停止這個Service。
為了創建一個支持“綁定”的Service所需做的第一件事是定義可供客戶端調用的服務接口。這個接口必須為一個IBinder接口,並且你的Service必須通過onBind () 返回這個接口對象。一旦客戶端獲得這個IBinder接口,就可以通過這個接口對象來使用服務。
多個客戶端可以同時綁定到同一個Service,當一個客服端使用好所需的服務後,調用onbindService() 來解除與Service之間的綁定關系。當一個Service沒有和它綁定的客戶端後,Android系統銷毀這個Service。
給用戶發送通知
Service通常是在後台運行,它可以通過Toast 通知或是狀態條通知來提醒用戶發生了某種事件。
一個Toast通知為覆蓋在當前屏幕上短時間顯示的消息,而狀態條通知可以在狀態條以圖標和文字的方法顯示,用戶可以選擇是否查閱這個通知並作出處理(比如啟動一個Activity)。
一般來說,使用狀態條通知是在某個後台工作完成後(比如文件下載完畢)通知用戶的最好方法。用戶可以在擴展後的通知窗口選擇某個通知,然後可以點擊是否啟動一個Activity(比如查看下載好的文件)
在前台運行Service
通常Service在後台運行,但有時某些Service是用戶明確知道在運行,即使在系統資源不足時,也不該做為銷毀的候選Service,這種Service可以設置成在前台運行,在前台運行的Service必須在狀態條顯示通知,除非Service停止或者轉入後台,這種通知不可以被忽視。
比如,如果一個音樂播放器使用Service來播放,這個Service應該被放在前台運行,因為用戶很清楚的知道音樂在播放,而在狀態條顯示的通知理當顯示當前播放的樂曲並且允許用戶啟動一個Activity來操作這個音樂播放器。
要把一個Service放在前台運行,可以調用startForeground()。這個方法接受兩個參數:一個整數來唯一標識一個通知,和顯示在狀態條上的通知對象。
例如:
要使一個Service推出前台運行,調用stopForeground(). 這個方法接受一個boolean參數,指明是否同時移除狀態條上的通知。 這個方法並不會停止Service的運行。但如果你停止一個在前台運行的Service,那麼會同時移除狀態條的通知。
管理Service的生命周期
和Activity相比,Service的生命周期要簡單很多。但你更要關注你的Service是如何創建和銷毀的,這是因為Service可以在用戶不知道的情況下在後台運行。
Service的生命周期,從其創建後到銷毀,可以有兩條不同的路徑:
“Started”的Service 這個Service是由其它的應用程序組件調用startService()創建的,這個service 可以在後台無限期運行直到調用 stopSelf()或者其它組件調用stopService()來停止它。當Service停止時,系統將銷毀這個Service。
支持“綁定”的Service 這種Service是在其它組件調用 bindService()綁定它時創建,客戶端然後可以通過服務接口和Serivce通信。客戶端可以調用 unbindService () 解除與Service的綁定。多個客戶端可以同時綁定同一Service,當一個Service沒有客戶端和它綁定時,系統則銷毀這個Service。
這兩條路徑並不是完全分開的,也就是說,你可以去綁定一個已經是“Started”狀態的Service。比如,一個通過媒體播放的Service可能通過指明需要播放音樂的Intent然後調用startService() 啟動的。再往後,用戶可能打算來操作媒體播放器,那麼一個Activity可以調用 bindServive()來綁定這個Service。在這種情況下,stopSelf()或stopSelf()並不真正停止Service直到所有的客戶端都解除與Service的綁定。
實現生命周期回調函數
和Activity類似,Service也提供了一些回調函數接口允許你重載這些方法來監視Service狀態的變化並添加合適的處理,下面代碼給出一個Service生命周期回調函數實現的框架:
[java]
package com.example.bookdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class ExampleService extends Service {
// indicates how to behave if the service is killed
int mStartMode;
// interface for clients that bind
IBinder mBinder;
// indicates whether onRebind should be used
boolean mAllowRebind;
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent,
int flags, int startId) {
// The service is starting,
// due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service
// with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
package com.example.bookdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class ExampleService extends Service {
// indicates how to behave if the service is killed
int mStartMode;
// interface for clients that bind
IBinder mBinder;
// indicates whether onRebind should be used
boolean mAllowRebind;
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent,
int flags, int startId) {
// The service is starting,
// due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service
// with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}