淺談 Android Service

前言:本文所寫的是博主的我的看法,若有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文連接demo連接javascript

Serviec(服務)簡述

  1. 什麼是Service
    Service 是一個能夠在後臺執行長時間運行操做而不提供用戶界面的應用組件。Service 能夠由其餘應用組件啓動,即使用戶切換到其餘應用,Service 仍將在後臺繼續運行。此外,組件能夠綁定到 Service ,進行交互,甚至執行進程間通訊(IPC)。例如,Service 能夠處理網絡事務,播放音樂,執行文件讀寫或與內容提供程序交互,這一切均可以在後臺進行。java

  2. 服務的兩種基本形式android

    啓動git

    當應用組件(如 Activity )經過調用 startService() 啓動服務時,服務處於 「啓動」 狀態,一旦啓動,服務能夠在後臺無限期運行,即便啓動服務的組件被銷燬了也不受影響。已經啓動的服務一般執行單一操做,並且不會講結果返回給調用方。例如,它可能經過網絡下載或者上傳文件。操做完成後,服務會自動中止運行。複製代碼

    綁定github

    當應用組件經過調用 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 生命週期回調方法不一樣,你不須要調用這些方法的超類實現。

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() 時)。

相關文章
相關標籤/搜索