淺談 Android Service

PassersHowe發表於2017-04-13

前言:本文所寫的是博主的個人見解,如有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文連結demo連結

Serviec(服務)簡述

  1. 什麼是Service
    Service 是一個可以在後臺執行長時間執行操作而不提供使用者介面的應用元件。Service 可以由其他應用元件啟動,即便使用者切換到其他應用,Service 仍將在後臺繼續執行。此外,元件可以繫結到 Service ,進行互動,甚至執行程式間通訊(IPC)。例如,Service 可以處理網路事務,播放音樂,執行檔案讀寫或與內容提供程式互動,這一切都可以在後臺進行。

  2. 服務的兩種基本形式

    啟動

    當應用元件(如 Activity )通過呼叫 startService() 啟動服務時,服務處於 “啟動” 狀態,一旦啟動,服務可以在後臺無限期執行,即使啟動服務的元件被銷燬了也不受影響。已經啟動的服務通常執行單一操作,而且不會講結果返回給呼叫方。例如,它可能通過網路下載或者上傳檔案。操作完成後,服務會自動停止執行。複製程式碼

    繫結

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

    雖然服務的形式有兩種,但服務可以同時以兩種方式執行,也就是說,它既可以是啟動服務(以無限期執行),也允許繫結。問題在於你是否實現一組回撥方法: onStartCommand() (允許元件啟動服務) 和 onBind() (允許繫結服務)。

    無論應用是否處於啟動狀態,繫結狀態,或是處於啟動並且繫結狀態,任何應用元件均可以像使用 Activity 那樣通過呼叫 Intent 來使用服務(即使服務來自另一個應用)。不過,你可以通過清單檔案宣告服務為私有服務,阻止其他應用訪問。

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

認識 Service

要建立服務,必須建立 Service 的子類(或者使用它的一個現有子類)。需要重寫一些回撥方法,以處理服務生命週期的有些關鍵方面,並提供一種機制將元件繫結到服務應重寫的最重要的回撥方法包括:

  • onStartCommand()

     當另一個元件(如 Activity )通過呼叫 startService() 請求啟動服務時,系統將呼叫此方法。一旦執行此方法,服務會啟動並可在後臺無限期執行。如果你實現了此方法,在服務工作完成後,需要呼叫 stopSelf() 或 stopService() 來停止服務(如果只是提供繫結則無需實現此方法)複製程式碼
  • onBind()

     當另一個元件呼叫 bindService() 與服務繫結時,系統將呼叫此方法。在此方法中必須返回 IBinder 提供一個介面,供客戶端與伺服器進行通訊。如果不希望允許繫結,則可以返回 null複製程式碼
  • onCreate()

     首次建立服務時,系統呼叫次方法來執行一次性程式(在呼叫 onStartCommand() 或 onBind() 之前)。如果服務已經執行則不會呼叫此方法。複製程式碼
  • onDestory()

     當服務不再使用且將被銷燬是,系統呼叫此方法。服務應該實現方法來清理所有資源,如執行緒、註冊的監聽器,接收器等。複製程式碼

如果元件呼叫 startService()啟動服務(會導致對 onStartCommand() 的呼叫),則服務將一直執行,知道服務使用 stopSelf() 自行停止執行或者其他元件呼叫 stopService() 停止它為止。

如果元件呼叫 bindService() 來建立服務(且未呼叫 onStartCommand() ),則服務只會在該元件與其繫結時執行,一旦服務與所有客戶端全部取消繫結時,系統會銷燬它。

僅當記憶體過低且系統必須回收資源供具有使用者焦點的 Activity 使用時,Android 系統才會強制停止服務。如果將服務繫結到具有使用者焦點的 Activity ,它被系統終止的可能性不大;如果將服務宣告為在前臺執行,則它幾乎永遠不會終止。如果服務已經啟動且要長時間執行,則系統會隨著時間推移降低服務在後臺列表中的位置,服務也將變得容易被終止;如果服務是啟動服務,則必須將其設計為能夠妥善處理系統對它的重啟。如果系統終止服務,那麼一旦資源變得再次可用,系統便會重啟服務(取決於從 onStartCommand() )返回的值。

使用清單宣告服務

如同 Activity (或是其他元件)一樣,必須在應用的清單檔案中宣告所有服務。

要宣告服務,清新增 <service> 元素作為 <application> 元素的子元素。例如

<manifest ... >
  ...
  <application ... >
      <service android:name=".DemoService" />
      ...
  </application>
</manifest>複製程式碼

<service> 元素還可以包含其他屬性,定義一些特性,如啟動服務及執行所需要的許可權。其中 android:name 屬性是唯一必須的屬性,用於指定服務的類名。應用一經發布,不可更改類名,不然會因依賴顯示 Intent 啟動或繫結服務而導致破壞程式碼的風險。

為了確保應用的安全性,請使用顯示 Intent 或 繫結 Service , 而且不要為服務宣告 Intent 過濾器。啟動哪個服務存在不確定性,對這種不確定性的考量非常有必要,可以為服務提供 Intent 過濾器並從 Intent 中排除想應的元件名稱,但必須使用 setPackage() 方法設定 Intent 的軟體包, 這樣做可以消除目標服務的不確定性。

此外還可以通過新增 android:exporeted ="false" ,確保服務為應用私有,可以阻止其他應用啟動你的服務,同理,使用顯示 Intent 時也是如此。

啟動服務的建立

啟動服務由另一個元件通過呼叫 startService() 啟動,服務的 onStartCommand() 方法也將被呼叫。

服務啟動之後,其生命週期完全獨立,且可以在後臺無限期地執行,即使啟動服務的元件被銷燬了,該服務也不受影響。如果要結束該服務,可以呼叫 stopSelf() 自行停止執行,或者由另一個元件呼叫 stopService() 來停止。

應用元件(如 Activity )可以通過呼叫 startService() 方法且傳遞一個 Intent 物件(指定服務和其所有資料)來啟動服務。服務通過 onStartCommand() 方法來接收 Intent 物件。

例如,某個 Activity 需要儲存一些資料到線上的資料庫中,這時它可以啟用一個協同服務,呼叫 startService() 並傳遞一個 Intent ,提供需要儲存的資料。服務通過 onStartCommand() 接收 Intent ,連線到網際網路並執行資料庫事務,事務完成後,服務將自行停止執行且隨即被銷燬。

注意: 預設情況下,服務與服務宣告所在的應用處於同一程式,而且執行在主執行緒中。因此,如果是執行一些耗時操作,需要在服務內啟動新的執行緒,避免影響應用的效能。

你可以通過擴充套件兩個類來建立啟動服務:

Service

這是適用於所有服務的基類。預設情況下該服務將在應用的主執行緒中執行,你需要建立一個新的執行緒供服務工作,避免影響正在執行的所有 Activity 的效能。複製程式碼

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

下面演示如何使用其中任意一個類來實現服務。

  1. 擴充套件 IntentService 類

    由於大多數啟動服務不用同時處理多個請求(這種多執行緒情況可能很危險),因此選擇 IntentService 類實現服務無疑是最好的。

    IntentServic 執行以下的操作:

    • 建立預設的工作執行緒,在主執行緒外執行傳遞給 onStartConmmand() 的所有 Intent。
    • 建立工作佇列,將 Intent 逐一傳遞給 onHandleIntent() 實現,不用擔心多執行緒問題。
    • 處理所有啟動請求後停止服務(不用手動呼叫 stopSelf() 來結束服務)
    • 提供 onBind() 的預設實現(返回 null )。
    • 提供 onStartCommand() 的預設實現,可以將 Intent 依次傳送到工作佇列 和 onHandleIntent() 實現。

    綜上所述,只需要實現 onHandleIntent() 來完成客戶端提供的工作即可。(需要為服務提供建構函式)

    下面是 IntentService 的實現示例:

        public class DemoIntentService extends IntentService {
         private static final String TAG = "DemoIntentService";
         /**
          * Creates an IntentService.  Invoked by your subclass's constructor.
          *
          * @param name Used to name the worker thread, important only for debugging.
          */
         public DemoIntentService(String name) {
             super(name);
         }
    
         @Override
         protected void onHandleIntent(@Nullable Intent intent) {
             //模擬耗時操作,執行緒沉睡3秒
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
    
             }
         }
    
         @Override
         public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
             Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
             Log.i(TAG, "service starting");
             return super.onStartCommand(intent, flags, startId);
         }
     }複製程式碼

    只需要一個建構函式和一個 onHandleIntent() 實現即可。

    如果重寫其他回撥方法(如 onCreate() 、onStartCommand() 或 onDestroy),要確保呼叫超類實現,讓 IntentService 能夠妥善處理工作執行緒的生命週期。

    例如, onStartCommand() 必須返回預設實現(將 Intent 傳遞給 onHandleIntent() ):

     @Override
     public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
         Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
         Log.i(TAG, "service starting");
         return super.onStartCommand(intent, flags, startId);
     }複製程式碼

    除了 onHandleIntent() 之外 ,無需呼叫的方法就是 onBind() (僅當服務允許繫結時,才需要實現該方法)

  2. 擴充套件服務類

    如上部分所述,使用 IntentService 簡化了啟動服務的實現,如果要服務執行多執行緒(不是通過工作佇列處理啟動請求),則可以擴充套件 Service 類來處理每個 Intent 。

    以下是 Service 類實現程式碼示例,該類執行的工作與上面的 IntentService 示例相同。對每個啟動請求,它都使用工作執行緒執行作業,且每次僅處理一個請求。

    public class DemoService extends Service {
     private Looper mServiceLooper;
     private ServiceHandle mServiceHandle;
    
     @Override
     public void onCreate() {
         //啟動執行該服務的執行緒
         HandlerThread thread = new HandlerThread("ServiceStartArguments", Process
                 .THREAD_PRIORITY_BACKGROUND);
         thread.start();
    
         //獲取HandlerThread的Looper並將其用於自己的Handler
         mServiceLooper = thread.getLooper();
         mServiceHandle = new ServiceHandle(mServiceLooper);
    
     }
    
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    
         //每一個啟動請求,傳送一個訊息來啟動一個工作並提交開始Id
         Message msg = mServiceHandle.obtainMessage();
         msg.arg1 = startId;
         mServiceHandle.sendMessage(msg);
    
         return START_STICKY;
     }
    
     @Nullable
     @Override
     public IBinder onBind(Intent intent) {
         return null;
     }
    
     @Override
     public void onDestroy() {
         Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
     }
    
     //從執行緒接收訊息的處理程式
     private final class ServiceHandle extends Handler {
         public ServiceHandle(Looper looper) {
             super(looper);
         }
    
         @Override
         public void handleMessage(Message msg) {
             //模擬耗時操作,執行緒沉睡3秒
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
             }
    
             stopSelf(msg.arg1);
         }
     }
    }複製程式碼

    如上面的示例,與使用 IntentService 相比,這需顯得複雜一些。

    但是,自己處理 onStartCommand() 的每個呼叫,因此可以同時執行多個請求。上面的示例沒有實現。如果你有需要,可以為每個請求建立一個新執行緒,然後立即執行這些執行緒(不是等待上一個請求完成)。

    注意: onStartCommand() 方法必須返回整型數。 該返回值用於描述系統改如何在服務終止的情況下繼續執行服務,從 onStartCommand() 返回的值必須是以下常量注意之一:

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

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

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

  3. 啟動服務

    可以通過將 Intent (指定要啟動的服務)傳遞給 startService,從 Activity 或其他應用元件啟動服務。 Android 系統呼叫服務的 onStartCommand() 方法,並向其傳遞 Intent 。(請勿直接呼叫 onStartCommand() )

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

    Intent intent = new Intent(this,DemoService.class);
    startService(intent);複製程式碼

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

    如果服務未提供繫結,則使用 startService() 傳遞的 Intent 是應用元件與服務之間唯一的通訊模式。但是,如果你希望服務返回結果,則啟動服務的客戶端可以為廣播建立一個 PendingIntent(使用 getBroadcast()),並通過啟動服務的 Intent 傳遞給服務,然後服務可以通過廣播來傳遞結果。

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

  4. 停止服務
    啟動服務必須管理自己的生命週期。也就是說,除非系統必須回收記憶體資源,否則系統不會停止或銷燬服務,而且服務會在 onStartCommand() 返回後繼續執行。因此,服務必須通過呼叫 stopSelf() 自行停止執行,或者其他元件呼叫 stopService() 來停止。

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

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

    注意: 為了避免浪費系統資源和小號電池電量,應用必須在工作完成後停止其服務。如有必要,其他元件可以通過呼叫 stopService 來停止服務,即使為服務啟用了繫結,一旦服務受到對 onStartCommand 的呼叫, 始終需要親自停止服務。

建立繫結服務

繫結服務允許應用通過呼叫 bindService() 與其繫結,以便建立長期連線(通常不允許元件通過呼叫 startService() 來啟動它)。

如需與 Activity 和其他應用元件中的服務進行互動,或者需要跨程式通訊,則應建立繫結服務。

建立繫結服務,必須實現 onBind() 回撥方法以返回 IBinder ,用於定義與服務通訊的介面。然後其他應用元件可以呼叫 bindService() 來檢索該介面,並開始對服務呼叫方法。服務只用於與其繫結的應用元件,因此如果沒有元件繫結到服務,則系統會銷燬服務(不必通過 onStartCommand() 啟動的服務來停止繫結服務)。

要建立繫結服務,必須定義與指定客戶端與服務通訊的介面。服務與客戶端之間的這個介面必須是 IBinder 的實現,且服務必須從 onBind() 回撥方法返回它。一旦客戶端收到 IBinder ,即可開始通過該介面與伺服器進行互動。

多個客戶端可以同時繫結到服務,客戶端完成與服務的互動後,會呼叫 unbindService() 取消繫結。一旦沒有客戶端繫結到該服務,系統就會銷燬它。

有多種方法實現繫結服務,實現方式比啟動服務更復雜,這裡就不詳說了,後續會單獨的說明。

向使用者傳送通知

服務一旦執行起來,即可使用 Toast 通知或狀態列通知來通知使用者所發生的事情。

Toast 通知是指出現在當前視窗的表面、片刻隨即消失不見的訊息,而狀態通知欄則在狀態列中隨訊息一起提供圖示,使用者可以選擇圖示來採取操作(例如啟動 Activity )。

通常,當某些後臺工作完成(錄入檔案下載完成)且使用者現在可以對其進行操作時,狀態列通知是最佳方法。當使用者從展開檢視中選定通知時,通知即可啟動 Activity (例如檢視下載的檔案)。

在前臺執行服務

前臺服務被認為是使用者主動意識到的一種服務,在記憶體不足時,系統也不會考慮將其終止。前臺服務必須為狀態列提供通知,放在 “正在進行” 標題下方,除非服務停止或者從前臺溢位,否則不會清除通知。

例如,通過服務播放音樂的音樂播放器設定為在前臺執行,使用者能明確意識到其操作。狀態列中的通知可能表示正在播放的歌曲,並允許使用者啟動 Activity 來與音樂播放器進行互動。

要請求讓服務執行於前臺,可以呼叫 startForeground() 。此方法採用兩個引數:唯一標識通知的整型數和通知欄的 Notification 。例如:

Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle("Notification title")
        .setContentText("Notification describe")
        .setSmallIcon(R.mipmap.ic_launcher);

Intent notificationIntent = new Intent(this,DemoActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);

builder.setContentIntent(pendingIntent);
Notification notification = builder.build();
startForeground(NOTIFICATION_ID,notification);複製程式碼

注意:提供給 startForegrond() 的整型 ID 不可以為0。

要從前臺移除服務,需要呼叫 stopForeground() 。次方法採用一個布林值,指示是否移除狀態列通知,此方法不會停止服務。但是,如果你的服務正在前臺執行時將其停止,則通知也會被移除。

管理服務生命週期

服務的生命週期比 Activity 的生週期要簡單多。但是密切關注如何建立和銷燬服務反而更重要,因為服務可以在使用者沒有意識到的情況下執行於後臺。

服務生命週期可以(從建立到銷燬)可以遵循兩條不同的路徑:

  • 啟動服務
    該服務在其他元件呼叫 startService() 時建立,然後無限期執行,且必須通過呼叫 stopSelf() 來自行停止執行。此外,其他元件也可以通過呼叫 stopService 來停止服務。服務停止後,系統將其銷燬。

  • 繫結服務
    該服務在另一個元件(客戶端)呼叫 bindService() 時建立,然後客戶端通過 IBinder 介面與服務進行通訊。客戶端可以呼叫 unbindService() 關閉連線。多個客戶端可以繫結到相同的服務,而且當所有繫結取消後,系統會銷燬該服務(服務不必自行停止)。

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

實現生命週期回撥

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

public class TestService extends Service {

    int mStartMode;       // 指示如果服務被殺死,該如何操作
    IBinder mBinder;      // 客戶端繫結介面
    boolean mAllowRebind; // 指示是否應使用onRebind

    @Override
    public void onCreate() {
        //服務正在建立中
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //呼叫了startService(),服務正在啟動
        return mStartMode;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //客戶端繫結到具有bindService()的服務
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        //所有客戶端都使用unbindService()取消繫結
        return mAllowRebind;
    }

    @Override
    public void onRebind(Intent intent) {
        //客戶端繫結到具有bindService()的服務,
        //onUnbind()已經被呼叫
    }

    @Override
    public void onDestroy() {
        //該服務已不再使用並被銷燬
    }
}複製程式碼

注:與 Activity 生命週期回撥方法不同,你不需要呼叫這些方法的超類實現。

淺談 Android Service
Service生命週期圖

左圖顯示了使用 startService() 所建立的服務的生命週期,右圖顯示了使用 bindService() 所建立的服務的生命週期。

通過實現這些方法,你可以監控這兩種服務生命週期:

  • 服務的整個生命週期從呼叫 onCreate() 開始,到 onDestroy() 返回時結束。與 Activity 類似,服務也在 onCreate() 中完成初始設定,在 onDestroy() 中釋放所有剩餘資源。例如音樂播放服務可以在 onCreate() 中建立用於播放音樂的執行緒,然後可以在 onDestroy() 中停止該執行緒。無論服務是通過 startService() 還是 bindService() 建立,都會為所有服務呼叫 onCreate() 和 onDestroy() 方法。

  • 服務的有效生命週期從呼叫 onStartCommand() 或 onBind() 方法開始。每種方法均有 Intent 物件,該物件分別傳遞到 startService() 或 bindService() 。
    對於啟動服務,有效生命週期與整個生命週期同時結束(即便是在 onStartCommand() 返回之後,服務仍然處於活動狀態)。對於繫結服務,有效生命週期在 onUnbind() 返回時結束。

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

儘管該圖分開介紹通過 startService() 建立的服務和通過 bindService() 建立的服務,但是記住一點,不管啟動方式如何,任何服務均有可能允許客戶端與其繫結。因此,最初使用 onStartCommand()(通過客戶端呼叫 startService())啟動的服務仍可接收對 onBind() 的呼叫(當客戶端呼叫 bindService() 時)。

相關文章