Service是運行在後臺的組件,它不能直接給用戶提供服務。其他組件可以啓動Service,即使是切換至別的應用,Service仍然可以在後臺運行,組件可以與Service綁定並且與之交互,甚至可以跨進程通信(IPC)。如,Service可以在後臺進行網絡請求,播放音樂,或者進行文件讀寫操作,又或者和Content Provider交互等等。
這篇博客將介紹Service的定義,創建,啓動,綁定等內容。
Service的兩種方式啓動:
Started:組件通過調用startService()方法啓動Service,啓動後,Service會在後臺一直運行,就算是喚起Service的啓動者被銷燬,Service也依然可以在後臺運行。一般情況下,一個被start的Service會在後臺執行單獨的操作,也並不會給啓動者返回結果。例如:一個start的Service在後臺執行下載或者是上傳文件的操作,完成之後,Service應當自己停止。
Bound:其他組件調用bindService()方法綁定一個Service。綁定啓動的Service是Service-Client的結構,也就是說Service可以與多個組件進行交互。一個bound Service只在有組件綁定時纔會運行,無組件綁定時,服務被銷燬。
Service也可以同時以上面兩種方式下啓動。這裏涉及到serivice中的兩個方法:onStartCommand(),onBind()
無論是哪種方式啓動Service(單獨start , 單獨 bind , 同時 bind 和 start),任何組件都可以喚起 Service,通過Intent對象傳遞參數,不過,我們也可以將Service在配置文件(manifest.xml)中配置爲私有,這樣不允許其他應用訪問它。
注:Service運行在主線程中,所以說Service不是個新的線程,也不是新的進程。也就是說,如果我們需要在Service中執行比較耗時的操作時(如音樂播放,網絡請求等等),這就需要在Service中另外創建一個新的線程。這麼做可以防止異常,保證主線程的流暢操作。
那麼Service和Thread處理,哪個更優呢?
Service 是運行在後臺的組件,並不能直接與用戶交互。我們只需要在需要用上的時候才創建Service.
當用戶與UI進行交互時,如果要執行一個主線程無法完成的操作時,就應該創建一個線程。如:當Activity在運行,而我們需要播放音樂,這時候就應該創建線程。最後在Activity消耗時,別忘了銷燬這個線程。我們也可以選擇AsyncTask或者是HandlerThread來替代Thread。
Service基礎內容
我們爲了創建新的Service,就要繼承Service。並且重寫一些方法。這些方法就代表了Service的生命週期,並且這些方法中提供了綁定Service的機制。如下:
- onStartCommand(): 當其他組件調用StartService()喚起Service時,這個方法會被調用。只要Service啓動,Service便會在後臺一直獨立的運行。在Service執行完後,應該調用stopSelf()或者是stopService()去銷燬Service。
- onBind(): 當其他組件調用bindService()綁定Service時,這個方法會被調用,並且會返回一個IBinder接口,IBinder接口是Service與其綁定者進行交互的橋樑。如果Service沒有綁定者,則返回null。
- onCreate(): 在Service首次創建時,啓動這個方法。方法只被觸發一次, 並且是在上述兩種方法啓動前被調用。如果Service已經在運行,那麼該方法不會執行。
- onDestory() : 在service 消耗時調用,在消耗的方法中,我們應當要釋放之前佔用的資源。比如:停止線程。解除綁定的監聽器或者是廣播接收器等等。這個方法是Service的最後一個調用方法。
如果有組件調用startService()啓動Service(那麼系統會調用onStartCommand()方法),直到調用stopSelf()或者是調用stopService()方法,Service纔會被停止。
如果有組件調用bindService()綁定了Service(這裏系統並不會調用onStartCommand()方法),只要有組件綁定着Service,那麼這個Service就會一直運行,當無組件綁定Service時,Service會被消耗。
在系統內存不夠時,系統將強制銷燬Service。如果Service綁定了Activity,而這個Activity正在和用戶交互,那麼Service將有可能不會被系統清理掉。假如創建的是前臺Service,那麼Service幾乎不會被殺死。當創建的Service運行了很長時間後,那麼系統將可能會降低它在棧中的級別–從而使得它更容易被銷燬掉。因此,在做Service時,應當讓Service時常被restart,那麼這樣就可以保持Service的級別。
註冊Service
在manifest.xml文件中:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
另外,在標籤中還可以配置別的屬性,例如:需要啓動Service所需要的權限,該Service應在哪個進程中運行等等。這裏 android:name是不能缺少的,這是指定Service的類名。一旦發佈了應用,類名將不可更改。
注:爲了保證應用的安全性,推薦使用顯式的Intent啓動或者綁定Service,不要在中配置 intent-filter。
將android:exported設置爲false時,表示不允許別的應用啓動本應用的組件,即使是顯示的Intent也是不可以的。
繼承IntentService
在很多情況下,啓動Service 並不會去同時處理多個請求,因爲處理多個線程是非常危險的,這個時候選擇繼承IntentService就是不錯的解決方案。
使用IntentService要注意以下幾點:
- 默認在子線程中處理回傳到onStartCommand()方法中的Intent;
- 在OnHanleIntent()中處理按時間排序的Intent隊列,因此不必擔心多線程問題。
- 當請求處理後,停止Service,無需手動調用stopSelf()
- 默認是實現了onBind()方法,返回爲空、
- 默認是實現了onStartCommand方法,並且將回傳的Intent以序列的形式發送給onHandleIntent(),只需要重寫該方法並且處理Intent就可以了。
綜上,我們只需要重寫OnhandleIntent()就可以了,不過,還少不了一個構造方法,如下:
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) {
}
}
}
}
}
如果你想在IntnetService的繼承類中重寫其他生命週期方法,如:onCreate(),onstartCommand(),onDestroy(),那麼先調用各自父類方法來保證子線程能夠正常啓動。
比如,要實現onStartCommand()方法,需要返回其父類方法,如:
@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
如果你需要在Service中執行多線程而不是處理一個請求隊列,那麼就需要繼承Service類,分別處理每個Intent。
在Service進行操作時,每個請求都應當開啓一個線程,並且在同一時刻,一個線程只處理一個請求。如:
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();
}
}
注意OnStartCommand返回一個整形變量,該變量必須是下列常量之一:
START_NOT_STICKY:如果執行完了onStartCommand後,系統就會消耗Service,不用再重新創建Service,除非系統回傳了一惡搞pending intent。這樣可以避免在不必要的時候運行Service。
START_STICKY:如果系統在onStartCommand()執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()。dangerous不要重新傳遞最後一個Intent(do not redeliver the last intent)。相反,系統回調onStartCommand()時會回傳一個空的Intent,除非有 pending intents傳遞,否則Intent將爲null。該模式適合做一些類似播放音樂的操作。
START_REDELIVER_INTENT:如果系統在onStartCommand()執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()並將最後一個Intent回傳至該方法。任何 pending intents都會被輪流傳遞。該模式適合做一些類似下載文件的操作。
啓動Service
若需要啓動Service,如:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService(intent)方法將會立即返回,並且回調onStartCommand()(請不要手動調用該方法),如果該Service未處於運行狀態,那麼系統將會首先回調onCreate(),接着再回調onStartCommand()。若希望Service可以返回結果,那麼需要通過調用getBroadcast返回的PendingIntent啓動Service(將PendingIntent包裝爲Intent),service可使用broadcast 傳遞結果。
多個啓動Service的請求可能導致onStartCommand()多次調用,但只需調用stopSelf() 、 stopService()這兩個方法之一,就可停止該服務。
停止Service
一個啓動的Service必須管理自己的生命週期。系統不會主動stop或destroy一個正在運行的Service,除非系統內存緊張,不然的話,執行完onStartCommand()方法後,Service會一直運行。停止Service必須手動調用stopSelf()(在Service中)或調用stopService()(在啓動組件中)。
一旦調用了上述兩種方法之一,系統會盡快destroy該Service。
若系統正在處理多個調用onStartCommand()請求,那麼在啓動一個請求時,您不應當在此時停止該Service。爲了避免這個問題,可以調用stopSelf()方法,以確保請求停止的Service時最新的啓動請求。當調用stopSelf()方法時,傳入的ID表示啓動請求(該ID會傳遞至onStartCommand()),該ID與請求停止的ID一致。則如果在調用stopSelf()之前,Service收到一個新的Start請求,ID將無法匹配,Service並不會停止。
爲了節省內存和電量,當Service完成其工作後將其stop很有必要。如有必要,可以在其他組件中調用stopService()方法,即便Service處於綁定狀態,只要它回調過onStartCommand(),也應當主動停止該Service。
綁定Service
通過其他組件調用bindService()方法可以綁定一個Service以保持長連接,這時一般不允許其他組件調用startService()啓動Service。
當其他組件需要與Service交互或者需要跨進程通信時,可以創建一個bound Service。
爲創建一個bound Service,必須重寫onbind回調,該方法返回一個IBinder接口。該接口時組件與Service通信的橋樑。組件調用bindService()與Service綁定,該組件可獲取IBinder接口,一旦獲取該接口,就可以調用Service中的方法。一旦沒有組件與Service綁定,系統將destroy它,我們不必手動停止它。
爲創建一個bound Service,必須定義一個接口 ,該接口指定組件與Service如何通信。定義的接口在組件與Service之間,且必須實現IBinder接口。這正是onBind()的返回值。一旦組件接收了IBinder,組件與Service便可以開始通信。
多個組件可同時與Service綁定,當組件與Service交互結束後,可調用unbindService()方法解綁。bound Service比start Service要複雜,故我將在後續單獨翻譯。
發通知
運行中的Service可以通過Toast Notifications 或 Status Bar Notifications 向用戶發送通知。Toast是一個可以短時間彈出的提醒框。Status Bar是頂部狀態欄中出現的太有圖標的信息,用戶可以通過下拉狀態欄獲得具體信息並執行某些操作(如啓動Activity)。
通常,Status Bar用於通知某些操作已經完成,如下載文件完成。當用戶下拉狀態欄後,點擊該通知,可獲取詳細內容,如查看該下載的文件。
前臺Service
前臺Service用於動態通知消息,如天氣預報。該Service不易被kill。前臺Service必須提供status bar,只有前臺Service被destroy後,status bar才能消失。
舉例來說,一個播放音樂的Service必須是前臺Service,只有這樣用戶才能確知其運行狀態。爲前臺Service提供的status bar可以顯示當前音樂的播放狀態,並可以啓動播放音樂的Activity。
調用startForeground()可以啓動前臺Service。該方法接收兩個參數,參數一是一個int型變量,用戶指定該通知的唯一性標識,而參數而是一個Notification用於配置status bar,示例如下:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
調用stopForeground()來移除(remove)前臺Service。該方法需傳入一個boolean型變量,表示是否也一併清除status bar上的notification(indicating whether to remove the status bar notification as well)。該方法並不停止Service,如果停止正在前臺運行的Service,那麼notification 也一併被清除。
管理Service的生命週期
從Service的啓動到銷燬,有兩種流程:
A started service:需手動停止
A bound service:可自動停止
如下圖所示 :
這兩條流程並不是毫無關係的:當調用startService()啓動一個Service後,我們仍可以bind該Service。例如,當播放音樂時,需調用startService()啓動指定播放的音樂,當需要獲取該音樂的播放進度時,又需要調用bindService(),在這種情況下,知道Service被unbind ,調用stopService() 或stopSelf()都不能停止該Service。
以上是Android官方文檔-Service的翻譯,翻譯了個大概,部分參考了別人的解讀方式。
附:Android官方文檔-Service