Android—Service解析

Android—Service

一、什麼是Service

Service(服務)是一個一種可以在後臺執行長時間運行操作而沒有用戶界面的應用組件。服務可由其他應用組件啓動(如Activity),服務一旦被啓動將在後臺一直運行,即使啓動服務的組件(Activity)已銷燬也不受影響。 此外,組件可以綁定到服務,以與之進行交互,甚至是執行進程間通信 (IPC) 。
每個Service類都必須在其包的 AndroidManifest.xml 中具有相應的服務聲明。 可以使用 Context.startService ()和 Context.bindService ()啓動服務。

二、Service分類

按是否是前臺服務分類

  • 前臺服務
    startForeground(int id, Notification notification)
    該方法的作用是把當前服務設置爲前臺服務,其中id參數代表唯一標識通知的整型數,需要注意的是提供給 startForeground() 的整型 ID 不得爲 0,而notification是一個狀態欄的通知。
  • 後臺服務
    後臺服務就是未顯示在通知欄的服務

按是否是遠程服務分類

  • 遠端服務
    在C/S結構中位於服務端的服務,遠端服務與客戶端運行在不同的進程
  • 本地服務
    即服務和客戶端運行在同一進程中

三、生命週期

  • oncreate
    Service首次創建時會回調該方法
  • onstartcommon
    每次通過startService啓動service時會回調
  • onbind
    每個組件首次通過bindservice綁定service時會回調
  • ondestory
    service銷燬時會回調

以上就是Service常用的幾個生命週期函數,因爲啓動service的方式有兩種,在這兩種方式中生命週期會有不同。
startService方式流程:oncreate ——>onstartcommon——>ondestory

  • oncreate僅在service創建時纔會調用之後再次調用startService啓動並不會再次調用。
  • onStartCommend每次調用startService啓動都會調用,該函數有兩個參數intent、flags,一個int返回值
    intent:啓動時,啓動組件傳遞過來的Intent
    flags:表示啓動請求時是否有額外數據,可選值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY。
    START_FLAG_REDELIVERY
    這個值代表了onStartCommand方法的返回值爲
    START_REDELIVER_INTENT,而且在上一次服務被殺死前會去調用stopSelf方法停止服務。其中START_REDELIVER_INTENT意味着當Service因內存不足而被系統kill後,則會重建服務,並通過傳遞給服務的最後一個 Intent 調用 onStartCommand(),此時Intent是有值的。
    START_FLAG_RETRY
    該flag代表當onStartCommand調用後一直沒有返回值時,會嘗試重新去調用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。這個值適用於主動執行應該立即恢復的作業(例如下載文件)的服務
  • ondestory
    我們應該始終記得在Service的onDestroy()方法裏去清理掉那些不再使用的資源,防止在Service被銷燬後還會有一些不再使用的對象仍佔用着內存

bindService方式流程:oncreate ——>onbind——>ondestory

  • oncreate
    同上
  • onbind
    當另一個組件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統將調用此方法。在此方法的實現中,必須返回 一個IBinder 接口的實現類,供客戶端用來與服務進行通信。無論是啓動狀態還是綁定狀態,此方法必須重寫,但在啓動狀態的情況下直接返回 null。

提供一個 IBinder接口的實現類,該類用以提供客戶端用來與服務進行交互的編程接口,該接口可以通過三種方法定義接口:

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

  2. 使用 Messenger
    Messenger可以翻譯爲信使,通過它可以在不同的進程中共傳遞Message對象(Handler中的Messager,因此 Handler 是 Messenger 的基礎),在Message中可以存放我們需要傳遞的數據,然後在進程間傳遞。如果需要讓接口跨不同的進程工作,則可使用 Messenger 爲服務創建接口,客戶端就可利用 Message 對象向服務發送命令。同時客戶端也可定義自有 Messenger,以便服務回傳消息。這是執行進程間通信 (IPC) 的最簡單方法,因爲 Messenger 會在單一線程中創建包含所有請求的隊列,也就是說Messenger是以串行的方式處理客戶端發來的消息,這樣我們就不必對服務進行線程安全設計了。

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

  • ondestory
    同上

四、使用

service使用步驟

  • 在AndroidManifest文件中註冊
    註冊時唯一不可缺省的屬性是name,它唯一標識了一個Service。
    Android:exported屬性設爲false,表示不允許其他應用程序啓動本應用的組件。android:pression屬性可以指定啓動該Service所需要的權限

  • 繼承Service類重寫相關方法

啓動方式

  • startService
    一旦啓動,Service將一直運行在後臺(run in the background indefinitely)即便啓動Service的組件已被destroy。通常,一個被start的Service會在後臺執行單獨的操作,也並不給啓動它的組件返回結果。比如說,一個start的Service執行在後臺下載或上傳一個文件的操作,完成之後,Service應自己停止。

  • bindService
    通過綁定方式啓動的Service是一個client-server結構,該Service可以與綁定它的組件進行交互。一個bound service僅在有組件與其綁定時纔會運行,多個組件可與一個service綁定,service不再與任何組件綁定時,該service會被destroy。多次調用bind方法只有第一次纔會觸發onbind方法

startService和bindService區別
1、調用 startService() 啓動服務時,服務即處於“啓動”狀態。一旦啓動,服務即可在後臺無限期運行,即使啓動服務的組件已被銷燬也不受影響,除非手動調用才能停止服務, 已啓動的服務通常是執行單一操作,而且不會將結果返回給調用方、

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

  • startForegroundService(8.0新增)
    創建前臺服務的方式通常是先創建一個後臺服務,然後將該服務推到前臺。Android O及Android P,系統不允許後臺應用創建後臺服務,Android O 引入了一種全新的方法,即ContextCompat.startForegroundService() ,以在前臺啓動新服務。
    一般使用流程:

    1. 調用ContextCompat.startForegroundService() 可以創建一個前臺服務,相當於創建一個後臺服務並將它推到前臺;
    2. 創建一個用戶可見的 Notification;
    3. 必須在5秒內調用該服務的startForeground(int id, Notification notification)方法,否則將停止服務並拋出android.app.RemoteServiceException:Context.startForegroundService() did not then call Service.startForeground()異常。

停止方式

  • stopself
  • stopService
  • unbindService

五、系統提供的Service實現類

IntentService

它本質是一種特殊的Service,繼承自Service並且本身就是一個抽象類
它可以用於在後臺執行耗時的異步任務,當任務完成後會自動停止
它擁有較高的優先級,不易被系統殺死(繼承自Service的緣故),因此比較適合執行一些高優先級的異步任務
它內部通過HandlerThread和Handler實現異步操作
創建IntentService時,只需實現onHandleIntent和構造方法,onHandleIntent爲異步方法,可以執行耗時操作

通過for循環多次去啓動IntentService,然後去下載圖片,注意即使我們多次啓動IntentService,但IntentService的實例只有一個,這跟傳統的Service是一樣的,最終IntentService會去調用onHandleIntent執行異步任務。這裏可能我們還會擔心for循環去啓動任務,而實例又只有一個,那麼任務會不會被覆蓋掉呢?其實是不會的,因爲IntentService真正執行異步任務的是HandlerThread+Handler,每次啓動都會把下載圖片的任務添加到依附的消息隊列中,最後由HandlerThread+Handler去執行

最後附上源碼解析鏈接:IntentService源碼分析

JobService

JobService是Android L添加的組件,適用於需要特定條件下才執行後臺任務的場景。 由系統統一管理和調度,在特定場景下使用JobService更加靈活和省心。
下面貼篇鏈接 有興趣可以看下。
使用介紹
JobService和Service - 簡書
JobService的使用介紹_allisonchen的專欄-CSDN博客
Android 9.0 JobScheduler(一) JobScheduler的使用_FightFightFight的博客-CSDN博客
android: job service_u011279649的專欄-CSDN博客

JobIntentService

源碼分析已完成

LifecycleService

待補充

六、相關問題

service保活

分兩種情況:

  • 因內存資源不足而殺死Service
    可將onStartCommand() 方法的返回值設爲 START_STICKY或START_REDELIVER_INTENT ,該值表示服務在內存資源緊張時被殺死後,在內存資源足夠時再恢復。也可將Service設置爲前臺服務,這樣就有比較高的優先級,在內存資源緊張時也不會被殺掉
  • 通過 settings -> Apps -> Running -> Stop 方式殺死Service
    這種情況是用戶手動干預的,不過幸運的是這個過程會執行Service的生命週期,也就是onDestory方法會被調用,這時便可以在 onDestory() 中發送廣播重新啓動。這樣殺死服務後會立即啓動。這種方案是行得通的,但爲程序更健全,我們可開啓兩個服務,相互監聽,相互啓動。服務A監聽B的廣播來啓動B,服務B監聽A的廣播來啓動A

5.0之後不可隱式啓動service問題

分析源碼可知啓動service的intent的component和package都爲空並且版本大於LOLLIPOP(5.0)的時候,直接拋出異常
解決辦法:
1、設置Action和packageName

 final Intent serviceIntent=new Intent(); 
 serviceIntent.setAction("com.android.For 
   egroundService");
  serviceIntent.setPackage(getPackageNa 
  me());//設置應用的包名
    startService(serviceIntent);

2、顯式啓動

AccessibilityService

AccessibilityService設計初衷在於幫助殘障用戶使用android設備和應用,在後臺運行,可以監聽用戶界面的一些狀態轉換,例如頁面切換、焦點改變、通知、Toast等,並在觸發AccessibilityEvents時由系統接收回調。後來被開發者另闢蹊徑,用於一些插件開發,比如微信紅包助手,還有一些需要監聽第三方應用的插件。
有興趣的可以自行查閱相關內容。
AccessibilityService使用入門
官方文檔

bindService獲取代理是同步還是異步

Android面試題:bindService獲取代理是同步還是異步 - 簡書

service與Thread區別

本質來講Service和Thread是兩個完全不同的東西,service用來在後臺執行任務但service一直運行在主線程所以它並不能做耗時操作。此時就需要Thread把耗時操作放到Thread中這樣就不會影響主線程

七、Service源碼解析

startService流程源碼分析

startService流程源碼分析

bindService流程源碼分析

bindService流程源碼分析

小結:service是Android四大組件之一,組件是用來在後臺執行任務,注意此處的後臺並不單純指應用切換到後臺,正確的理解是沒有用戶界面。除此之外service還涉及一個後臺概念,就是8.0之後禁止在後臺啓動service,此處後臺的正確理解是應用退出前臺界面且這種狀態持續時間達到系統設置的閾值(目前系統閾值是退出前臺界面一分鐘後即算進入後臺狀態,此時無法啓動service)。service與thread的本質上是不同的,線程是系統分配資源的最小單元,service是Android系統提供的一個組件它運行在主線程,也就是說它是線程中運行的一個組件,因爲在主線程所以不能執行耗時操作,如果必須要執行耗時操作可以在內部啓動一個子線程去執行,也可以直接使用intentservice這是Android提供的執行異步請求的service。

service要注意的知識點有:生命週期、分類、start/bind源碼分析、8.0之後後臺啓動service問題、前臺service、service保活、系統提供的service子類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章