之前幾篇文章簡單梳理了在Android系統的四大元件之一,最主要的介面Activity中,使應用程式與使用者進行互動響應的相關知識點,那對於應用程式中不需要與使用者互動的邏輯,又要用到哪些內容呢?本文開始將介紹應用程式無需介面互動的內部互動相關知識點,首先從另外一個四大元件之一的服務Service開始。

清單檔案一文的元件宣告中,已經知道服務Service與介面Activity一樣,都要在清單檔案中註冊宣告。同樣的,每個註冊宣告的服務Service類向上追溯都必須繼承自android.app.Service父類,因此服務Service也有自己的生命週期。

生命週期

Service主要負責應用程式中不需要介面展示或互動的長時間操作,像是播放音樂,網路請求等都是可以在服務Service中完成的。與介面Activity的生命週期一樣,在服務Service的宣告週期內不允許執行耗時操作,所以,雖然服務Service中可以進行長時間操作,但是仍然需要將這部分耗時操作放入非Android系統主執行緒中。

(呼叫構造方法)物件例項化

首次啟動新的服務Service時,系統會申請記憶體空間儲存該服務Service的例項化物件。服務Service兩種啟動方式,其一與介面Activity一樣需要藉助意圖Intent物件,呼叫上下文環境Context物件的startService(Intent service)啟動。

其二是程序間通訊時所用的方式,不僅要藉助意圖Intent物件,還要使用實現android.content.ServiceConnection介面的物件,進而呼叫上下文環境Context物件的bindService(Intent service, ServiceConnection conn, int flags)方法,而引數三 flags 標記了啟動該服務Service時所繫結的模式,通常使用Context.BIND_AUTO_CREATE標記會自動建立當前繫結的服務Service。除此之外,還可以按位或的形式追加其他標記,例如追加|Context.BIND_NOT_FOREGROUND標記當前繫結服務Service為較低優先順序的後臺服務,在手機息屏或高能耗時,系統會優先殺死低優先順序的服務;而追加|Context.BIND_IMPORTANT標記可以提升繫結服務Service的優先順序為前臺服務,在手機息屏或高能耗時,只要當前應用程式存活,當前服務Service就不會被殺死;如果追加|Context.BIND_ABOVE_CLIENT標記當前繫結服務Service的優先順序要高於當前應用程式,當手機息屏或高能耗時,系統可能會先殺死應用程式,但是當前服務Service仍然存活。

Android系統為不同程序間的通訊提供了一套AIDL語言規範,詳情將在以後的文章中介紹,這裡只需瞭解在實現ServiceConnection介面中,要重寫兩個方法,分別是在程序通訊介面與服務Service連線成功後回撥的onServiceConnected(ComponentName name, IBinder service)方法,和連線斷開之後回撥的onServiceDisconnected(ComponentName name)方法。

上述兩種啟動方式,都會觸發系統在當前應用程式的清單檔案中查詢對應註冊過的服務Service,在找到之後,就會建立其例項化物件,如果在清單檔案中沒有找到對應的服務Service,將不會有任何錯誤或異常。

與啟動未註冊的服務Service不同的是,當呼叫startActivity()系列方法啟動一個沒有在清單檔案中註冊過的介面Activity時,系統通常會丟擲android.content.ActivityNotFoundException異常。

(呼叫attachBaseContext(Context base))載入執行環境

由於Service也是android.content.ContentWrapper的子類,所以系統在建立服務Service的例項化物件後,也會優先對其載入上下文執行環境,將引數 base 作為當前應用程式的Context物件與該服務Service繫結。在該方法被呼叫之後的任意位置,就可以通過呼叫getBaseContext()等系列方法獲取並使用當前服務Service所在的上下文環境了。

(呼叫onCreate())服務建立

在建立服務Service並載入執行環境之後,系統會優先呼叫該方法,表示當前服務已經完成建立。可以重寫該方法執行一些服務內部使用的資源初始化操作。在執行完該方法之後,就標誌著當前服務Service建立成功了,之後會一直處於執行狀態,同時根據上述啟動方式的不同,呼叫不同的生命週期方法。

(呼叫onStartCommand(Intent intent, int flags, int startId))服務啟動

如果是通過上述啟動方式一啟動的服務Service,每呼叫一次startService(),系統都會呼叫一次該方法。

其中引數 intent 接收每次啟動服務所傳入的Intent意圖。

引數 flags 標記當前服務多次啟動狀態,一般預設是 0 ;當該服務Service被首次呼叫該方法且成功返回Service.START_REDELIVER_INTENT=3後,莫名被系統殺死,之後再次啟動該服務時,標記引數則是 Service.START_FLAG_REDELIVERY=1 ;如果該服務Service被首次呼叫該方法並未返回結果,系統將會再次嘗試呼叫該方法,標記引數則為 Service.START_FLAG_RETRY=2

引數 startId 作為系統唯一值,以此標記當前服務Service

最終返回指定的int型別,如果返回預設的Service.START_STICKY_COMPATIBILITY=0,當系統殺死該服務Service後,再次呼叫startService()將不會再被系統回撥該方法;另外如果返回Service.START_STICKY=1,在系統殺死該服務Service後,再次呼叫startService()將會被系統重新建立例項化並回調該方法。

(呼叫onBind(Intent intent))服務繫結

如果是通過上述啟動方式二啟動的服務Service,在首次呼叫bindService()後,系統會呼叫該方法,而之後如果多次呼叫bindService(),只有在當前服務Service已經執行完onUnbind()解綁的生命週期方法,並在解綁方法中返回 false 時,系統才會回撥該方法。引數 intent 接收繫結服務所傳入的Intent意圖,最終返回android.os.IBinder介面的實現類物件。返回結果可以在bindService(Intent service, ServiceConnection conn, int flags)方法的引數二conn.onServiceConnected(ComponentName name, IBinder service)方法中接收,也就是其中的引數二IBinder型別的 service

服務解綁(呼叫onUnbind(Intent intent))

如果是通過上述啟動方式二啟動的服務Service,可以在bindService()繫結服務Service位置相對應的位置,呼叫unbindService()解綁服務Service。之後系統會回撥該方法,同樣的藉助引數Intent意圖例項來指定要解綁的指令資訊。

服務重綁(呼叫onRebind(Intent intent))

如果是通過上述啟動方式二啟動的服務Service,如果當前服務Service已經執行完解綁生命週期,並在onUnbind()方法中返回 true 時,系統將會呼叫該方法以使用原有的IBinder物件重新綁定當前服務Service,因此該方法不需要返回值。其引數 intent 接收繫結服務所傳入的Intent意圖。最終返回值boolean型別,以標記當前服務再次被繫結時是否使用原有繫結過的IBinder物件。

服務銷燬(呼叫onDestroy())

啟動之後的服務Service會一直處於執行狀態,直到系統可能因能耗過過而殺死低優先順序的程序時,當前服務Service將會被動殺死,或者當前服務Service主動呼叫程式碼停止執行。與上述兩種啟動方式對應,服務Service也有兩種停止方式

其一對應於startService()啟動的服務,呼叫上下文環境Context物件的stopService(Intent service)方法停止執行,或者呼叫當前服務Service物件的stopSelf()方法也可以停止執行自身服務。

其二對應於bindService()繫結的服務,呼叫上下文環境Context物件的unbindService(ServiceConnection conn)方法解除繫結。由於繫結該服務的可以有多個ServiceCOnnection連線,所以必須每個bindService()繫結服務的位置都對應呼叫unbindService()方法主動解綁,以防止出現記憶體洩漏或資源佔用等誤操作。

在服務Service所有繫結已解綁或主動停止執行後,系統最終會呼叫該方法,之後將銷燬記憶體中建立的該服務Service例項化物件。因此可以重寫該方法,對應於onCreate()中申請的初始化資源在該方法中釋放掉。


服務Service的生命週期與介面Activity有些類似,這也保證了在其中可以執行無使用者互動的操作,那麼針對這種應用場景還需要怎麼構建子執行緒操作呢?而且服務Service的設計還可以進行程序間通訊。具體又是如何編寫程式碼搭建程序間的溝通橋樑呢?敬請期待後續文章。