1、Service的種類
按運行地點分類:
類別 |
區別 |
優點 |
缺點 |
應用 |
本地服務(Local) |
該服務依附在主進程上, |
服務依附在主進程上而不是獨立的進程,這樣在一定程度上節約了資源,另外Local服務因為是在同一進程因此不需要IPC,也不需要AIDL。相應bindService會方便很多。 |
主進程被Kill後,服務便會終止。 |
非常常見的應用如:HTC的音樂播放服務,天天動聽音樂播放服務。 |
遠程服務(Remote) |
該服務是獨立的進程, |
服務為獨立的進程,對應進程名格式為所在包名加上你指定的android:process字串。由於是獨立的進程,因此在Activity所在進程被Kill的時候,該服務依然在運行,不受其他進程影響,有利於為多個進程提供服務具有較高的靈活性。 |
該服務是獨立的進程,會佔用一定資源,並且使用AIDL進行IPC稍微麻煩一點。 |
一些提供系統服務的Service,這種Service是常駐的。 |
其實remote服務還是很少見的,並且一般都是系統服務。
按運行類型分類:
類別 |
區別 |
應用 |
前台服務 |
會在通知一欄顯示 ONGOING 的 Notification, |
當服務被終止的時候,通知一欄的 Notification 也會消失,這樣對於使用者有一定的通知作用。常見的如音樂播放服務。 |
後台服務 |
預設的服務即為後台服務,即不會在通知一欄顯示 ONGOING 的 Notification。 |
當服務被終止的時候,使用者是看不到效果的。某些不需要運行或終止提示的服務,如天氣更新,日期同步,郵件同步等。 |
有同學可能會問,後台服務我們可以自己建立 ONGOING 的 Notification 這樣就成為前台服務嗎?答案是否定的,前台服務是在做了上述工作之後需要調用 startForeground ( android 2.0 及其以後版本 )或 setForeground (android 2.0 以前的版本)使服務成為 前台服務。這樣做的好處在於,當服務被外部強制終止掉的時候,ONGOING 的 Notification 任然會移除掉。
按使用方式分類:
類別 |
區別 |
startService 啟動的服務 |
主要用於啟動一個服務執行背景工作,不進行通訊。停止服務使用stopService |
bindService 啟動的服務 |
該方法啟動的服務要進行通訊。停止服務使用unbindService |
startService 同時也 bindService 啟動的服務 |
停止服務應同時使用stepService與unbindService |
以上面三種方式啟動的服務其生命週期也有區別,將在隨後給出。
2、Service 與 Thread 的區別
很多時候,你可能會問,為什麼要用 Service,而不用 Thread 呢,因為用 Thread 是很方便的,比起 Service 也方便多了,下面我詳細的來解釋一下。
1). Thread:Thread 是程式執行的最小單元,它是分配CPU的基本單位。可以用 Thread 來執行一些非同步操作。
2). Service:Service 是android的一種機制,當它啟動並執行時候如果是Local Service,那麼對應的 Service 是運行在主進程的 main 線程上的。如:onCreate,onStart 這些函數在被系統調用的時候都是在主進程的 main 線程上啟動並執行。如果是Remote Service,那麼對應的 Service 則是運行在獨立進程的 main 線程上。因此請不要把 Service 理解成線程,它跟線程半毛錢的關係都沒有!
既然這樣,那麼我們為什麼要用 Service 呢?其實這跟 android 的系統機制有關,我們先拿 Thread 來說。Thread 的運行是獨立於 Activity 的,也就是說當一個 Activity 被 finish 之後,如果你沒有主動停止 Thread 或者 Thread 裡的 run 方法沒有執行完畢的話,Thread 也會一直執行。因此這裡會出現一個問題:當 Activity 被 finish 之後,你不再持有該 Thread 的引用。另一方面,你沒有辦法在不同的 Activity 中對同一 Thread 進行控制。
舉個例子:如果你的 Thread 需要不停地隔一段時間就要串連伺服器做某種同步的話,該 Thread 需要在 Activity 沒有start的時候也在運行。這個時候當你 start 一個 Activity 就沒有辦法在該 Activity 裡面控制之前建立的 Thread。因此你便需要建立並啟動一個 Service ,在 Service 裡面建立、運行並控制該 Thread,這樣便解決了該問題(因為任何 Activity 都可以控制同一 Service,而系統也只會建立一個對應 Service 的執行個體)。
因此你可以把 Service 想象成一種Message Service,而你可以在任何有 Context 的地方調用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,來控制它,你也可以在 Service 裡註冊 BroadcastReceiver,在其他地方通過發送 broadcast 來控制它,當然這些都是 Thread 做不到的。
3、Service的生命週期
onCreate onStart onDestroy onBind
1). 被啟動的服務的生命週期:如果一個Service被某個Activity 調用 Context.startService 方法啟動,那麼不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在後台運行。如果一個Service被startService 方法多次啟動,那麼onCreate方法只會調用一次,onStart將會被調用多次(對應調用startService的次數),並且系統只會建立Service的一個執行個體(因此你應該知道只需要一次stopService調用)。該Service將會一直在後台運行,而不管對應程式的Activity是否在運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。
2). 被綁定的服務的生命週期:如果一個Service被某個Activity 調用 Context.bindService 方法綁定啟動,不管調用 bindService 調用幾次,onCreate方法都只會調用一次,同時onStart方法始終不會被調用。當串連建立之後,Service將會一直運行,除非調用Context.unbindService 中斷連線或者之前調用bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被調用。
3). 被啟動又被綁定的服務的生命週期:如果一個Service又被啟動又被綁定,則該Service將會一直在後台運行。並且不管如何調用,onCreate始終只會調用一次,對應startService調用多少次,Service的onStart便會調用多少次。調用unbindService將不會停止Service,而必須調用 stopService 或 Service的 stopSelf 來停止服務。
4). 當服務被停止時清除服務:當一個Service被終止(1、調用stopService;2、調用stopSelf;3、不再有綁定的串連(沒有被啟動))時,onDestroy方法將會被調用,在這裡你應當做一些清除工作,如停止在Service中建立並啟動並執行線程。
特別注意:
1、你應當知道在調用 bindService 綁定到Service的時候,你就應當保證在某處調用 unbindService 解除綁定(儘管 Activity 被 finish 的時候綁定會自 動解除,並且Service會自動停止);
2、你應當注意 使用 startService 啟動服務之後,一定要使用 stopService停止服務,不管你是否使用bindService;
3、同時使用 startService 與 bindService 要注意到,Service 的終止,需要unbindService與stopService同時調用,才能終止 Service,不管 startService 與 bindService 的調用順序,如果先調用 unbindService 此時服務不會自動終止,再調用 stopService 之後服務才會停止,如果先調用 stopService 此時服務也不會終止,而再調用 unbindService 或者 之前調用 bindService 的 Context 不存在了(如Activity 被 finish 的時候)之後服務才會自動停止;
4、當在旋轉手機螢幕的時候,當手機螢幕在“橫”“豎”變換時,此時如果你的 Activity 如果會自動旋轉的話,旋轉其實是 Activity 的重新建立,因此旋轉之前的使用 bindService 建立的串連便會斷開(Context 不存在了),對應服務的生命週期與上述相同。
5、在 sdk 2.0 及其以後的版本中,對應的 onStart 已經被否決變為了 onStartCommand,不過之前的 onStart 任然有效。這意味著,如果你開發的應用程式用的 sdk 為 2.0 及其以後的版本,那麼你應當使用 onStartCommand 而不是 onStart。
4、startService 啟動服務
想要用 startService 啟動服務,不管Local 還是 Remote 我們需要做的工作都是一樣簡單。當然要記得在 Androidmanifest.xml 中註冊 service。
根據上面的生命週期,我們便會給出 Service 中的代碼架構: 複製代碼 代碼如下:package com.newcj.test;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class LocalService1 extends Service {
/**
* onBind 是 Service 的虛方法,因此我們不得不實現它。
* 返回 null,表示客服端不能建立到此服務的串連。
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
複製代碼 代碼如下:// 啟動一個 Activity
startActivity(new Intent(this, LocalService1.class));
...
// 停止一個 Activity
stopService(new Intent(this, LocalService1.class));
對應的 Intent 為標誌服務類的 Intent。
5、Local 與 Remote 服務綁定
同樣記得在 Androidmanifest.xml 中註冊 service
1). Local 服務綁定:Local 服務的綁定較簡單,首先在 Service 中我們需要實現 Service 的抽象方法 onBind,並返回一個實現 IBinder 介面的對象。
Service 中的代碼: 複製代碼 代碼如下:package com.newcj.test;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class LocalService extends Service {
/**
* 在 Local Service 中我們直接繼承 Binder 而不是 IBinder,因為 Binder 實現了 IBinder 介面,這樣我們可以少做很多工作。
* @author newcj
*/
public class SimpleBinder extends Binder{
/**
* 擷取 Service 執行個體
* @return
*/
public LocalService getService(){
return LocalService.this;
}
public int add(int a, int b){
return a + b;
}
}
public SimpleBinder sBinder;
@Override
public void onCreate() {
super.onCreate();
// 建立 SimpleBinder
sBinder = new SimpleBinder();
}
@Override
public IBinder onBind(Intent intent) {
// 返回 SimpleBinder 對象
return sBinder;
}
}
上面的代碼關鍵之處,在於 onBind(Intent) 這個方法 返回了一個實現了 IBinder 介面的對象,這個對象將用於綁定Service 的 Activity 與 Local Service 通訊。下面是 Activity 中的代碼: 複製代碼 代碼如下:package com.newcj.test;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
public class Main extends Activity {
private final static String TAG = "SERVICE_TEST";
private ServiceConnection sc;
private boolean isBind;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sc = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LocalService.SimpleBinder sBinder = (LocalService.SimpleBinder)service;
Log.v(TAG, "3 + 5 = " + sBinder.add(3, 5));
Log.v(TAG, sBinder.getService().toString());
}
};
findViewById(R.id.btnBind).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
bindService(new Intent(Main.this, LocalService.class), sc, Context.BIND_AUTO_CREATE);
isBind = true;
}
});
findViewById(R.id.btnUnbind).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(isBind){
unbindService(sc);
isBind = false;
}
}
});
}
}
在 Activity 中,我們通過 ServiceConnection 介面來取得建立串連 與 串連意外丟失的回調。bindService有三個參數,第一個是用於區分 Service 的Intent 與 startService 中的 Intent 一致,第二個是實現了 ServiceConnection 介面的對象,最後一個是 flag 標誌位。有兩個flag,BIND_DEBUG_UNBIND 與 BIND_AUTO_CREATE,前者用於調試(詳細內容可以查看javadoc 上面描述的很清楚),後者預設使用。unbindService 解除綁定,參數則為之前建立的 ServiceConnection 介面對象。另外,多次調用 unbindService 來釋放相同的串連會拋出異常,因此我建立了一個 boolean 變數來判斷是否 unbindService 已經被調用過。
運行結果:
2). Remote 服務綁定:Remote 的服務綁定由於服務是在另外一個進程,因此需要用到 android 的 IPC 機制。這將又是一個很長的話題,因此,我打算寫另外一篇 android 的 IPC 機制分析 ,並在其中進行詳述,然後在這裡更新連結,敬請關注。
特別注意:
1、Service.onBind如果返回null,則調用 bindService 會啟動 Service,但不會串連上 Service,因此 ServiceConnection.onServiceConnected 不會被調用,但你任然需要使用 unbindService 函數斷開它,這樣 Service 才會停止。
6、建立前台服務
前台服務的優點上面已經說明,但設定服務為前台服務,我們需要注意在 sdk 2.0 及其以後版本使用的方法是 startForeground 與 stopForeground,之前版本使用的是 setForeground ,因此如果你應用程式的最低運行環境要求是 2.0,那麼這裡可以直接運用新方法,如果運行環境是2.0以下,那麼為了保證向後相容性,這裡必須使用反射技術來調用新方法。
下面是我仿照 ApiDemos 重新敲的代碼,對某些地方進行了修改,因此更具有說明性: 複製代碼 代碼如下:package com.newcj.test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
public class ForegroundService extends Service {
private static final Class[] mStartForegroundSignature = new Class[] {
int.class, Notification.class};
private static final Class[] mStopForegroundSignature = new Class[] {
boolean.class};
private NotificationManager mNM;
private Method mStartForeground;
private Method mStopForeground;
private Object[] mStartForegroundArgs = new Object[2];
private Object[] mStopForegroundArgs = new Object[1];
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mNM = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
try {
mStartForeground = ForegroundService.class.getMethod("startForeground", mStartForegroundSignature);
mStopForeground = ForegroundService.class.getMethod("stopForeground", mStopForegroundSignature);
} catch (NoSuchMethodException e) {
mStartForeground = mStopForeground = null;
}
// 我們並不需要為 notification.flags 設定 FLAG_ONGOING_EVENT,因為
// 前台服務的 notification.flags 總是預設包含了那個標誌位
Notification notification = new Notification(R.drawable.icon, "Foreground Service Started.",
System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Main.class), 0);
notification.setLatestEventInfo(this, "Foreground Service",
"Foreground Service Started.", contentIntent);
// 注意使用 startForeground ,id 為 0 將不會顯示 notification
startForegroundCompat(1, notification);
}
@Override
public void onDestroy() {
super.onDestroy();
stopForegroundCompat(1);
}
// 以相容性方式開始前台服務
private void startForegroundCompat(int id, Notification n){
if(mStartForeground != null){
mStartForegroundArgs[0] = id;
mStartForegroundArgs[1] = n;
try {
mStartForeground.invoke(this, mStartForegroundArgs);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return;
}
setForeground(true);
mNM.notify(id, n);
}
// 以相容性方式停止前台服務
private void stopForegroundCompat(int id){
if(mStopForeground != null){
mStopForegroundArgs[0] = Boolean.TRUE;
try {
mStopForeground.invoke(this, mStopForegroundArgs);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return;
}
// 在 setForeground 之前調用 cancel,因為我們有可能在取消前台服務之後
// 的那一瞬間被kill掉。這個時候 notification 便永遠不會從通知一欄移除
mNM.cancel(id);
setForeground(false);
}
}
特別注意: 1、使用 startForeground ,如果 id 為 0 ,那麼 notification 將不會顯示。
7、在什麼情況下使用 startService 或 bindService 或 同時使用startService 和 bindService
如果你只是想要啟動一個後台服務長期進行某項任務那麼使用 startService 便可以了。如果你想要與正在啟動並執行 Service 取得聯絡,那麼有兩種方法,一種是使用 broadcast ,另外是使用 bindService ,前者的缺點是如果交流較為頻繁,容易造成效能上的問題,並且 BroadcastReceiver 本身執行代碼的時間是很短的(也許執行到一半,後面的代碼便不會執行),而後者則沒有這些問題,因此我們肯定選擇使用 bindService(這個時候你便同時在使用 startService 和 bindService 了,這在 Activity 中更新 Service 的某些運行狀態是相當有用的)。另外如果你的服務只是公開一個遠程介面,供串連上的客服端(android 的 Service 是C/S架構)遠程調用執行方法。這個時候你可以不讓服務一開始就運行,而只用 bindService ,這樣在第一次 bindService 的時候才會建立服務的執行個體運行它,這會節約很多系統資源,特別是如果你的服務是Remote Service,那麼該效果會越明顯(當然在 Service 建立的時候會花去一定時間,你應當注意到這點)。
8、在 AndroidManifest.xml 裡 Service 元素的常見選項
android:name ------------- 服務類名
android:label -------------- 服務的名字,如果此項不設定,那麼預設顯示的服務名則為類名
android:icon -------------- 服務的表徵圖
android:permission ------- 申明此服務的許可權,這意味著只有提供了該許可權的應用才能控制或串連此服務
android:process ---------- 表示該服務是否運行在另外一個進程,如果設定了此項,那麼將會在包名後面加上這段字串表示另一進程的名字
android:enabled ---------- 如果此項設定為 true,那麼 Service 將會預設被系統啟動,不設定預設此項為 false
android:exported --------- 表示該服務是否能夠被其他應用程式所控制或串連,不設定預設此項為 false