1. 程式人生 > >關於Android Service真正的完全詳解,你需要知道的一切

關於Android Service真正的完全詳解,你需要知道的一切

  Service全部內容基本會在本篇涉及到,我們將圍繞以下主要知識點進行分析:

  • Service簡單概述
  • Service在清單檔案中的宣告
  • Service啟動服務實現方式及其詳解
  • Service繫結服務的三種實現方式
  • 關於啟動服務與繫結服務間的轉換問題
  • 前臺服務以及通知傳送
  • 服務Service與執行緒Thread的區別
  • 管理服務生命週期的要點
  • Android 5.0以上的隱式啟動問題及其解決方案
  • 保證服務不被殺死的實現思路

1.Service簡單概述

  Service(服務)是一個一種可以在後臺執行長時間執行操作而沒有使用者介面的應用元件。服務可由其他應用元件啟動(如Activity),服務一旦被啟動將在後臺一直執行,即使啟動服務的元件(Activity)已銷燬也不受影響。 此外,元件可以繫結到服務,以與之進行互動,甚至是執行程序間通訊 (IPC)。 例如,服務可以處理網路事務、播放音樂,執行檔案 I/O 或與內容提供程式互動,而所有這一切均可在後臺進行,Service基本上分為兩種形式:

  • 啟動狀態

  當應用元件(如 Activity)通過呼叫 startService() 啟動服務時,服務即處於“啟動”狀態。一旦啟動,服務即可在後臺無限期執行,即使啟動服務的元件已被銷燬也不受影響,除非手動呼叫才能停止服務, 已啟動的服務通常是執行單一操作,而且不會將結果返回給呼叫方。

  • 繫結狀態

  當應用元件通過呼叫 bindService() 繫結到服務時,服務即處於“繫結”狀態。繫結服務提供了一個客戶端-伺服器介面,允許元件與服務進行互動、傳送請求、獲取結果,甚至是利用程序間通訊 (IPC) 跨程序執行這些操作。 僅當與另一個應用元件繫結時,繫結服務才會執行。 多個元件可以同時繫結到該服務,但全部取消繫結後,該服務即會被銷燬。

2.Service在清單檔案中的宣告

  前面說過Service分為啟動狀態和繫結狀態兩種,但無論哪種具體的Service啟動型別,都是通過繼承Service基類自定義而來,也都需要在AndroidManifest.xml中宣告,那麼在分析這兩種狀態之前,我們先來了解一下Service在AndroidManifest.xml中的宣告語法,其格式如下:

<service android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
android:isolatedProcess=["true" | "false"] android:label="string resource" android:name="string" android:permission="string" android:process="string" > . . . </service>
  • android:exported:代表是否能被其他應用隱式呼叫,其預設值是由service中有無intent-filter決定的,如果有intent-filter,預設值為true,否則為false。為false的情況下,即使有intent-filter匹配,也無法開啟,即無法被其他應用隱式呼叫。

  • android:name:對應Service類名

  • android:permission:是許可權宣告

  • android:process:是否需要在單獨的程序中執行,當設定為android:process=”:remote”時,代表Service在單獨的程序中執行。注意“:”很重要,它的意思是指要在當前程序名稱前面附加上當前的包名,所以“remote”和”:remote”不是同一個意思,前者的程序名稱為:remote,而後者的程序名稱為:App-packageName:remote。

  • android:isolatedProcess :設定 true 意味著,服務會在一個特殊的程序下執行,這個程序與系統其他程序分開且沒有自己的許可權。與其通訊的唯一途徑是通過服務的API(bind and start)。

  • android:enabled:是否可以被系統例項化,預設為 true因為父標籤 也有 enable 屬性,所以必須兩個都為預設值 true 的情況下服務才會被啟用,否則不會啟用。
      ok~,關於Service在清單檔案的宣告我們先了解這些就行,接下來分別針對Service啟動服務和繫結服務進行詳細分析

3.Service啟動服務

  首先要建立服務,必須建立 Service 的子類(或使用它的一個現有子類如IntentService)。在實現中,我們需要重寫一些回撥方法,以處理服務生命週期的某些關鍵過程,下面我們通過簡單案例來分析需要重寫的回撥方法有哪些?

package com.zejian.ipctest.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by zejian
 * Time 2016/9/29.
 * Description:service simple demo
 */
public class SimpleService extends Service {

    /**
     * 繫結服務時才會呼叫
     * 必須要實現的方法  
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 首次建立服務時,系統將呼叫此方法來執行一次性設定程式(在呼叫 onStartCommand() 或 onBind() 之前)。
     * 如果服務已在執行,則不會呼叫此方法。該方法只被呼叫一次
     */
    @Override
    public void onCreate() {
        System.out.println("onCreate invoke");
        super.onCreate();
    }

    /**
     * 每次通過startService()方法啟動Service時都會被回撥。
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("onStartCommand invoke");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 服務銷燬時的回撥
     */
    @Override
    public void onDestroy() {
        System.out.println("onDestroy invoke");
        super.onDestroy();
    }
}

  從上面的程式碼我們可以看出SimpleService繼承了Service類,並重寫了onBind方法,該方法是必須重寫的,但是由於此時是啟動狀態的服務,則該方法無須實現,返回null即可,只有在繫結狀態的情況下才需要實現該方法並返回一個IBinder的實現類(這個後面會詳細說),接著重寫了onCreate、onStartCommand、onDestroy三個主要的生命週期方法,關於這幾個方法說明如下:

  • onBind()

  當另一個元件想通過呼叫 bindService() 與服務繫結(例如執行 RPC)時,系統將呼叫此方法。在此方法的實現中,必須返回 一個IBinder 介面的實現類,供客戶端用來與服務進行通訊。無論是啟動狀態還是繫結狀態,此方法必須重寫,但在啟動狀態的情況下直接返回 null。

  • onCreate()

  首次建立服務時,系統將呼叫此方法來執行一次性設定程式(在呼叫 onStartCommand() 或onBind() 之前)。如果服務已在執行,則不會呼叫此方法,該方法只調用一次

  • onStartCommand()

  當另一個元件(如 Activity)通過呼叫 startService() 請求啟動服務時,系統將呼叫此方法。一旦執行此方法,服務即會啟動並可在後臺無限期執行。 如果自己實現此方法,則需要在服務工作完成後,通過呼叫 stopSelf() 或 stopService() 來停止服務。(在繫結狀態下,無需實現此方法。)

  • onDestroy()

  當服務不再使用且將被銷燬時,系統將呼叫此方法。服務應該實現此方法來清理所有資源,如執行緒、註冊的偵聽器、接收器等,這是服務接收的最後一個呼叫。

  我們通過Demo測試一下Service啟動狀態方法的呼叫順序,MainActivity程式碼如下:

package com.zejian.ipctest;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.zejian.ipctest.service.SimpleService;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startBtn;
    private Button stopBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startBtn= (Button) findViewById(R.id.startService);
        stopBtn= (Button) findViewById(R.id.stopService);
        startBtn.setOnClickListener(this);
        assert stopBtn != null;
        stopBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent it=new Intent(this, SimpleService.class);
        switch (v.getId()){
            case R.id.startService:
                startService(it);
                break;
            case R.id.stopService:
                stopService(it);
                break;
        }
    }
}

記得在清單配置檔案中宣告Service(宣告方式跟Activity相似):

<manifest ... >
  ...
  <application ... >
      <service android:name=".service.SimpleService" />
      ...
  </application>
</manifest>

  從程式碼看出,啟動服務使用startService(Intent intent)方法,僅需要傳遞一個Intent物件即可,在Intent物件中指定需要啟動的服務。而使用startService()方法啟動的服務,在服務的外部,必須使用stopService()方法停止,在服務的內部可以呼叫stopSelf()方法停止當前服務。如果使用startService()或者stopSelf()方法請求停止服務,系統會就會盡快銷燬這個服務。值得注意的是對於啟動服務,一旦啟動將與訪問它的元件無任何關聯,即使訪問它的元件被銷燬了,這個服務也一直執行下去,直到手動呼叫停止服務才被銷燬,至於onBind方法,只有在繫結服務時才會起作用,在啟動狀態下,無需關注此方法,ok~,我們執行程式並多次呼叫startService方法,最後呼叫stopService方法。Log截圖如下:

  從Log可以看出,第一次呼叫startService方法時,onCreate方法、onStartCommand方法將依次被呼叫,而多次呼叫startService時,只有onStartCommand方法被呼叫,最後我們呼叫stopService方法停止服務時onDestory方法被回撥,這就是啟動狀態下Service的執行週期。接著我們重新回過頭來進一步分析onStartCommand(Intent intent, int flags, int startId),這個方法有3個傳入引數,它們的含義如下:

onStartCommand(Intent intent, int flags, int startId)

  • intent :啟動時,啟動元件傳遞過來的Intent,如Activity可利用Intent封裝所需要的引數並傳遞給Service

  • flags:表示啟動請求時是否有額外資料,可選值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表沒有,它們具體含義如下:

    • START_FLAG_REDELIVERY
      這個值代表了onStartCommand方法的返回值為
      START_REDELIVER_INTENT,而且在上一次服務被殺死前會去呼叫stopSelf方法停止服務。其中START_REDELIVER_INTENT意味著當Service因記憶體不足而被系統kill後,則會重建服務,並通過傳遞給服務的最後一個 Intent 呼叫 onStartCommand(),此時Intent時有值的。

    • START_FLAG_RETRY
      該flag代表當onStartCommand呼叫後一直沒有返回值時,會嘗試重新去呼叫onStartCommand()。

  • startId : 指明當前服務的唯一ID,與stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根據ID停止服務。

  實際上onStartCommand的返回值int型別才是最最值得注意的,它有三種可選值, START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,它們具體含義如下:

  • START_STICKY
      當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閒時,系統將會嘗試重新建立此Service,一旦建立成功後將回調onStartCommand方法,但其中的Intent將是null,除非有掛起的Intent,如pendingintent,這個狀態下比較適用於不執行命令、但無限期執行並等待作業的媒體播放器或類似服務。

  • START_NOT_STICKY
      當Service因記憶體不足而被系統kill後,即使系統記憶體再次空閒時,系統也不會嘗試重新建立此Service。除非程式中再次呼叫startService啟動此Service,這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時執行服務。

  • START_REDELIVER_INTENT
      當Service因記憶體不足而被系統kill後,則會重建服務,並通過傳遞給服務的最後一個 Intent 呼叫 onStartCommand(),任何掛起 Intent均依次傳遞。與START_STICKY不同的是,其中的傳遞的Intent將是非空,是最後一次呼叫startService中的intent。這個值適用於主動執行應該立即恢復的作業(例如下載檔案)的服務。

  由於每次啟動服務(呼叫startService)時,onStartCommand方法都會被呼叫,因此我們可以通過該方法使用Intent給Service傳遞所需要的引數,然後在onStartCommand方法中處理的事件,最後根據需求選擇不同的Flag返回值,以達到對程式更友好的控制。好~,以上便是Service在啟動狀態下的分析,接著我們在來看看繫結狀態的Service又是如何處理的?

4.Service繫結服務

  繫結服務是Service的另一種變形,當Service處於繫結狀態時,其代表著客戶端-伺服器介面中的伺服器。當其他元件(如 Activity)繫結到服務時(有時我們可能需要從Activity組建中去呼叫Service中的方法,此時Activity以繫結的方式掛靠到Service後,我們就可以輕鬆地方法到Service中的指定方法),元件(如Activity)可以向Service(也就是服務端)傳送請求,或者呼叫Service(服務端)的方法,此時被繫結的Service(服務端)會接收資訊並響應,甚至可以通過繫結服務進行執行程序間通訊 (即IPC,這個後面再單獨分析)。與啟動服務不同的是繫結服務的生命週期通常只在為其他應用元件(如Activity)服務時處於活動狀態,不會無限期在後臺執行,也就是說宿主(如Activity)解除繫結後,繫結服務就會被銷燬。那麼在提供繫結的服務時,該如何實現呢?實際上我們必須提供一個 IBinder介面的實現類,該類用以提供客戶端用來與服務進行互動的程式設計介面,該介面可以通過三種方法定義介面:

  • 擴充套件 Binder 類
      如果服務是提供給自有應用專用的,並且Service(服務端)與客戶端相同的程序中執行(常見情況),則應通過擴充套件 Binder 類並從 onBind() 返回它的一個例項來建立介面。客戶端收到 Binder 後,可利用它直接訪問 Binder 實現中以及Service 中可用的公共方法。如果我們的服務只是自有應用的後臺工作執行緒,則優先採用這種方法。 不採用該方式建立介面的唯一原因是,服務被其他應用或不同的程序呼叫。

  • 使用 Messenger
      Messenger可以翻譯為信使,通過它可以在不同的程序中共傳遞Message物件(Handler中的Messager,因此 Handler 是 Messenger 的基礎),在Message中可以存放我們需要傳遞的資料,然後在程序間傳遞。如果需要讓介面跨不同的程序工作,則可使用 Messenger 為服務建立介面,客戶端就可利用 Message 物件向服務傳送命令。同時客戶端也可定義自有 Messenger,以便服務回傳訊息。這是執行程序間通訊 (IPC) 的最簡單方法,因為 Messenger 會在單一執行緒中建立包含所有請求的佇列,也就是說Messenger是以序列的方式處理客戶端發來的訊息,這樣我們就不必對服務進行執行緒安全設計了。

  • 使用 AIDL
       由於Messenger是以序列的方式處理客戶端發來的訊息,如果當前有大量訊息同時傳送到Service(服務端),Service仍然只能一個個處理,這也就是Messenger跨程序通訊的缺點了,因此如果有大量併發請求,Messenger就會顯得力不從心了,這時AIDL(Android 介面定義語言)就派上用場了, 但實際上Messenger 的跨程序方式其底層實現 就是AIDL,只不過android系統幫我們封裝成透明的Messenger罷了 。因此,如果我們想讓服務同時處理多個請求,則應該使用 AIDL。 在此情況下,服務必須具備多執行緒處理能力,並採用執行緒安全式設計。使用AIDL必須建立一個定義程式設計介面的 .aidl 檔案。Android SDK 工具利用該檔案生成一個實現介面並處理 IPC 的抽象類,隨後可在服務內對其進行擴充套件。

  以上3種實現方式,我們可以根據需求自由的選擇,但需要注意的是大多數應用“都不會”使用 AIDL 來建立繫結服務,因為它可能要求具備多執行緒處理能力,並可能導致實現的複雜性增加。因此,AIDL 並不適合大多數應用,本篇中也不打算闡述如何使用AIDL(後面會另開一篇分析AIDL),接下來我們分別針對擴充套件 Binder 類和Messenger的使用進行分析。

4.1 擴充套件 Binder 類

  前面描述過,如果我們的服務僅供本地應用使用,不需要跨程序工作,則可以實現自有 Binder 類,讓客戶端通過該類直接訪問服務中的公共方法。其使用開發步驟如下

  • 1.建立BindService服務端,繼承自Service並在類中,建立一個實現IBinder 介面的例項物件並提供公共方法給客戶端呼叫
  • 2.從 onBind() 回撥方法返回此 Binder 例項。
  • 3.在客戶端中,從 onServiceConnected() 回撥方法接收 Binder,並使用提供的方法呼叫繫結服務。

  注意:此方式只有在客戶端和服務位於同一應用和程序內才有效,如對於需要將 Activity 繫結到在後臺播放音樂的自有服務的音樂應用,此方式非常有效。另一點之所以要求服務和客戶端必須在同一應用內,是為了便於客戶端轉換返回的物件和正確呼叫其 API。服務和客戶端還必須在同一程序內,因為此方式不執行任何跨程序編組。
  以下是一個擴充套件 Binder 類的例項,先看看Service端的實現BindService.java

package com.zejian.ipctest.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * Created by zejian
 * Time 2016/10/2.
 * Description:繫結服務簡單例項--服務端
 */
public class LocalService extends Service{
    private final static String TAG = "wzj";
    private int count;
    private boolean quit;
    private Thread thread;
    private LocalBinder binder = new LocalBinder();

    /**
     * 建立Binder物件,返回給客戶端即Activity使用,提供資料交換的介面
     */
    public class LocalBinder extends Binder {
        // 宣告一個方法,getService。(提供給客戶端呼叫)
        LocalService getService() {
            // 返回當前物件LocalService,這樣我們就可在客戶端端呼叫Service的公共方法了
            return LocalService.this;
        }
    }

    /**
     * 把Binder類返回給客戶端
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }


    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Service is invoke Created");
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 每間隔一秒count加1 ,直到quit為true。
                while (!quit) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                }
            }
        });
        thread.start();
    }

    /**
     * 公共方法
     * @return
     */
    public int getCount(){
        return count;
    }
    /**
     * 解除繫結時呼叫
     * @return
     */
     @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "Service is invoke onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "Service is invoke Destroyed");
        this.quit = true;
        super.onDestroy();
    }
}

  BindService類繼承自Service,在該類中建立了一個LocalBinder繼承自Binder類,LocalBinder中聲明瞭一個getService方法,客戶端可訪問該方法獲取LocalService物件的例項,只要客戶端獲取到LocalService物件的例項就可呼叫LocalService服務端的公共方法,如getCount方法,值得注意的是,我們在onBind方法中返回了binder物件,該物件便是LocalBinder的具體例項,而binder物件最終會返回給客戶端,客戶端通過返回的binder物件便可以與服務端實現互動。接著看看客戶端BindActivity的實現:

package com.zejian.ipctest.service;

import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
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.widget.Button;

import com.zejian.ipctest.R;

/**
 * Created by zejian
 * Time 2016/10/2.
 * Description:繫結服務例項--客戶端
 */
public class BindActivity extends Activity {
    protected static final String TAG = "wzj";
    Button btnBind;
    Button btnUnBind;
    Button btnGetDatas;
    /**
     * ServiceConnection代表與服務的連線,它只有兩個方法,
     * onServiceConnected和onServiceDisconnected,
     * 前者是在操作者在連線一個服務成功時被呼叫,而後者是在服務崩潰或被殺死導致的連線中斷時被呼叫
     */
    private ServiceConnection conn;
    private LocalService mService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bind);
        btnBind = (Button) findViewById(R.id.BindService);
        btnUnBind = (Button) findViewById(R.id.unBindService);
        btnGetDatas = (Button) findViewById(R.id.getServiceDatas);
        //建立繫結物件
        final Intent intent = new Intent(this, LocalService.class);

        // 開啟繫結
        btnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "繫結呼叫:bindService");
                //呼叫繫結方法
                bindService(intent, conn, Service.BIND_AUTO_CREATE);
            }
        });
        // 解除繫結
        btnUnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "解除繫結呼叫:unbindService");
                // 解除繫結
                if(mService!=null) {
                    mService = null;
                    unbindService(conn);
                }
            }
        });

        // 獲取資料
        btnGetDatas.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mService != null) {
                    // 通過繫結服務傳遞的Binder物件,獲取Service暴露出來的資料

                    Log.d(TAG, "從服務端獲取資料:" + mService.getCount());
                } else {

                    Log.d(TAG, "還沒繫結呢,先繫結,無法從服務端獲取資料");
                }
            }
        });


        conn = new ServiceConnection() {
            /**
             * 與伺服器端互動的介面方法 繫結服務的時候被回撥,在這個方法獲取繫結Service傳遞過來的IBinder物件,
             * 通過這個IBinder物件,實現宿主和Service的互動。
             */
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.d(TAG, "繫結成功呼叫:onServiceConnected");
                // 獲取Binder
                LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
                mService = binder.getService();
            }
            /**
             * 當取消繫結的時候被回撥。但正常情況下是不被呼叫的,它的呼叫時機是當Service服務被意外銷燬時,
             * 例如記憶體的資源不足時這個方法才被自動呼叫。
             */
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mService=null;
            }
        };
    }
}

  在客戶端中我們建立了一個ServiceConnection物件,該代表與服務的連線,它只有兩個方法, onServiceConnected和onServiceDisconnected,其含義如下:

  • onServiceConnected(ComponentName name, IBinder service)
    系統會呼叫該方法以傳遞服務的 onBind() 方法返回的 IBinder。其中service便是服務端返回的IBinder實現類物件,通過該物件我們便可以呼叫獲取LocalService例項物件,進而呼叫服務端的公共方法。而ComponentName是一個封裝了元件(Activity, Service, BroadcastReceiver, or ContentProvider)資訊的類,如包名,元件描述等資訊,較少使用該引數。

  • onServiceDisconnected(ComponentName name)
    Android 系統會在與服務的連線意外中斷時(例如當服務崩潰或被終止時)呼叫該方法。注意:當客戶端取消繫結時,系統“絕對不會”呼叫該方法

conn = new ServiceConnection() {

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.d(TAG, "繫結成功呼叫:onServiceConnected");
                // 獲取Binder
                LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
                mService = binder.getService();
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mService=null;
            }
        };

  在onServiceConnected()被回撥前,我們還需先把當前Activity繫結到服務LocalService上,繫結服務是通過通過bindService()方法,解綁服務則使用unbindService()方法,這兩個方法解析如下:

  • bindService(Intent service, ServiceConnection conn, int flags)
    該方法執行繫結服務操作,其中Intent是我們要繫結的服務(也就是LocalService)的意圖,而ServiceConnection代表與服務的連線,它只有兩個方法,前面已分析過,flags則是指定繫結時是否自動建立Service。0代表不自動建立、BIND_AUTO_CREATE則代表自動建立。

  • unbindService(ServiceConnection conn)
    該方法執行解除繫結的操作,其中ServiceConnection代表與服務的連線,它只有兩個方法,前面已分析過。

Activity通過bindService()繫結到LocalService後,ServiceConnection#onServiceConnected()便會被回撥並可以獲取到LocalService例項物件mService,之後我們就可以呼叫LocalService服務端的公共方法了,最後還需要在清單檔案中宣告該Service。而客戶端佈局檔案實現如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/BindService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="繫結伺服器"
        />

    <Button
        android:id="@+id/unBindService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="解除繫結"
        />

    <Button
        android:id="@+id/getServiceDatas"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="獲取服務方資料"
        />
</LinearLayout>

  我們執行程式,點選繫結服務並多次點選繫結服務接著多次呼叫LocalService中的getCount()獲取資料,最後呼叫解除繫結的方法移除服務,其結果如下:
這裡寫圖片描述
  通過Log可知,當我們第一次點選繫結服務時,LocalService服務端的onCreate()、onBind方法會依次被呼叫,此時客戶端的ServiceConnection#onServiceConnected()被呼叫並返回LocalBinder物件,接著呼叫LocalBinder#getService方法返回LocalService例項物件,此時客戶端便持有了LocalService的例項物件,也就可以任意呼叫LocalService類中的宣告公共方法了。更值得注意的是,我們多次呼叫bindService方法繫結LocalService服務端,而LocalService得onBind方法只調用了一次,那就是在第一次呼叫bindService時才會回撥onBind方法。接著我們點選獲取服務端的資料,從Log中看出我們點選了3次通過getCount()獲取了服務端的3個不同資料,最後點選解除繫結,此時LocalService的onUnBind、onDestroy方法依次被回撥,並且多次繫結只需一次解綁即可。此情景也就說明了繫結狀態下的Service生命週期方法的呼叫依次為onCreate()、onBind、onUnBind、onDestroy。ok~,以上便是同一應用同一程序中客戶端與服務端的繫結回撥方式。

4.2 使用Messenger

  前面瞭解瞭如何使用IBinder應用內同一程序的通訊後,我們接著來了解服務與遠端程序(即不同程序間)通訊,而不同程序間的通訊,最簡單的方式就是使用 Messenger 服務提供通訊介面,利用此方式,我們無需使用 AIDL 便可執行程序間通訊 (IPC)。以下是 Messenger 使用的主要步驟:

  • 1.服務實現一個 Handler,由其接收來自客戶端的每個呼叫的回撥

  • 2.Handler 用於建立 Messenger 物件(對 Handler 的引用)

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

  • 4.客戶端使用 IBinder 將 Messenger(引用服務的 Handler)例項化,然後使用Messenger將 Message 物件傳送給服務

  • 5.服務在其 Handler 中(在 handleMessage() 方法中)接收每個 Message

以下是一個使用 Messenger 介面的簡單服務示例,服務端程序實現如下:

package com.zejian.ipctest.messenger;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;

/**
 * Created by zejian
 * Time 2016/10/3.
 * Description:Messenger服務端簡單例項,服務端程序
 */
public class MessengerService extends Service {

    /** Command to the service to display a message */
    static final int MSG_SAY_HELLO = 1;
    private static final String TAG ="wzj" ;

    /**
     * 用於接收從客戶端傳遞過來的資料
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Log.i(TAG, "thanks,Service had receiver message from client!");
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * 建立Messenger並傳入Handler例項物件
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * 當繫結Service時,該方法被呼叫,將通過mMessenger返回一個實現
     * IBinder介面的例項物件
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "Service is invoke onBind");
        return mMessenger.getBinder();
    }
}

  首先我們同樣需要建立一個服務類MessengerService繼承自Service,同時建立一個繼承自Handler的IncomingHandler物件來接收客戶端程序傳送過來的訊息並通過其handleMessage(Message msg)進行訊息處理。接著通過IncomingHandler物件建立一個Messenger物件,該物件是與客戶端互動的特殊物件,然後在Service的onBind中返回這個Messenger物件的底層Binder即可。下面看看客戶端程序的實現:

package com.zejian.ipctest.messenger;

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.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.zejian.ipctest.R;

/**
 * Created by zejian
 * Time 2016/10/3.
 * Description: 與伺服器互動的客戶端
 */
public class ActivityMessenger extends Activity {
    /**
     * 與服務端互動的Messenger
     */
    Messenger mService = null;

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

    /**
     * 實現與服務端連結的物件
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            /**
             * 通過服務端傳遞的IBinder物件,建立相應的Messenger
             * 通過該Messenger物件與服務端進行互動
             */
            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;
        // 建立與服務互動的訊息實體Message
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            //傳送訊息
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenager);
        Button bindService= (Button) findViewById(R.id.bindService);
        Button unbindService= (Button) findViewById(R.id.unbindService);
        Button sendMsg= (Button) findViewById(R.id.sendMsgToService);

        bindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("zj","onClick-->bindService");
                //當前Activity繫結服務端
                bindService(new Intent(ActivityMessenger.this, MessengerService.class), mConnection,
                        Context.BIND_AUTO_CREATE);
            }
        });

        //傳送訊息給服務端
        sendMsg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sayHello(v);
            }
        });


        unbindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Unbind from the service
                if (mBound) {
                    Log.d("zj","onClick-->unbindService");
                    unbindService(mConnection);
                    mBound = false;
                }
            }
        });
    }

}

  在客戶端程序中,我們需要建立一個ServiceConnection物件,該物件代表與服務端的連結,當呼叫bindService方法將當前Activity繫結到MessengerService時,onServiceConnected方法被呼叫,利用服務端傳遞給來的底層Binder物件構造出與服務端互動的Messenger物件,接著建立與服務互動的訊息實體Message,將要發生的資訊封裝在Message中並通過Messenger例項物件傳送給服務端。關於ServiceConnection、bindService方法、unbindService方法,前面已分析過,這裡就不重複了,最後我們需要在清單檔案宣告Service和Activity,由於要測試不同程序的互動,則需要將Service放在單獨的程序中,因此Service宣告如下:

<service android:name=".messenger.MessengerService"
         android:process=":remote"
        />

其中android:process=":remote"代表該Service在單獨的程序中建立,最後我們執行程式,結果如下:
這裡寫圖片描述
  接著多次點選繫結服務,然後傳送資訊給服務端,最後解除繫結,Log列印如下:
這裡寫圖片描述
  通過上述例子可知Service服務端確實收到了客戶端傳送的資訊,而且在Messenger中進行資料傳遞必須將資料封裝到Message中,因為Message和Messenger都實現了Parcelable介面,可以輕鬆跨程序傳遞資料(關於Parcelable介面可以看博主的另一篇文章:序列化與反序列化之Parcelable和Serializable淺析),而Message可以傳遞的資訊載體有,what,arg1,arg2,Bundle以及replyTo,至於object欄位,對於同一程序中的資料傳遞確實很實用,但對於程序間的通訊,則顯得相當尷尬,在android2.2前,object不支援跨程序傳輸,但即便是android2.2之後也只能傳遞android系統提供的實現了Parcelable介面的物件,也就是說我們通過自定義實現Parcelable介面的物件無法通過object欄位來傳遞,因此object欄位的實用性在跨程序中也變得相當低了。不過所幸我們還有Bundle物件,Bundle可以支援大量的資料型別。接著從Log我們也看出無論是使用拓展Binder類的實現方式還是使用Messenger的實現方式,它們的生命週期方法的呼叫順序基本是一樣的,即onCreate()、onBind、onUnBind、onDestroy,而且多次繫結中也只有第一次時才呼叫onBind()。好~,以上的例子演示瞭如何在服務端解釋客戶端傳送的訊息,但有時候我們可能還需要服務端能迴應客戶端,這時便需要提供雙向訊息傳遞了,下面就來實現一個簡單服務端與客戶端雙向訊息傳遞的簡單例子。
  先來看看服務端的修改,在服務端,我們只需修改IncomingHandler,收到訊息後,給客戶端回覆一條資訊。

  /**
     * 用於接收從客戶端傳遞過來的資料
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Log.i(TAG, "thanks,Service had receiver message from client!");
                    //回覆客戶端資訊,該物件由客戶端傳遞過來
                    Messenger client=msg.replyTo;
                    //獲取回覆資訊的訊息實體
                    Message replyMsg=Message.obtain(null,MessengerService.MSG_SAY_HELLO);
                    Bundle bundle=new Bundle();
                    bundle.putString("reply","ok~,I had receiver message from you! ");
                    replyMsg.setData(bundle);
                    //向客戶端傳送訊息
                    try {
                        client.send(replyMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }

                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

  接著修改客戶端,為了接收服務端的回覆,客戶端也需要一個接收訊息的Messenger和Handler,其實現如下:

  /**
     * 用於接收伺服器返回的資訊
     */
    private Messenger mRecevierReplyMsg= new Messenger(new ReceiverReplyMsgHandler());


    private static class ReceiverReplyMsgHandler extends Handler{
        private static final String TAG = "zj";

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                //接收服務端回覆
                case MessengerService.MSG_SAY_HELLO:
                    Log.i(TAG, "receiver message from service:"+msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

  除了新增以上程式碼,還需要在傳送資訊時把接收伺服器端的回覆的Messenger通過Message的replyTo引數傳遞給服務端,以便作為同學橋樑,程式碼如下:

 public void sayHello(View v) {
        if (!mBound) return;
        // 建立與服務互動的訊息實體Message
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        //把接收伺服器端的回覆的Messenger通過Message的replyTo引數傳遞給服務端
        msg.replyTo=mRecevierReplyMsg;
        try {
            //傳送訊息
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

  ok~,到此服務端與客戶端雙向訊息傳遞的簡單例子修改完成,我們執行一下程式碼,看看Log列印,如下:
這裡寫圖片描述
  由Log可知,服務端和客戶端確實各自收到了資訊,到此我們就把採用Messenge進行跨程序通訊的方式分析完了,最後為了輔助大家理解,這裡提供一張通過Messenge方式進行程序間通訊的原理圖:
這裡寫圖片描述

4.3 關於繫結服務的注意點

  1.多個客戶端可同時連線到一個服務。不過,只有在第一個客戶端繫結時,系統才會呼叫服務的 onBind() 方法來檢索 IBinder。系統隨後無需再次呼叫 onBind(),便可將同一 IBinder 傳遞至任何其他繫結的客戶端。當最後一個客戶端取消與服務的繫結時,系統會將服務銷燬(除非 startService() 也啟動了該服務)。

  2.通常情況下我們應該在客戶端生命週期(如Activity的生命週期)的引入 (bring-up) 和退出 (tear-down) 時刻設定繫結和取消繫結操作,以便控制繫結狀態下的Service,一般有以下兩種情況:

  • 如果只需要在 Activity 可見時與服務互動,則應在 onStart() 期間繫結,在 onStop() 期間取消繫結。

  • 如果希望 Activity 在後臺停止執行狀態下仍可接收響應,則可在 onCreate() 期間繫結,在 onDestroy() 期間取消繫結。需要注意的是,這意味著 Activity 在其整個執行過程中(甚至包括後臺執行期間)都需要使用服務,因此如果服務位於其他程序內,那麼當提高該程序的權重時,系統很可能會終止該程序。

  3.通常情況下(注意),切勿在 Activity 的 onResume() 和 onPause() 期間繫結和取消繫結,因為每一次生命週期轉換都會發生這些回撥,這樣反覆繫結與解綁是不合理的。此外,如果