【Android】Service那點事兒

1.Service簡介

服務是一個應用程序組件,可以在后臺執行長時間運行的操作,不提供用戶界面。一個應用程序組件可以啟動一個服務,它將繼續在后臺運行,即使用戶切換到另一個應用程序。此外,一個組件可以綁定到一個服務與它交互,甚至執行進程間通信(IPC)。例如,一個服務可能處理網絡通信、播放音樂、計時操作或與一個內容提供者交互,都在后臺執行。


2.Service的種類

  • 按運行地點分類:
類別 區別 優點 缺點 應用
本地服務(Local Service) 該服務依附在主進程上 服務依附在主進程上而不是獨立的進程,這樣在一定程度上節約了資源,另外Local服務因為是在同一進程因此不需要IPC,也不需要AIDL。相應bindService會方便很多。 主進程被Kill后,服務便會終止。 如:音樂播放器播放等不需要常駐的服務。
遠程服務(Remote Service) 該服務是獨立的進程 服務為獨立的進程,對應進程名格式為所在包名加上你指定的android:process字符串。由于是獨立的進程,因此在Activity所在進程被Kill的時候,該服務依然在運行,不受其他進程影響,有利于為多個進程提供服務具有較高的靈活性。 該服務是獨立的進程,會占用一定資源,并且使用AIDL進行IPC稍微麻煩一點。 一些提供系統服務的Service,這種Service是常駐的。
  • 按運行類型分類:
類別 區別 應用
前臺服務 會在通知欄顯示onGoing的 Notification 當服務被終止的時候,通知一欄的 Notification 也會消失,這樣對于用戶有一定的通知作用。常見的如音樂播放服務。
后臺服務 默認的服務即為后臺服務,即不會在通知一欄顯示 onGoing的 Notification。 當服務被終止的時候,用戶是看不到效果的。某些不需要運行或終止提示的服務,如天氣更新,日期同步,郵件同步等。
  • 按使用方式分類:
類別 區別
startService啟動的服務 主要用于啟動一個服務執行后臺任務,不進行通信。停止服務使用stopService。
bindService啟動的服務 方法啟動的服務要進行通信。停止服務使用unbindService。
同時使用startService、bindService 啟動的服務 停止服務應同時使用stopService與unbindService。

3.Service的生命周期

不同啟動方式下生命周期對比.png

通過這個圖可以看到,兩種啟動Service的方式以及他們的生命周期,bindService的不同之處在于當綁定的組件銷毀后,對應的service也就被kill了。

  • 被啟動的服務的生命周期
      一個Service被使用startService方法啟動,不管是否調用了bindService(綁定服務)或unbindService(解除綁定服務)到該Service,該Service都會在后臺運行并不受影響。
      一個Service被使用startService方法啟動多少次,onCreate方法只會調用一次,onStartCommand方法將會被調用多次(與startService的次數一致),且系統只會創建一個Service實例(結束該Service也只需要調用一次stopService),該Service會一直在后臺運行,直至調用stopService或調用自身的stopSelf方法。

注:在系統資源不足的情況下,服務有可能被系統結束(kill);

  • 被綁定的服務的生命周期
      如果一個Service在某個Activity中被調用bindService方法啟動,不論bindService被調用幾次,Service的onCreate方法只會執行一次,同時onStartCommand方法始終不會調用。
      當建立連接后,Service會一直運行,除非調用unbindService來接觸綁定、斷開連接或調用該Service的Context不存在了(如Activity被Finish——即通過bindService啟動的Service的生命周期依附于啟動它的Context),系統在這時會自動停止該Service。

  • 被啟動又被綁定的服務的生命周期
      當一個Service在被啟動(startService)的同時又被綁定(bindService),該Service將會一直在后臺運行,并且不管調用幾次,onCreate方法始終只會調用一次,onStartCommand的調用次數與startService調用的次數一致(使用bindService方法不會調用onStartCommand)。同時,調用unBindService將不會停止Service,必須調用stopService或Service自身的stopSelf來停止服務。

官方原文:
If you do allow your service to be started and bound,then when then service has been started,the System does not destory the service when all clients unbind.Instead,you must explicitly stop the service,by calling stopSelf() or stopService().
如果你同意你的服務被開啟和綁定,然后當服務被開啟的時候,當所有的客戶端都解除對服務的綁定android操作系統也不會銷毀這個服務,相反的你必須顯示的調用stopSelf()或者stopService()方法來停止服務。
什么情況下使用:
如果你想要與正在運行的Service取得聯系,那么有兩種方法,一種是使用廣播,另外一種方法就是使用bindService來建立聯系,前者的缺點是如果交流較為頻繁,容易造成性能上的問題,并且BroadcastReceiver本身執行代碼的時間是很短的(也許執行到一半,后面的代碼便不會執行),而后者則沒有這些問題,因此我們肯定選擇使用bindService(這個時候你便同時在使用startService和bindService了,這在Activity中更新Service的某些運行狀態是相當有用的)。

  • 當服務被停止時
      當一個服務被終止(stopServicestopSelfunbindService)時,onDestory方法將會被調用——所以我們需要在該方法中清除一些工作(依附該Service生命周期的,如:停止在Service中創建并運行的線程)。

特別注意:
1.在使用startService方法啟動服務后,一定要調用stopService方法來停止該服務(同上,可以在Activity的onDestory中來停止服務);
2.在某處調用bindService綁定Service的時候,要在對應的某處調用unbindService來解除綁定(如在Activity中綁定了Service,可以在onDestory中來解除綁定——雖然綁定的Service會在Activity結束時自動解除、停止);
3.如果同時使用startServicebindService方法啟動Service,需要終止該Service時,要調用stopServiceunbindService方法(unbindService依附于啟動它的Context,startServicec并不依附于啟動它的Context。如果先調用unbindService,這時服務并不會被終止,當調用stopService后,服務才會被終止;如果先調用stopService,服務也不會被終止,當調用unbindService或者之前調用bindService的Context不存在了(如Activity被finish掉了)服務才會自動停止);
4.當手機屏幕發生旋轉時,如果Activity設置的是自動旋轉的話,在旋轉的過程中,Activity會重新創建,那么之前通過bindService建立的連接便會斷開(之前的Context不存在了),服務也會被自動停止。


4.Service的使用

在新建一個Service后,記得在AndroidManifest.xml中注冊Service,在application內添加需要注冊的Service信息:

<service android:name=".service.PlayerService"
    android:label="PlayerService"
    android:exported="true" />

Service示例如下:

public class PlayerService extends Service{

    @Override
    public void onCreate() {
        super.onCreate(); 
    } 

    /** 
    * onBind 是 Service 的虛方法,因此我們不得不實現它。
    * 返回 null,表示客服端不能建立到此服務的連接。
    */ 
    @Override
    public IBinder onBind(Intent intent) { 
        return new MyBinder();
    }

    // 已取代onStart方法--onStart方法是在Android2.0之前的平臺使用的.
    // 在2.0及其之后,則需重寫onStartCommand方法,同時,舊的onStart方法則不會再被直接調用
    // (外部調用onStartCommand,而onStartCommand里會再調用 onStart。在2.0之后,
    // 推薦覆蓋onStartCommand方法,而為了向前兼容,在onStartCommand依然會調用onStart方法。
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId); 
    } 

    @Override
    public boolean onUnbind(Intent intent) { 
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy(); 
    } 
    // IBinder是遠程對象的基本接口,是為高性能而設計的輕量級遠程調用機制的核心部分。但它不僅用于遠程
    // 調用,也用于進程內調用。這個接口定義了與遠程對象交互的協議。
    // 不要直接實現這個接口,而應該從Binder派生。
    // Binder類已實現了IBinder接口
    class MyBinder extends Binder { 
        /** 
        * 獲取Service的方法
        * @return 返回PlayerService
        */ 
        public  PlayerService getService(){
            return PlayerService.this;
        }
    }
}
  • startService啟動服務
      通過startService()啟動的服務處于“啟動”狀態,一旦啟動,Service就在后臺運行,即使啟動它的應用組件已經被銷毀了。通常started狀態的Service執行單任務并且不返回任何結果給啟動者(如當下載或上傳一個文件,當這項操作完成時,Service應該停止它本身),示例如下:
public class PlayerActivity extends Activity{
  private Intent intent;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    intent = new Intent(this, PlayerService.class);
    startService(intent);// 啟動服務
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    stopService(intent);// 在退出Activity時停止該服務
  }
}
  • bindService綁定服務
      一個綁定的Service提供一個允許組件與Service交互的接口,可以發送請求、獲取返回結果,還可以通過跨進程通信來交互(IPC)。綁定的Service只有當應用組件綁定后才能運行,多個組件可以綁定一個Service,當調用unbindService()方法時,這個Service就會被銷毀了。
  • Local(本地服務)的綁定
public class PlayerActivity extends Activity{
   @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Intent intent = new Intent(this, PlayerService.class);
    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);//綁定目標Service
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    unbindService(serviceConnection);// 解除綁定,斷開連接
  }
  // 在Activity中,我們通過ServiceConnection接口來取得建立連接與連接意外丟失的回調
  ServiceConnection serviceConnection = new ServiceConnection() {            @Override
    public void onServiceConnected(ComponentName name, IBinder service){
      // 建立連接
      // 獲取服務的操作對象
               PlayerService.MyBinder binder = (PlayerService.MyBinder)service;
               binder.getService();// 獲取到的Service即PlayerService
    } 
    @Override
    public void onServiceDisconnected(ComponentName name) {
      // 連接斷開
    }
  };
}


Service的onBind如果返回null,則調用bindService會啟動Service,但不會連接上Service因此ServiceConnection.onServiceConnected不會被調用,但仍然需要使用unbindService方法來斷開連接,這樣Service才會停止。


  Service與Activity一樣都存在與當前進程的主線程中,所以,一些阻塞UI的操作(如在Service進行網絡請求等)不能放在Service里進行。如果在Service里進行一些耗CPU和耗時操作,可能會引發ANR警告,這時應用會彈出是強制關閉還是等待的對話框。所以,對Service的理解就是和Activity平級的,只不過是看不見的,在后臺運行的一個組件,這也是為什么和Activity同被說為Android的基本組件。


5. AndroidManifest.xml中Service元素常見屬性

  • andorid:name

服務類名。可以是完整的包名+類名。也可使用.代替包名。

  • adroid:exported

其他應用能否訪問該服務,如果不能,則只有本應用或有相同用戶ID的應用能訪問。默認為false。

  • android:enabled

標識服務是否可以被系統實例化。true--系統默認啟動,false--不啟動。(默認值為true)

  • android:label

顯示給用戶的服務名稱。如果沒有進行服務名稱的設置,默認顯示服務的類名。

  • android:process

服務所運行的進程名。默認是在當前進程下運行,與包名一致。如果進行了設置,將會在包名后加上設置的集成名。
如果名稱設置為冒號 :開頭,一個對應用程序私有的新進程會在需要時和運行到這個進程時建立。如果名稱為小寫字母開頭,服務會在一個相同名字的全局進程運行,如果有權限這樣的話。這允許不同應用程序的組件可以分享一個進程,減少了資源的使用。

  • android:icon

服務的圖標。

  • android:permission

申請使用該服務的權限,如果沒有配置下相關權限,服務將不執行,使用startService()bindService()方法將都得不到執行。


6.Thread與Service的區別

  • Thread的優先級低于Service的優先級,在系統資源緊張的情況下,優先殺死前者;
  • 在Thread啟動后,如果退出當前Activity,則無法對已經啟動的Thread進行操作,而服務則可以——因此可以在服務中啟動Thread來解決該問題;
  • Service可以進行跨進程訪問,而Thread不行;
  • 在應用中,如果是長時間的在后臺運行,而且不需要交互的情況下,使用Service;
  • 同樣是在后臺運行,不需要交互的情況下,如果只是完成某個任務,之后就不需要運行,而且可能是多個任務,需需要長時間運行的情況下使用Thread;
  • 如果任務占用CPU時間多,資源大的情況下,要使用Thread。
  • 拓展閱讀
    Thread和Service應用場合的區別
    Android中Service與Thread的區別
    android-Service和Thread的區別

7.Service系列拓展閱讀

【Android】Service前臺服務的使用
【Android】遠程服務(Remote Service)的使用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容