1. 程式人生 > 實用技巧 >Android—Service解析

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子類