本次我們沒有采取分享公眾號推文的方法,我想直接這樣看看效果。
本文首發於微信公眾號「nanchen」,你可以直接在公眾號搜尋「nanchen」或者掃描最下面的二維碼關注我。做不完的開源,寫不完的矯情,南塵一直與你同行。
這是 面試系列 的第三期。本期我們將來探討一下 Android 四大元件的重要組成部分:Service。
往期內容傳遞:
Android 面試(一):說說 Android 的四種啟動模式
Android 面試(二):如何理解 Activity 的生命週期
Android 面試(三):用廣播 BroadcastReceiver 更新 UI 介面真的好嗎?
Service 有多重要?
之前在「蘭柳學」的文章中看到這樣一個場景,挺有意思的,先給大家分享一下,讓我們一起來看看對 Service 的無知到底會有多麻煩。
場景:如果一個應用要從網路上下載一個檔案,並在 Activity 上展示進度條,這個 Activity 要求是可以轉屏的。那麼在轉屏時 Actvitiy 會重啟,如何保證下載的進度條能正確展示進度呢?
不會 Service 的人,一般會想出來這樣的方案。
-
- 在轉屏前將進度快取,轉屏後再讀出來。
-
- 使用
android:configChanges
設定,讓轉屏時 Activity 不銷燬和重建。
- 使用
針對第 1 種方案,其實細想漏洞百出。首先,轉屏的過程中,我們知道 Activity 的重建算是比較耗時的,可能會有幾百毫秒甚至更久,這時候下載執行緒仍然在工作,進度肯定和儲存時的進度不一致了,如何處理這個問題呢?
第 2 個方案,大家可以自己展開思考,實際的專案中可能會需要額外做一些事情來處理 ContentView 的橫豎佈局的問題。
如果採用 Service,你有什麼好主意呢?不妨在評論區給出。
一定聽過 Service 吧,它有幾種啟動方式?
Service 是一個專門在後臺執行長時間操作的類,它並不與使用者產生 UI 互動。它提供了兩種啟動方式。
- started
其它元件呼叫startService()
啟動一個 Service。一旦啟動,Service 將一直執行在後臺,即使啟動這個 Service 的元件已經被銷燬。通常一個被 start 的 Service 會在後臺執行單獨的操作,也並不需要給啟動它的元件返回結果。只有當 Service 自己呼叫stopSelf()
或者其它元件呼叫stopService()
才會終止。- bind
其它元件可以呼叫bindService()
來繫結一個 Service。這種方式會讓 Service 和啟動它的元件繫結在一起,當啟動它的元件銷燬的時候,Service 也會自動進行 unBind 操作。同一個 Service 可以被多個元件繫結,只有所有繫結它的元件都進行了 unBind 操作,這個 Service 才會被銷燬。
當然,Service 還可以同時在上述兩種方式下執行。這涉及到 Service 的兩個回撥方法的執行: onStartCommand()
(通過 start 方式啟動一個 Service 時的回撥方法。)、onBind()
(通過 bind 方式啟動一個 Service 回撥的方法)。
無論通過那種方式啟動 Service(start、bind、start & bind),任何元件(甚至其他應用的元件)都可以使用 Service。並通過 Intent 傳遞引數。當然,你也可以將 Service 在 AndroidMenifest.xml
檔案中配置成私有的,不允許其他應用訪問。
將
android:exported
屬性設為 false,表示不允許其他應用程式啟動本應用的元件,即便是顯式 Intent 也不行(even when using an explicit intent)。這可以防止其他應用程式啟動您的 Service 元件。Service 的生命週期
一談到元件,我們總是喜歡去研究它的生命週期,而這時候用圖來展示肯定是最好的了。
這兩條路徑並不是毫不相干的。當呼叫
startService()
去 start 一個 Service 後,你仍然可以 bind 這個 Service。比如:當播放音樂的時候,需要呼叫startService()
啟動指定的音樂,當需要獲取該音樂的播放進度的時候,又需要呼叫bindService()
,在這種情況下,除非 Service 被 unbind,此前呼叫stopService()
和stopSelf()
都不能停止該 Service。
這些生命週期方法在使用的時候並不需要呼叫各自的父類方法。
兩條生命週期路徑都可以包含兩個巢狀的生命週期:
-
完整生命週期(entire lifetime):從
onCreate()
被呼叫,到onDestroy()
返回。和 Activity 類似,一般在onCreate()
方法中做一些初始化的工作,在onDestroy()
中做一些資源釋放的工作。如,若 Service 在後臺播放一個音樂,就需要在onCreate()
方法中開啟一個執行緒啟動音樂,並在onDestroy()
中結束執行緒。 -
活動生命週期(activity lifetime):從
onStartCommand()
或onBind()
回撥開始,由相應的startService()
或bindService()
呼叫。start 方式的活動生命週期結束就意味著完整證明週期的結束,而 bind 方式,當onUnbind()
返回後,Service 的活動生命週期結束。
值得注意的是,無論是
startService()
還是bindService()
啟動 Service,onCreate()
和onDestroy()
均會被回撥。
Service 的 onCreate() 可以執行耗時操作嗎?
Service 執行在主執行緒中,它並不是一個新的執行緒,也不是新的程式,所以並不能執行耗時操作。
那如果要在 Service 中執行耗時操作,怎麼做?
我想基本所有人都能想到使用 Thread,事實上我們也經常這麼做。需要在主執行緒執行耗時操作,無非就是開一個執行緒,然後一陣混沌操作。當然,你還可以使用 AysncTask
或 HandlerThread
來替代 Thread 建立執行緒。
當然沒有問題,那還有其它更有意思的方式嗎?
有,當然有,IntentService 就是一個不錯的選擇。
紛繁複雜的 IntentService
IntentService
繼承於Service
,若 Service 不需要同時處理多個請求,那麼使用IntentService
將是最好選擇。你只需要重寫onHandleIntent()
方法,該方法接收一個回撥的 Intent 引數,你可以在方法內進行耗時操作,因為它預設開啟了一個子執行緒,操作執行完成後也無需手動呼叫stopSelf()
方法,onHandleIntent()
將會自動呼叫該方法。
使用 IntentService 的要點如下:
- 預設在子執行緒中處理回傳到
onStartCommand()
方法中的 Intent; - 在重寫的
onHandleIntent()
方法中處理按時間排序的 Intent 佇列,所以不用擔心多執行緒(multi-threading)帶來的問題。 - 當所有請求處理完成後,自動停止 Service,無需手動呼叫
stopSelf()
方法; - 預設實現了
onBind()
方法,並返回 null; - 預設實現了
onStartCommand()
方法,並將回傳的 Intent 以序列的形式傳送給onHandleIntent()
,您只需重寫該方法並處理 Intent 即可。
小結
當我們知道了 Service 的用途,心中有一個 Service 相關的概念時,針對實際的場景還是要做具體的分析再決定是否使用 Service。因為 Service 仍然是在主執行緒中呼叫,還是要開執行緒才能處理長時間的工作,Service 和 UI 的互動也讓這個方式變得不那麼簡便。如果你只需要在當前介面去做一些耗時操作,介面退出或改變時,工作也要停止,那麼這時直接使用 Thread(或者 AsyncTask, ThreadHandler)會更合適你。
做不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公眾號搜尋「nanchen」關注我的微信公眾號,目前多運營 Android ,儘自己所能為你提升。如果你喜歡,為我點贊分享吧~