Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android基礎終極篇--Service

Android基礎終極篇--Service

編輯:關於Android編程

Service 是一個可以在後台執行長時間運行操作而不使用用戶界面的應用組件。 例如,服務可以處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序交互,而所有這一切均可在後台進行。

服務基本上分為兩種形式:

啟動服務

當應用組件(如 Activity)通過調用 startService() 啟動服務時,服務即處於“啟動”狀態。
一旦啟動,服務即可在後台無限期運行,即使啟動服務的組件已被銷毀也不受影響。 
已啟動的服務通常是執行單一操作,而且不會將結果返回給調用方。例如,它可能通過網絡下載或上傳文件。 操作完成後,服務會自行停止運行。

綁定服務

當應用組件通過調用 bindService() 綁定到服務時,服務即處於“綁定”狀態。
綁定服務提供了一個客戶端-服務器接口,允許組件與服務進行交互、發送請求、獲取結果,甚至是利用進程間通信 (IPC) 跨進程執行這些操作。 
僅當與另一個應用組件綁定時,綁定服務才會運行。 多個組件可以同時綁定到該服務,但全部取消綁定後,該服務即會被銷毀。

無論應用是處於啟動狀態還是綁定狀態,抑或處於啟動並且綁定狀態,任何應用組件均可像使用活動那樣通過調用 Intent 來使用服務(即使此服務來自另一應用)。 不過,您可以通過清單文件將服務聲明為私有服務,並阻止其他應用訪問。 使用清單文件聲明服務部分將對此做更詳盡的闡述。

注意:服務在其托管進程的主線程中運行,它既不創建自己的線程,也不在單獨的進程中運行(除非另行指定)。 
這意味著,如果服務將執行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或聯網),則應在服務內創建新線程來完成這項工作。
通過使用單獨的線程,可以降低發生“應用無響應”(ANR) 錯誤的風險,而應用的主線程仍可繼續專注於運行用戶與 Activity 之間的交互。

在清單文件中聲明服務

如同 Activity(以及其他組件)一樣,也必須在應用的清單文件中聲明所有服務。

添加 < service >元素作為< application > 元素的子元素。例如:


  ...
  
      
      ...
  

< service >元素有以下屬性:


    . . .

android:enabled

服務是否能被系統實例化 —“true”可以,“false”不允許。 默認值是“true”。
< application > 元素擁有自己的 enabled 屬性,適用於應用程序所有的內部組件,包括服務。 
服務要能被啟用, < application> 和 < service> 的此屬性都必須設置為“true”(均為默認值即可)。

android:exported

其它應用程序的組件能否調用服務或與服務交互 — “true”可以,“false”不可以。 
如果設為“false”,則只有本應用程序或用戶 ID 相同程序的組件才能啟動或綁定該服務。
默認值取決於服務是否包含 Intent 過濾器。 
如果不含任何過濾器,該服務僅供應用程序內部使用(因為其他應用程序通常不知道確切的類名稱)。這時的默認值是“false”。 
如果存在一個以上的過濾器,則表示服務願意被外部使用,因此默認值是“true”。

本屬性並不是限制服務對其他應用程序的公開程度的唯一途徑。 還可以利用權限機制對可與服務進行交互的外部對象進行限制(請參閱permission屬性)。

為了確保應用的安全性,請始終使用顯式 Intent 啟動或綁定 Service,且不要為服務聲明 Intent 過濾器。 
啟動哪個服務存在一定的不確定性,而如果對這種不確定性的考量非常有必要,則可為服務提供 Intent 過濾器並從 Intent 中排除相應的組件名稱,
但隨後必須使用 setPackage() 方法設置 Intent 的軟件包,這樣可以充分消除目標服務的不確定性。
或者添加 android:exported 屬性並將其設置為 "false",確保服務僅適用於您的應用。這可以有效阻止其他應用啟動您的服務,即便在使用顯式 Intent 時也如此。

android:icon

代表服務的圖標。 
本屬性必須設為對 drawable 資源的引用,該資源包含了圖片的定義。 如果未設置本屬性值,則會用全局性的應用程序圖標來代替。
服務的圖標不管是在此處還是在< application> 元素中設置的,同時也是本服務中所有 Intent 過濾器的默認圖標

android:isolatedProcess

如果設為 true ,則本服務將會運行於一個特殊的進程中。 該進程與系統其他部分隔離,且沒有自己的權限。 與其通訊的唯一手段就是通過 Service API (綁定和啟動)。

android:label

供用戶查看的服務名稱。 如果未設置本屬性,則用全局性的應用程序文本標簽代替。
服務的文本標簽 — 不管是在在此處還是在 < application> 元素中設置的, 同時也是本服務中所有 Intent 過濾器的默認文本標簽。

android:name

實現服務的 Service 子類的名稱。 這應該是一個完全限定格式的類名(比如“com.example.project.RoomService”)。 
不過作為簡稱,如果首字符為句點(比如“.RoomService”),則會在前面自動加上 < manifest> 元素定義的包名稱。
應用程序一經發布,就 不應再更改名稱 (除非設置了 android:exported="false")。
沒有默認值。本名稱必須指定。

android:permission

要啟動或綁定服務的對象所必須擁有的權限名稱。 
如果 startService()、 bindService()或 stopService() 的調用者未獲得本屬性設定的權限,這些方法將會失效,Intent 對象也不會分發給本服務。
如果本屬性未被設置,則會把 < application> 元素的 permission 屬性所定義的權限應用於本服務。 如果兩個地方的屬性都沒有設置,則本服務將不受權限機制的保護。

android:process

運行服務的進程名稱。 通常,應用程序的所有組件都運行在創建時的默認進程中。
該進程的名稱與程序包名相同。 < application> 元素的 process 屬性可以為每個組件設置不同的默認進程。 
但每個組件也可以用各自的 process 屬性覆蓋默認設置,使得程序可以跨越多個進程運行。
如果本屬性設置的名稱以冒號(':')開頭,則必要時會新建一個屬於該程序私有的進程,服務將在該新進程中運行。 
如果進程名稱以小寫字母開頭,則服務將運行於一個以此名字命名的全局進程中,並賦予應有的訪問權限。 這就允許分屬於不同應用程序的多個組件共享同一個進程,以減少資源的占用。

啟動服務

從傳統上講,您可以擴展兩個類來創建啟動服務:

Service

這是適用於所有服務的基類。
擴展此類時,必須創建一個用於執行所有服務工作的新線程,因為默認情況下,服務將使用應用的主線程,這會降低應用正在運行的所有 Activity 的性能。

IntentService

這是 Service 的子類,它使用工作線程逐一處理所有啟動請求。如果您不要求服務同時處理多個請求,這是最好的選擇。
只需實現 onHandleIntent() 方法即可,該方法會接收每個啟動請求的 Intent,使您能夠執行後台工作。

擴展 IntentService 類

由於大多數啟動服務都不必同時處理多個請求(實際上,這種多線程情況可能很危險),因此使用 IntentService 類實現服務也許是最好的選擇。

IntentService 執行以下操作:

創建默認的工作線程,用於在應用的主線程外執行傳遞給 onStartCommand() 的所有 Intent。
創建工作隊列,用於將一個 Intent 逐一傳遞給 onHandleIntent() 實現,這樣您就永遠不必擔心多線程問題。
在處理完所有啟動請求後停止服務,因此您永遠不必調用 stopSelf()。
提供 onBind() 的默認實現(返回 null)。
提供 onStartCommand() 的默認實現,可將 Intent 依次發送到工作隊列和 onHandleIntent() 實現。

綜上所述,只需實現 onHandleIntent() 來完成客戶端提供的工作即可。(另外,還需要為服務提供一個構造函數。)
以下是 IntentService 的實現示例:

public class HelloIntentService extends IntentService {
 //您只需要一個構造函數和一個 onHandleIntent() 實現即可。
  /** 
   * 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) {
              } 
          } 
      } 
  } 
} 

//如果您決定還重寫其他回調方法(如 onCreate()、onStartCommand() 或 onDestroy()),
//請確保調用超類實現,以便 IntentService 能夠妥善處理工作線程的生命周期。
//除 onHandleIntent() 之外,您無需從中調用超類的唯一方法就是 onBind()(僅當服務允許綁定時,才需要實現該方法)。

//例如,onStartCommand() 必須返回默認實現(即,如何將 Intent 傳遞給 onHandleIntent()):
@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);
} 

擴展 Service 類

使用 IntentService 顯著簡化了啟動服務的實現,但是,若要求服務執行多線程(而不是通過工作隊列處理啟動請求),則可擴展 Service 類來處理每個 Intent。

為了便於比較,以下提供了 Service 類實現的代碼示例,該類執行的工作與上述使用 IntentService 的示例完全相同。也就是說,對於每個啟動請求,它均使用工作線程執行作業,且每次僅處理一個請求。:

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  //  服務實現一個 Handler
  private final class ServiceHandler extends Handler { 
      public ServiceHandler(Looper looper) {
          super(looper);
      } 
      @Override 
      public void handleMessage(Message msg) {
          // 執行任務, 比如下載文件. 
          // 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() { 
    // 因為服務運行在主線程中,又不希望主線程阻塞,所以新建一個線程運行服務 We also make it 
    // 我們還讓他優先在後台運行,這樣CPU密集型任務不會阻塞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);

      //如果任務中斷,從這裡重啟任務 
      return START_STICKY;
  } 

  @Override 
  public IBinder onBind(Intent intent) {
      // 因為不提供綁定服務,所以返回null 
      return null; 
  } 

  @Override 
  public void onDestroy() { 
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  } 
} 

與使用 IntentService 相比,這需要執行更多工作。

但是,因為是由您自己處理對 onStartCommand() 的每個調用,因此可以同時執行多個請求。此示例並未這樣做,但如果您希望如此,則可為每個請求創建一個新線程,然後立即運行這些線程(而不是等待上一個請求完成)。

請注意,onStartCommand() 方法必須返回整型數。整型數是一個值,用於描述系統應該如何在服務終止的情況下繼續運行服務(如上所述,IntentService 的默認實現將為您處理這種情況,不過您可以對其進行修改)。從 onStartCommand() 返回的值必須是以下常量之一:

START_NOT_STICKY:

如果系統在 onStartCommand() 返回後終止服務,則除非有掛起 Intent 要傳遞,否則系統不會重建服務。
這是最安全的選項,可以避免在不必要時以及應用能夠輕松重啟所有未完成的作業時運行服務。

START_STICKY:

如果系統在 onStartCommand() 返回後終止服務,則會重建服務並調用 onStartCommand(),但絕對不會重新傳遞最後一個 Intent。
相反,除非有掛起 Intent 要啟動服務(在這種情況下,將傳遞這些 Intent ),否則系統會通過空 Intent 調用 onStartCommand()。
這適用於不執行命令、但無限期運行並等待作業的媒體播放器(或類似服務)。

START_REDELIVER_INTENT:

如果系統在 onStartCommand() 返回後終止服務,則會重建服務,並通過傳遞給服務的最後一個 Intent 調用 onStartCommand()。
任何掛起 Intent 均依次傳遞。
這適用於主動執行應該立即恢復的作業(例如下載文件)的服務。

啟動服務

您可以通過將 Intent(指定要啟動的服務)傳遞給 startService(),從 Activity 或其他應用組件啟動服務。Android 系統調用服務的 onStartCommand() 方法,並向其傳遞 Intent。

例如,Activity 可以結合使用顯式 Intent 與 startService(),啟動上文中的示例服務 (HelloSevice):

Intent intent = new Intent(this, HelloService.class);
startService(intent);

startService() 方法將立即返回,且 Android 系統調用服務的 onStartCommand() 方法。如果服務尚未運行,則系統會先調用 onCreate(),然後再調用 onStartCommand()。

如果服務亦未提供綁定,則使用 startService() 傳遞的 Intent 是應用組件與服務之間唯一的通信模式。
但是,如果您希望服務返回結果,則啟動服務的客戶端可以為廣播創建一個 PendingIntent (使用 getBroadcast()),並通過啟動服務的 Intent 傳遞給服務。
然後,服務就可以使用廣播傳遞結果。

多個服務啟動請求會導致多次對服務的 onStartCommand() 進行相應的調用。但是,要停止服務,只需一個服務停止請求(使用 stopSelf() 或 stopService())即可。

停止服務

啟動服務必須管理自己的生命周期。也就是說,除非系統必須回收內存資源,否則系統不會停止或銷毀服務,而且服務在 onStartCommand() 返回後會繼續運行。因此,服務必須通過調用 stopSelf() 自行停止運行,或者由另一個組件通過調用 stopService() 來停止它。

一旦請求使用 stopSelf() 或 stopService() 停止服務,系統就會盡快銷毀服務。

但是,如果服務同時處理多個 onStartCommand() 請求,則您不應在處理完一個啟動請求之後停止服務,因為您可能已經收到了新的啟動請求(在第一個請求結束時停止服務會終止第二個請求)。為了避免這一問題,您可以使用 stopSelf(int) 確保服務停止請求始終基於最近的啟動請求。也就說,在調用 stopSelf(int) 時,傳遞與停止請求的 ID 對應的啟動請求的 ID(傳遞給 onStartCommand() 的 startId) 。然後,如果在您能夠調用 stopSelf(int) 之前服務收到了新的啟動請求, ID 就不匹配,服務也就不會停止。

注意:為了避免浪費系統資源和消耗電池電量,應用必須在工作完成之後停止其服務。 如有必要,其他組件可以通過調用 stopService() 來停止服務。即使為服務啟用了綁定,一旦服務收到對 onStartCommand() 的調用,您始終仍須親自停止服務。

綁定服務

創建提供綁定的服務時,您必須提供 IBinder,用以提供客戶端用來與服務進行交互的編程接口。 您可以通過三種方法定義接口:

使用Binder

如果服務是供您的自有應用專用,並且在與客戶端相同的進程中運行(常見情況),則應通過擴展 Binder 類並從 onBind() 返回它的一個實例來創建接口。
客戶端收到 Binder 後,可利用它直接訪問 Binder 實現中乃至 Service 中可用的公共方法。
如果服務只是您的自有應用的後台工作線程,則優先采用這種方法。 不以這種方式創建接口的唯一原因是,您的服務被其他應用或不同的進程占用。

使用 Messenger

如需讓接口跨不同的進程工作,則可使用 Messenger 為服務創建接口。
服務可以這種方式定義對應於不同類型 Message 對象的 Handler。
此 Handler 是 Messenger 的基礎,後者隨後可與客戶端分享一個 IBinder,從而讓客戶端能利用 Message 對象向服務發送命令。
此外,客戶端還可定義自有 Messenger,以便服務回傳消息。
這是執行進程間通信 (IPC) 的最簡單方法,因為 Messenger 會在單一線程中創建包含所有請求的隊列,這樣您就不必對服務進行線程安全設計。

使用 AIDL

AIDL(Android 接口定義語言)執行所有將對象分解成原語的工作,操作系統可以識別這些原語並將它們編組到各進程中,以執行 IPC。
之前采用 Messenger 的方法實際上是以 AIDL 作為其底層結構。如上所述,Messenger 會在單一線程中創建包含所有客戶端請求的隊列,以便服務一次接收一個請求。
不過,如果您想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,您的服務必須具備多線程處理能力,並采用線程安全式設計。
如需直接使用 AIDL,您必須創建一個定義編程接口的 .aidl 文件。Android SDK 工具利用該文件生成一個實現接口並處理 IPC 的抽象類,您隨後可在服務內對其進行擴展。

注:大多數應用“都不會”使用 AIDL 來創建綁定服務,因為它可能要求具備多線程處理能力,並可能導致實現的復雜性增加。因此,AIDL 並不適合大多數應用,本文也不會闡述如何將其用於您的服務。如果您確定自己需要直接使用 AIDL,請參閱 AIDL 文檔。

使用Binder 創建綁定服務

如果您的服務僅供本地應用使用,不需要跨進程工作,則可以實現Binder 類,讓客戶端通過該類直接訪問服務中的公共方法。

注:此方法只有在客戶端和服務位於同一應用和進程內這一最常見的情況下方才有效。 例如,對於需要將 Activity 綁定到在後台播放音樂的自有服務的音樂應用,此方法非常有效。

以下是具體的設置方法:

1>在您的服務中,創建一個可滿足下列任一要求的 Binder 實例:
    包含客戶端可調用的公共方法
    返回當前 Service 實例,其中包含客戶端可調用的公共方法
    或返回由服務承載的其他類的實例,其中包含客戶端可調用的公共方法

2>從 onBind() 回調方法返回此 Binder 實例。

3>在客戶端中,從 onServiceConnected() 回調方法接收 Binder,並使用提供的方法調用綁定服務。

注:之所以要求服務和客戶端必須在同一應用內,是為了便於客戶端轉換返回的對象和正確調用其 API。服務和客戶端還必須在同一進程內,因為此方法不執行任何跨進程編組。

例如,以下這個服務可讓客戶端通過 Binder 實現訪問服務中的方法:

public class LocalService extends Service {
    // 2> Binder,讓客戶端通過該對象直接訪問服務中的公共方法
    private final IBinder mBinder = new LocalBinder();
    // Random number generator 
    private final Random mGenerator = new Random();

    /** 1> 實現Binder 類,返回當前 Service 實例
     * Class used for the client Binder.  Because we know this service always 
     * runs in the same process as its clients, we don't need to deal with IPC. 
     */ 
    public class LocalBinder extends Binder {
        LocalService getService() { 
            // Return this instance of LocalService so clients can call public methods 
            return LocalService.this;
        } 
    } 

    // 3> 從 onBind() 回調方法返回此 Binder 實例。
    @Override 
    public IBinder onBind(Intent intent) {
        return mBinder;
    } 

    /**4> 客戶端可調用的公共方法 */ 
    public int getRandomNumber() { 
      return mGenerator.nextInt(100);
    } 
} 

LocalBinder 為客戶端提供 getService() 方法,以檢索 LocalService 的當前實例。這樣,客戶端便可調用服務中的公共方法。 例如,客戶端可調用服務中的 getRandomNumber()。

點擊按鈕時,以下這個 Activity 會綁定到 LocalService 並調用 getRandomNumber():

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    } 

    @Override 
    protected void onStart() { 
        super.onStart(); 
        // 7> 綁定LocalService 
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    } 

    @Override 
    protected void onStop() { 
        super.onStop(); 
        // 8> 解綁服務 
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        } 
    } 

    /** Called when a button is clicked (the button in the layout file attaches to 
      * this method with the android:onClick attribute) */ 
    public void onButtonClick(View v) {
        if (mBound) {
            // 6> 使用提供的方法調用綁定服務 
            // However, if this call were something that might hang, then this request should 
            // occur in a separate thread to avoid slowing down the activity performance. 
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        } 
    } 
    // 5> 在客戶端中,從 onServiceConnected() 回調方法接收 Binder。
    /** Defines callbacks for service binding, passed to bindService() */ 
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override 
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance 
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        } 

        @Override 
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        } 
    }; 
} 

上例說明了客戶端如何使用 ServiceConnection 的實現和 onServiceConnected() 回調綁定到服務。

注:上例並未顯式取消與服務的綁定,但所有客戶端都應在適當的時間(例如當 Activity 暫停時)取消綁定。

如需查看更多示例代碼,請參閱 ApiDemos 中的 LocalService.java 類和 LocalServiceActivities.java 類。

使用 Messenger

如需讓服務與遠程進程通信,則可使用 Messenger 為您的服務提供接口。利用此方法,您無需使用 AIDL 便可執行進程間通信 (IPC)。

以下是 Messenger 的使用方法摘要:

1>服務實現一個 Handler,由其接收來自客戶端的每個調用的回調

2>用於創建 Messenger 對象(對 Handler 的引用)

3>Messenger 創建一個 IBinder,服務通過 onBind() 使其返回客戶端

4>客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,然後使用後者將 Message 對象發送給服務

5>服務在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每個 Message

這樣,客戶端並沒有調用服務的“方法”。而客戶端傳遞的“消息”(Message 對象)是服務在其 Handler 中接收的。

以下是一個使用 Messenger 接口的簡單服務示例:

public class MessengerService extends Service {
    /** Command to the service to display a message */ 
    static final int MSG_SAY_HELLO = 1;

    /**
     * 1>服務實現一個 Handler,由其接收來自客戶端的每個調用的回調 
     */ 
    class IncomingHandler extends Handler { 
        @Override 
        public void handleMessage(Message msg) {
        //6>服務在 handleMessage() 方法中接收每個 Message
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break; 
                default: 
                    super.handleMessage(msg);
            } 
        } 
    } 

    /** 
     * 2>用於創建 Messenger 對象(對 Handler 的引用) 
     */ 
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**3>Messenger 創建一個 IBinder,服務通過 onBind() 使其返回客戶端 
     * When binding to the service, we return an interface to our messenger 
     * for sending messages to the service. 
     */ 
    @Override 
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    } 
} 

請注意,服務就是在 Handler 的 handleMessage() 方法中接收傳入的 Message,並根據 what 成員決定下一步操作。

客戶端只需根據服務返回的 IBinder 創建一個 Messenger,然後利用 send() 發送一條消息。例如,以下就是一個綁定到服務並向服務傳遞 MSG_SAY_HELLO 消息的簡單 Activity:

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */ 
    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */ 
    boolean mBound;

    /** 4>客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化
     * Class for interacting with the main interface of the service. 
     */ 
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been 
            // established, giving us the object we can use to 
            // interact with the service.  We are communicating with the 
            // service using a Messenger, so here we get a client-side 
            // representation of that from the raw IBinder object. 
            mService = new Messenger(service);
            mBound = true;
        } 

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been 
            // unexpectedly disconnected -- that is, its process crashed. 
            mService = null;
            mBound = false;
        } 
    }; 

    public void sayHello(View v) {
        if (!mBound) return;
        // Create and send a message to the service, using a supported 'what' value 
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
        // 5> 將 Message 對象發送給服務 
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        } 
    } 

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    } 

    @Override 
    protected void onStart() { 
        super.onStart(); 
        // 7> 綁定服務 
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    } 

    @Override 
    protected void onStop() { 
        super.onStop(); 
        // 8> 解綁服務 
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        } 
    } 
} 

請注意,此示例並未說明服務如何對客戶端作出響應。如果您想讓服務作出響應,則還需要在客戶端中創建一個 Messenger。然後,當客戶端收到 onServiceConnected() 回調時,會向服務發送一條 Message,並在其 send() 方法的 replyTo 參數中包含客戶端的 Messenger。

如需查看如何提供雙向消息傳遞的示例,請參閱 MessengerService.java(服務)和 MessengerServiceActivities.java(客戶端)示例。

使用AIDL

AIDL(Android Interface Definition Language)接口定義語言。它定義一個編程接口,這個接口是客戶端與服務端都約定一致的用來跨進程通信。

注意:只有當允許讓來自不同應用的客戶端跨進程通信(IPC)訪問你的服務並且在你的服務中有多個線程需要處理,才有必要使用AIDL。
如果沒有必要處理並發跨進程(IPC)訪問不同的應用,應該通過實現Binder接口,或者如果你想執行跨進程通信,但又沒有必要處理多線程, 使用Messager 來實現接口。

AIDL接口的調用采用的是直接的函數調用方式,但你無法預知哪個進程(或線程)將調用該接口。同進程的線程調用和其他進程調用該接口之間是有所區別的:

在同進程中調用AIDL接口,AIDL接口代碼的執行將在調用該AIDL接口的線程中完成,
如果在主UI線程中調用AIDL接口,那麼AIDL接口代碼的執行將會在這個主UI線程中完成。
如果是其他線程,AIDL接口代碼的執行將在service中完成。
因此,如果僅僅是本進程中的線程訪問該服務,你完全可以控制哪些線程將訪問這個服務(但是如果是這樣,那就完全沒必要使用AIDL了,采取Binder接口的方式更為合適)。

遠程進程(其他線程)調用AIDL接口時,將會在AIDL所屬的進程的線程池中分派一個線程來執行該AIDL代碼,
所以編寫AIDL時,你必須准備好可能有未知線程訪問、同一時間可能有多個調用發生(多個線程的訪問),
所以ADIL接口的實現必須是線程安全的。

可以用關鍵字oneway來標明遠程調用的行為屬性,如果使用了該關鍵字,那麼遠程調用將僅僅是調用所需的數據傳輸過來並立即返回,而不會等待結果的返回,也即是說不會阻塞遠程線程的運行。
AIDL接口將最終將獲得一個從Binder線程池中產生的調用(和普通的遠程調用類似)。
如果關鍵字oneway在本地調用中被使用,將不會對函數調用有任何影響。

簡單使用AIDL

AIDL接口使用後綴名位.aidl的文件來定義,.aidl文件使用java語法編寫,並且將該.aidl文件保存在 src/目錄下(無論是服務端還是客戶端都得保存同樣的一份拷貝,也就是說只要是需要使用到該AIDL接口的應用程序都得在其src目錄下擁有一份.aidl文件的拷貝)。

要創建一個通過AIDL綁定的服務,采用下面的步驟。

1>創建.aidl文件。這個文件定義了帶有方法簽名的接口

2>實現該接口。Android SDK工具會根據你的.aidl文件以Java編程語言生成一個接口。
該接口有一個叫做Stub的內部抽象類, 它繼承自Binder並且實現了你在AIDL接口中定義的方法。你必須繼承Stub類然後實現它的方法。

3>暴露接口給客戶端。實現一個 Service並且覆蓋 onBind() 方法,返回你針對Stub類的實現.

1、創建.aidl文件

每一個.aidl文件只能定義一個接口,只需要申明該接口的方法(不需要實現)。

AIDL默認支持下面幾種數據類型:

1>Java編程語言中的基礎數據類型(int,long,char,boolean等)

2>String

3>CharSequence

4>List。在List 中的所有元素必須是上面支持的類型或者是其他由AIDL生成的接口,或者你申明的實現了Parcelable接口的類型。
List可能被選用為泛型類,比如 List< String>.實際在接受服務一側生成的類為永遠是 ArrayList。盡管生成的方法使用的是 List接口。

5>Map。Map中的雖有元素必須是上面類型或者是其他由AIDL生成的接口,或者你申明的實現了Parcelable接口的類型。
泛型例如 Map不支持。實際在接受服務一側生成的類為永遠是HashMap。盡管生成的方法使用的是 Map接口。

必須要為每一個上面未列出類型添加import申明,盡管他們是作為接口定義在同一個包裡面。

當定義服務接口:

方法可以帶有0個或者多個參數,帶有返回值或者沒有返回值
所有的非基礎數據類型參數需要一個額外的用於標明參數去向的標記。可以是in、out或者inout(參見下面的例子)。基礎數據類型默認是in.而不能選擇其他方式。
需要限制確實需要的方向,因為組裝參數的開銷很大。
所有包含在.aidl文件中的代碼注釋也就會包生成的 IBinder 接口中,除了導包語句之前的聲明的注釋。
在AIDL中僅支持申明方法,不能申明靜態域。

下面是一個.aidl文件示例:

// IRemoteService.aidl
package com.example.android;

// 聲明一個沒有import的aidl

/** Example service interface */
interface IRemoteService {
    /** 獲取service的進程ID, to do evil things with it. */
    int getPid();

    /** 參數可以使用的基本類型,和AIDL的返回值基本類型
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

只需要將.aidl文件保存到項目的src/目錄下面,當構建項目的時候SDK工具會在項目的gen/目錄下生成IBinder接口文件,生成的文件名與.aidl文件名相匹配,但是是以.java擴展名結尾(例如IRemoteService.aidl會生成IRemoteService.java)

如果使用的是Android Studio,增量構建幾乎會立即生成Binder類。如果你沒有使用Android Studio,Gradle 工具會在你下次構建應用時生成的binder類。一旦你寫完了.aidl文件,你應該用gradle assembleDebug或者gradle assembleRelease構建項目,這樣才能讓你的代碼鏈接到生成的類上。

2、實現該接口

當構建應用的時候,Android SDK工具生成一個根據.aidl文件生成.java接口,生成的接口包含一個叫做的Stub內部類,這個類是父接口(比如YourInterface.Stub)的抽象實現,並且申明了所有來自.aidl文件的方法。

注意:Stub也定義了一些幫助方法,比較常用的是asInterface(),這個方法傳入一個 IBinder (這個參數通常傳入到客戶端的的onServiceConnected()回調方法中)並且返回一個Stub的實例。
為了實現由.aidl生成的接口,繼承生成的Binder接口,例如YourInterface.Stub,然後實現繼承自.aidl文件中的方法。

我們可以新建一個類RemoteService, 繼承服務類Service,然後在裡面對接口進行實現。下面是一個調用名為IRemoteService 接口的實現。(在上面的例子 IRemoteService.aidl中定義),使用匿名實例:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

現在mBinder是Stub 類的實例,它定義了該服務的跨進程通信接口,在下一步,這個實例會暴露給客戶端這樣客戶端便能與服務端交互。

下面是在實現AIDL接口是應當注意的規則:

傳入的調用不一定保證會在主線程中執行,因此你需要考慮多線程從開始到構建服務保證線程安全

默認情況下,遠程過程調用(Remote Procedure Call,縮寫為 RPC)是同步的,如果知道服務會花費超過數秒來完成一個請求,你不應當在activity 的主線中調用該方法,
因為這可能會導致應用掛起(Android應用可能會顯示應用無法響應的對話框),通常你應該在客戶端的一個單獨的線程中調用該方法。

不會有異常回傳給調用者

3、暴露接口給客戶端

一旦你實現了服務接口,你需要將它暴露給客戶端這樣客戶端就能綁定服務。為了暴露接口給服務,繼承服務類Service 並實現 onBind()以返回一個實現了生成的Stub(前面所討論).

下面是一個暴露IRemoteService實例接口給服務端的示例:

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }
//3、暴露接口給客戶端
    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }
//2、實現該接口
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

4、客戶端綁定該服務

現在,當客戶端例如Activity調用bindService()綁定到該服務,客戶端的 onServiceConnected() 回調方法接受一個service的 onBind()方法返回的mBinder實例。

客戶端必須訪問接口類,如果客戶端和服務端處在不同的應用中,客戶端必須具有一個應用必須具有一份.aidl的拷貝並將其存放於src/目錄下面。(Android SDK)會生成android.os.Binder接口。提供客戶端訪問AIDL的方法。

當客戶端接收到 onServiceConnected() 回調方法中 IBinder .它必須調用YourServiceInterface.Stub.asInterface(service)將其轉換成YourServiceInterface 類型。示例如下:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

了解更多的示例代碼參考 ApiDemos中的 RemoteService.java 類.

通過IPC傳遞對象

如果想要通過IPC接口將一個類對象從一個進程傳遞給另外一個進程,這個可以實現,但是必須得保證這個類在IPC兩端的有效性,並且該類必須支持Parcelable接口。支持Parcelable接口非常重要,因為這允許Android系統將Object拆解為原始數據類型,這樣才能達到跨進程封送(序列化發送)。

要想創建一個支持 Parcelable協議的類,需要如下幾個步驟:

1,創建一個類並實現 Parcelable接口

2,實現 writeToParcel方法,這個方法將當前對象的狀態寫入到 Parcel中。

3,添加一個名為CREATOR的靜態對象到類裡面,這個對象實現了 Parcelable.Creator接口。

4,最後,創建一個.aidl文件,申明你的parcelable類(如下面的 Rect.aidl文件所示)。

如果你正在使用一個自定義構建進程,不需要添加.aidl文件到你的構建。跟C語言的頭文件很相似,.aidl文件不會編譯。
AIDL使用這些所生成的代碼方法和成員來組裝和拆解你的對象。

例如。下面是一個Rect.aidl文件來創建一個可組裝和拆解的Rect類.

//4,最後,創建一個.aidl文件,申明你的parcelable類
package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

下面是Rect類如何實現 Parcelable 協議的

import android.os.Parcel;
import android.os.Parcelable;
//1,創建一個類並實現 Parcelable接口.
public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;
//3,添加一個名為CREATOR的靜態對象到類裡面,這個對象實現了 Parcelable.Creator接口。
    public static final Parcelable.Creator CREATOR = new
Parcelable.Creator() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }
//2,實現 writeToParcel方法,這個方法將當前對象的狀態寫入到 Parcel中。
    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}

對Rect類的組裝非常簡單,查看 Parcel 上的其他方法可以知道你能向Parcel中寫入其他類型的值。

警告:不要忘記接受來自其他進程數據的安全性。在該例子中,Rect讀取來自 Parcel的四個數字。但是這取決於你確保這些值是在一個可接受的范圍類,無論客戶端嘗試做什麼。參考 安全和權限了解更多有關如何保證你的應用免於惡意軟件破壞的信息。

調用AIDL接口的方法

下面是一個類調用由遠程接口AIDL定義的方法的步驟:

1,將.aidl文件放入項目的src/aidl目錄下面。

2,聲明IBinder接口實例(該示例基於AIDL生成)

3,實現 ServiceConnection.

4,調用 Context.bindService(),傳入你 ServiceConnection 的實現。

5,在 onServiceConnected()的實現中,你會收到一個IBinder的實例(也叫service),
調用YourInterfaceName.Stub.asInterface((IBinder)service)將 onServiceConnected()回調方法中的IBinder參數轉換成YourInterface 類型。

6,調用你在接口中定義的方法。你始終要要捕獲 DeadObjectException 異常,這個異常會在鏈接斷開時拋出。這個異常只會有遠程方法拋出.

7,如果想斷開鏈接,用你接口的實例調用 Context.unbindService()

關於調用跨進程服務的一點說明:

對象跨進程采取引用計數方式
可以發送匿名對象作為方法的參數

下面是摘自ApiDemos項目中遠程服務范例部分代碼展示如何調用AIDL創建的服務。

public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

    Button mKillButton;
    TextView mCallbackText;

    private boolean mIsBound;

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(false);

        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            mKillButton.setEnabled(true);
            mCallbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mKillButton.setEnabled(false);
            mCallbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection mSecondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            mSecondaryService = ISecondary.Stub.asInterface(service);
            mKillButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            mSecondaryService = null;
            mKillButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };

    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(false);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener mKillListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (mSecondaryService != null) {
                try {
                    int pid = mSecondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    mCallbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    };
}

綁定服務

應用組件(客戶端)可通過調用 bindService() 綁定到服務。Android 系統隨後調用服務的 onBind() 方法,該方法返回用於與服務交互的 IBinder。

綁定是異步的。bindService() 會立即返回,“絕對不會”使 IBinder 返回客戶端。要接收 IBinder,客戶端必須創建一個 ServiceConnection 實例,並將其傳遞給 bindService()。ServiceConnection 包括一個回調方法,系統通過調用它來傳遞 IBinder。

注:只有 Activity、服務和內容提供程序可以綁定到服務—您無法從廣播接收器綁定到服務。

因此,要想從您的客戶端綁定到服務,您必須:

實現 ServiceConnection。
您的實現必須重寫兩個回調方法:
    onServiceConnected() 系統會調用該方法以傳遞服務的 onBind() 方法返回的 IBinder。
    onServiceDisconnected() Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法。當客戶端取消綁定時,系統“絕對不會”調用該方法。
調用 bindService() 以傳遞 ServiceConnection 實現。

當系統調用您的 onServiceConnected() 回調方法時,您可以使用接口定義的方法開始調用服務。
要斷開與服務的連接,請調用 unbindService()。
當您的客戶端被銷毀時,它將取消與服務的綁定,但您應該始終在完成與服務的交互時或您的 Activity 暫停時取消綁定,以便服務能夠在未被占用時關閉。
例如,以下代碼段通過擴展 Binder 類將客戶端與上面創建的服務相連,因此它只需將返回的 IBinder 轉換為 LocalService 類並請求 LocalService 實例:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established 
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit 
        // service that is running in our own process, we can 
        // cast its IBinder to a concrete class and directly access it. 
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true; 
    } 

    // Called when the connection with the service disconnects unexpectedly 
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false; 
    } 
}; 

客戶端可通過將此 ServiceConnection 傳遞至 bindService() 綁定到服務。例如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

bindService() 的第一個參數是一個 Intent,用於顯式命名要綁定的服務(但 Intent 可能是隱式的)
第二個參數是 ServiceConnection 對象
第三個參數是一個指示綁定選項的標志。它通常應該是 BIND_AUTO_CREATE,以便創建尚未激活的服務。 其他可能的值為 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示無)。

以下是一些有關綁定到服務的重要說明:

您應該始終捕獲 DeadObjectException 異常,它們是在連接中斷時引發的。這是遠程方法引發的唯一異常
對象是跨進程計數的引用
您通常應該在客戶端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 時刻期間配對綁定和取消綁定。 例如:
如果您只需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。
如果您希望 Activity 在後台停止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。
請注意,這意味著您的 Activity 在其整個運行過程中(甚至包括後台運行期間)都需要使用服務,因此如果服務位於其他進程內,那麼當您提高該進程的權重時,系統終止該進程的可能性會增加

注:通常情況下,切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,因為每一次生命周期轉換都會發生這些回調,您應該使發生在這些轉換期間的處理保持在最低水平。此外,如果您的應用內的多個 Activity 綁定到同一服務,並且其中兩個 Activity 之間發生了轉換,則如果當前 Activity 在下一次綁定(恢復期間)之前取消綁定(暫停期間),系統可能會銷毀服務並重建服務。 (Activity文檔中介紹了這種有關 Activity 如何協調其生命周期的 Activity 轉換。)

如需查看更多顯示如何綁定到服務的示例代碼,請參閱 ApiDemos 中的 RemoteService.java 類。

第三部分

向用戶發送通知

一旦運行起來,服務即可使用 Toast 通知或狀態欄通知來通知用戶所發生的事件。

Toast 通知是指出現在當前窗口的表面、片刻隨即消失不見的消息,而狀態欄通知則在狀態欄提供內含消息的圖標,用戶可以選擇該圖標來采取操作(例如啟動 Activity)。

通常,當某些後台工作已經完成(例如文件下載完成)且用戶現在可以對其進行操作時,狀態欄通知是最佳方法。 當用戶從展開視圖中選定通知時,通知即可啟動 Activity(例如查看已下載的文件)。

在前台運行服務

前台服務被認為是用戶主動意識到的一種服務,因此在內存不足時,系統也不會考慮將其終止。 前台服務必須為狀態欄提供通知,狀態欄位於“正在進行”標題下方,這意味著除非服務停止或從前台刪除,否則不能清除通知。

例如,應該將從服務播放音樂的音樂播放器設置為在前台運行,這是因為用戶明確意識到其操作。 狀態欄中的通知可能表示正在播放的歌曲,並允許用戶啟動 Activity 來與音樂播放器進行交互。

要請求讓服務運行於前台,需要調用 startForeground()。此方法有兩個參數:唯一標識通知的整型數和狀態欄的 Notification。例如:

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); 

注意:提供給 startForeground() 的整型 ID 不得為 0。

要從前台刪除服務,調用 stopForeground()。此方法取一個布爾值,指示是否也刪除狀態欄通知。 此方法絕對不會停止服務。 但是,如果您在服務正在前台運行時將其停止,則通知也會被刪除。

管理服務生命周期

服務的生命周期比 Activity 的生命周期要簡單得多。但是,密切關注如何創建和銷毀服務反而更加重要,因為服務可以在用戶沒有意識到的情況下運行於後台。

服務生命周期(從創建到銷毀)可以遵循兩條不同的路徑:

啟動服務

該服務在其他組件調用 startService() 時創建,然後無限期運行,且必須通過調用 stopSelf() 來自行停止運行。
此外,其他組件也可以通過調用 stopService() 來停止服務。服務停止後,系統會將其銷毀。

綁定服務

該服務在另一個組件(客戶端)調用 bindService() 時創建。然後,客戶端通過 IBinder 接口與服務進行通信。客戶端可以通過調用 unbindService() 關閉連接。
多個客戶端可以綁定到相同服務,而且當所有綁定全部取消後,系統即會銷毀該服務。 (服務不必自行停止運行。)

這兩條路徑並非完全獨立。也就是說,可以綁定到已經使用 startService() 啟動的服務。例如,可以通過使用 Intent(標識要播放的音樂)調用 startService() 來啟動後台音樂服務。隨後,可能在用戶需要稍加控制播放器或獲取有關當前播放歌曲的信息時,Activity 可以通過調用 bindService() 綁定到服務。在這種情況下,除非所有客戶端均取消綁定,否則 stopService() 或 stopSelf() 不會真正停止服務。

實現生命周期回調

與 Activity 類似,服務也擁有生命周期回調方法,可以實現這些方法來監控服務狀態的變化並適時執行工作。 以下框架服務展示了每種生命周期方法:

public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    @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 
    } 
} 

注:與 Activity 生命周期回調方法不同,不需要調用這些回調方法的超類實現。

這裡寫圖片描述
圖 1. 服務生命周期左圖顯示了使用 startService() 所創建的服務的生命周期,右圖顯示了使用 bindService() 所創建的服務的生命周期。

通過實現這些方法,您可以監控服務生命周期的兩個嵌套循環:

服務的整個生命周期從調用 onCreate() 開始起,到 onDestroy() 返回時結束。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> 與 Activity 類似,服務也在 onCreate() 中完成初始設置,並在 onDestroy() 中釋放所有剩余資源。 例如,音樂播放服務可以在 onCreate() 中創建用於播放音樂的線程,然後在 onDestroy() 中停止該線程。 無論服務是通過 startService() 還是 bindService() 創建,都會為所有服務調用 onCreate() 和 onDestroy() 方法。

服務的有效生命周期從調用 onStartCommand() 或 onBind() 方法開始。

每種方法均有 Intent 對象,該對象分別傳遞到 startService() 或 bindService()。
對於啟動服務,有效生命周期與整個生命周期同時結束(即便是在 onStartCommand() 返回之後,服務仍然處於活動狀態)。
對於綁定服務,有效生命周期在 onUnbind() 返回時結束。

注:盡管啟動服務是通過調用 stopSelf() 或 stopService() 來停止,但是該服務並無相應的回調(沒有 onStop() 回調)。因此,除非服務綁定到客戶端,否則在服務停止時,系統會將其銷毀—onDestroy() 是接收到的唯一回調。

圖 1 說明了服務的典型回調方法。盡管該圖分開介紹通過 startService() 創建的服務和通過 bindService() 創建的服務,但是請記住,不管啟動方式如何,任何服務均有可能允許客戶端與其綁定。因此,最初使用 onStartCommand()(通過客戶端調用 startService())啟動的服務仍可接收對 onBind() 的調用(當客戶端調用 bindService() 時)。

管理綁定服務的生命周期

當服務與所有客戶端之間的綁定全部取消時,Android 系統便會銷毀服務(除非還使用 onStartCommand() 啟動了該服務)。因此,如果您的服務是純粹的綁定服務,則無需對其生命周期進行管理—Android 系統會根據它是否綁定到任何客戶端代您管理。

不過,如果您選擇實現 onStartCommand() 回調方法,則您必須顯式停止服務,因為系統現在已將服務視為已啟動。在此情況下,服務將一直運行到其通過 stopSelf() 自行停止,或其他組件調用 stopService() 為止,無論其是否綁定到任何客戶端。

此外,如果您的服務已啟動並接受綁定,則當系統調用您的 onUnbind() 方法時,如果您想在客戶端下一次綁定到服務時接收 onRebind() 調用(而不是接收 onBind() 調用),則可選擇返回 true。onRebind() 返回空值,但客戶端仍在其 onServiceConnected() 回調中接收 IBinder。下文圖 2 說明了這種生命周期的邏輯。

這裡寫圖片描述
圖 2. 允許綁定的已啟動服務的生命周期。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved