今天我們來探討一下 Android 四大組件的重要組成部分:Service。
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)會更合適你。
最後對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!
這裏附上上述的技術體系圖相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裏以圖片的形式給大家展示一部分。
相信它會給大家帶來很多收穫:
上述【高清技術腦圖】以及【配套的架構技術PDF】可以 加我wx:X1524478394 免費獲取!
當程序員容易,當一個優秀的程序員是需要不斷學習的,從初級程序員到高級程序員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。