Android Service === ## 簡述 * `Service`可以在背景不斷的工作,直到停止或是系統無法提供資源為止。 * `Service` 需要透過某`Activity` 或者其他`Context` 物件來啟動。 * `Service`不需要和 user 互動,所以沒有操作介面。 * 生命週期與`Activity`是各自獨立的,`Activity`就算關閉,`Service`仍然可以繼續執行。 * 類似 `BroadcastReceiver`,需要定義一個繼承 `Service` 的類別,並覆寫其中的生命週期函數,最後在`AndroidManifest.xml`中宣告才能使用 * `Service`可以同時支援 **Started** 與 **Bind** 兩種模式。在這種情況下,`Service` 需要等到兩種模式都被關閉才會觸發`onDestroy()`事件。 * `Service` 只有第一次被啟動時,會執行`onCreate()`,若重複啟動則不會執行`onCreate()`。 * `Service`的運作優先權相當的高,一般來說除非系統資源耗盡,否則 Android 不會主動關閉一個已被啟動的`Service`。一旦系統有足夠的資源,被 Android 關閉的`Service`也會被重新啟動。 * 兩者都需要在`AndroidManifest.xml`宣告 ```xml= <service android:name=".MyService" android:enabled="true" /> <!-- .MyService: 實作繼承Service的類別名稱 --> ``` ### Service 生命週期 根據執行方式的不同,啟動`Service`分為兩種,注意只有<u>黃色區塊</u>不一樣: 圖左- **Started** 模式;圖右- **Bind** 模式,並**根據不同模式複寫白色框框裡的函式**。 ![](https://i.imgur.com/44S6olC.png) ### Started 模式 (圖左) 啟動此模式的 `Service`,即便退出 `Activity` 也不會影響 `Service` 的運行,且`Activity`無法調用 `Service` 的方法。 * **啟動** `Service` 的方式 * Client side (`Activity`):`Context.startService(intent)` * **關閉** `Service` 的方式 * Client side (`Activity`):`Context.stopService()` * Service inside:`stopSelf()` * `onStartCommand()` 最後需要回傳一個常數,這些常數定義在 `Service` 類別中。該回傳值是用在如果這個 `Service` 被 Android 作業系統終止後的行為: * **`Service.START_STICKY`** `Service` 如果被中止的話會自動重啟。用在`onStartCommand()`方法中不需要依賴`Intent`傳入的資料就可以執行的時候(重新啟動時重新傳入的`Intent`會是`null`)。這也是預設使用`super.onStartCommnad()`的回傳值。 * **`Service.START_NOT_STICKY`** `Servcie` 如果被中止的話不重新啟動,用在`onStartCommand()`方法中所執行的工作需要依賴`Intent`物件內帶進來的參數。 * **`Service.START_REDELIVER_INTENT`** 和 **`START_STICKY`** 差不多,但 Android 會在中止 `Service` 之前將`Intent`保留下來,等待重新啟動時再將原本的`Intent`物件交還給`onStartCommand()`事件。 ### Bind 模式 (圖右) 啟動此模式的 `Service`,會伴隨著與調用者(Client)一起存活或是退出,當`Activity`退出`Service`的運行也會一起終止。 * **啟動** `Service` 的方式 * Client side (`Activity`):`Context.bindService(intent, mServiceConnection, int flags)`。其中第二個參數為`ServiceConnection` 物件,當`bindService()`綁定成功後,會呼叫此物件內的 `onServiceConnected` 函式,此函式會接收到由 `Service` 內的 `onBind()` 所丟出來的 `IBinder` 物件來直接操作 `Service` 內各個 `public` 的 method。 (`ServiceConnection`物件實作方式參考下方連結) * **關閉** `Service` 的方式 * `Service` inside: `unbindService(mServiceConnection);` ## 解析 startService 透過以下範例了解`startService`運作流程,若需詳細程式碼請[參考](https://blog.csdn.net/iispring/article/details/47689819) ### 測試角色 #### Service 端 * **`TestService`** #### Client 端 * **`MainActive`** ```java ... public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("DemoLog", "Thread ID: " + Thread.currentThread().getId()); Log.i("DemoLog", "before test startService"); //连续启动Service Intent intent1 = new Intent(this, TestService.class); startService(intent1); Intent intent2 = new Intent(this, TestService.class); startService(intent2); Intent intent3 = new Intent(this, TestService.class); startService(intent3); //停止Service Intent intent4 = new Intent(this, TestService.class); stopService(intent4); //再次启动Service Intent intent5 = new Intent(this, TestService.class); startService(intent5); Log.i("DemoLog", "after test startService"); } } ``` ### 結果 ![这里写图片描述](https://img-blog.csdn.net/20150815230029404) ### 生命週期流程 ![这里写图片描述](https://img-blog.csdn.net/20150815230054513) ### 再次分析`onStartCommand()` 的回傳值 * **`START_NOT_STICKY`** * 表示當 `Service` 運行的 process 被 Android 系統強制殺掉之後,不會重新創建該 `Service`,當然如果在其被殺掉之後一段時間又調用了 `startService`,那麼該 `Service` 又將被實例化。 * Ex:某個 `Service` 需要定時從 server 獲取最新數據:通過一個定時器每隔指定的N分鐘讓定時器啟動 `Service` 去獲取 server 的最新數據。當執行到 `Service` 的`onStartCommand` 時,在該方法內再規劃一個N分鐘後的定時器用於再次啟動 `Service` 並開闢一個新的執行緒去執行網絡操作。假設 `Service` 在從 server 獲取最新數據的過程中被 Android 系統強制殺掉,`Service` 不會再重新創建,這也沒關係,因為再過N分鐘定時器就會再次啟動該 `Service` 並重新獲取數據。 * **`START_STICKY`** * 表示 `Service` 運行的 process 被 Android 系統強制殺掉之後,Android 系統會將該 `Service` 依然設置為 started 狀態(即運行狀態),但是不再保存 `onStartCommand` 方法傳入的 `intent` 對象,然後 Android 系統會嘗試再次重新創建該 `Service`,並執行 `onStartCommand` 回調方法,但是 `onStartCommand` 回調方法的 `Intent` 參數為 `null`,也就是 `onStartCommand` 方法雖然會執行但是獲取不到 `intent` 信息。 * Ex:如果 `Service` 可以在任意時刻運行或結束都沒什麼問題,而且不需要 `intent` 信息,那麼就可以在 `onStartCommand` 方法中返回 `START_STICKY`,比如一個用來播放背景音樂功能的 `Service` 就適合返回該值 * **`START_REDELIVER_INTENT`** * 表示 `Service` 運行的 process 被 Android 系統強制殺掉之後,與返回 `START_STICKY` 的情況類似,Android 系統會將再次重新創建該 `Service`,並執行 `onStartCommand` 回調方法,但是不同的是,Android 系統會再次將 `Service` 在被殺掉之前最後一次傳入 `onStartCommand` 方法中的 `Intent` 再次保留下來,並再次傳入到重新創建後的 `Service` 的 `onStartCommand` 方法中,這樣我們就能讀取到 `intent` 參數。 * Ex:如果 `Service` 需要依賴具體的 `Intent` 才能運行(需要從 `Intent` 中讀取相關數據信息等),並且在強制銷毀後有必要重新創建運行,那麼這樣的 `Service` 就適合。 ## 解析 bindService ### Client 與 Service 在同一個 App 中 透過以下幾個範例了解 `bindService` 的運作流程,若需詳細程式碼請[參考](https://blog.csdn.net/iispring/article/details/48169339) ### 測試角色 #### Service 端 * `TestService` #### Client 端 * 兩個 `Activity`,分別為 **`Active A`** 與 **`Active B`** 1. **`Activity A`** ![](https://i.imgur.com/vTV5qAl.png) 2. **`Activity B`** ![](https://i.imgur.com/u2zPJPt.png) ### 測試流程 #### 測試一 ##### 步驟 * **`Activity A`** * click **bindService** * click **unbindService** ##### 結果 ![](https://i.imgur.com/OFUEeHC.png) #### 測試二 ##### 步驟 * **`Activity A`** * click **bindService** * click **Finish** ##### 結果 ![](https://i.imgur.com/ishWv4P.png) #### 測試三 ##### 步驟 * **`Activity A`** * click **bindService** * click **start Activity B** * **`Activity B`** * click **bindService** * click **unbindService** * click **Finish** * **`Activity A`** * click **unbindService** ##### 結果 ![](https://i.imgur.com/3m85SpM.png) #### 生命週期流程 ![](https://i.imgur.com/JqqWneB.png) ### Client 與 Service 在不同一個 App 中 [參考](https://blog.csdn.net/iispring/article/details/48329925) ## 淺談 Service v.s. thread * 看似都是在背景運作,但實際上`Service` 與 `Thread` 沒有任何關係。 * `Service` 是運行於**主執行緒**(Main Thread)上,故若在此執行太耗時的任務,依然會出現 ANR。 * `Thread` 是用於開啟一個**子執行緒**(Child thread),去執行一些耗時作不會阻塞主執行緒的運行。 * `Activity` 很難對 `Thread` 進行控制: * 當 `Activity` 被銷毀後卻沒有主動停止 `thread`,就會造成没有辦法可以再重新獲取到之前建立的 `thread` 的實例。 * 在某 `Activity` 中建立的 `thread`,另一個 `Activity` 無法對其進行操作。 * `Service` 方便取得控制權: * 當 `Activity` 被銷毀,其他的 `Activity` 都可以與 `Service` 重新進行關聯,就又能夠獲取到原有 `Service` 中 Binder 的實例。 * 使用 `Service` 來處理後台任務,`Activity` 可以放心地 finish,完全不需要擔心無法對後台任務進行控制的情況。 * 如果在`Service`內部做一些很耗時的任務,可以在`Service`內部建立一個執行緒來處理。因為`Service`是跑在主執行緒中,會影響到 UI 操作或是阻擋主執行緒的運行 * ex:在後台播放多媒體檔案會需要檢查SD、在後台紀錄 gps 資訊的變換等等 ## Ref. [Service背景執行程式](http://www.aaronlife.com/v1/teaching/android_service.html) [《Android》『Service』- 背景執行服務的基本用法](https://xnfood.com.tw/android-service/) [Android中startService的使用及Service生命周期](https://blog.csdn.net/iispring/article/details/47689819) [Android中bindService的使用及Service生命周期](https://blog.csdn.net/iispring/article/details/48169339) [Android Service和Thread区别](https://blog.csdn.net/xiaoxiangyuhai/article/details/77918931) [Android service 启动篇之 startForegroundService](https://blog.csdn.net/shift_wwx/article/details/82496447?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165888751016782388014406%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165888751016782388014406&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-82496447-null-null.142^v35^new_blog_fixed_pos,185^v2^control&utm_term=startservice%20startForegroundService&spm=1018.2226.3001.4187) ###### tags: `Service`