23 個重難點突破,帶你吃透 Service 知識點「長達 1W+ 字」
- 2019 年 11 月 3 日
- 筆記
前言
- 學
Android
有一段時間了,想必不少人也和我一樣,平時經常東學西湊,感覺知識點有些凌亂難成體系。所以趁着這幾天忙裡偷閒,把學的東西歸納下,捋捋思路。
這篇文章主要針對
Service
相關的知識點,進行詳細的梳理,祝大家食用愉快!
文章目錄
方便大家學習,我在 GitHub 建立了 倉庫
-
倉庫內容與博客同步更新。由於我在
稀土掘金
簡書
CSDN
博客園
等站點,都有新內容發佈。所以大家可以直接關注該倉庫,以免錯過精彩內容!
# 第一篇:Service 是什麼
1.1 什麼是 Service
Service
(服務) 是一個一種可以在後台執行長時間運行操作而沒有用戶界面的應用組件。- 服務可由其他應用組件啟動(如
Activity
),服務一旦被啟動將在後台一直運行,即使啟動服務的組件(Activity
)已銷毀也不受影響。 - 此外,組件可以綁定到服務,以與之進行交互,甚至是執行進程間通信 (
IPC
)。
1.2 Service 通常總是稱之為 「後台服務」
- 其中 「後台」 一詞是相對於前台而言的,具體是指:其本身的運行並不依賴於用戶可視的
UI
界面 - 因此,從實際業務需求上來理解,
Service
的適用場景應該具備以下條件:
-
並不依賴於用戶可視的
UI
界面(當然,這一條其實也不是絕對的,如前台Service
就是與Notification
界面結合使用的) -
具有較長時間的運行特性
-
注意: 是運行在主線程當中的
1.3 服務進程
-
服務進程是通過
startService()
方法啟動的進程,但不屬於前台進程和可見進程。例如,在後台播放音樂或者在後台下載就是服務進程。 -
系統保持它們運行,除非沒有足夠內存來保證所有的前台進程和可視進程。
# 第二篇:生命周期
2.1 Service 的生命周期
- 我們先來看看
Service
的生命周期 的基本流程 - 一張聞名遐邇的圖
2.2 開啟 Service 的兩種方式
2.2.1 startService()
-
定義一個類繼承
Service
-
在
Manifest.xml
文件中配置該Service
-
使用
Context
的startService(intent)
方法開啟服務。 -
使用
Context
的stopService(intent)
方法關閉服務。 -
該啟動方式,
app
殺死、Activity
銷毀沒有任何影響,服務不會停止銷毀。
2.2.2 bindService()
-
創建
BindService
服務端,繼承Service
並在類中,創建一個實現IBinder
接口的實例對象,並提供公共方法給客戶端(Activity
)調用。 -
從
onBinder()
回調方法返回該Binder
實例。 -
在客戶端(
Activity
)中, 從onServiceConnection()
回調方法參數中接收Binder
,通過Binder
對象即可訪問Service
內部的數據。 -
在
manifests
中註冊BindService
, 在客戶端中調用bindService()
方法開啟綁定Service
, 調用unbindService()
方法註銷解綁Service
。 -
該啟動方式依賴於客戶端生命周期,當客戶端
Activity
銷毀時, 沒有調用unbindService()
方法 ,Service
也會停止銷毀。
2.3 Service 有哪些啟動方法,有什麼區別,怎樣停用 Service
-
在
Service
的生命周期中,被回調的方法比Activity
少一些,只有onCreate
,onStart
,onDestroy
,onBind
和onUnbind
。 -
通常有兩種方式啟動一個
Service
, 他們對Service
生命周期的影響是不一樣的。
2.3.1 通過 startService
Service
會經歷onCreate
到onStart
,然後處於運行狀態,stopService
的時候調用onDestroy
方法。
如果是調用者自己直接退出而沒有調用
stopService
的話,Service
會一直在後台運行。
2.3.2 通過 bindService
Service
會運行 onCreate
,然後是調用 onBind
, 這個時候調用者和 Service
綁定在一起。調用者退出了,Srevice
就會調用 onUnbind
-> onDestroyed
方法。
所謂綁定在一起就共存亡了。調用者也可以通過調用
unbindService
方法來停止服務,這時候Srevice
就會調用onUnbind
->onDestroyed
方法。
2.3.3 需要注意的是如果這幾個方法交織在一起的話,會出現什麼情況呢?
-
一個原則是
Service
的onCreate
的方法只會被調用一次,就是你無論多少次的startService
又bindService
,Service
只被創建一次。 -
如果先是
bind
了,那麼start
的時候就直接運行Service
的onStart
方法,如果先是start
,那麼bind
的時候就直接運行onBind
方法。 -
如果
service
運行期間調用了bindService
,這時候再調用stopService
的話,service
是不會調用onDestroy
方法的,service
就stop
不掉了,只能調用UnbindService
,service
就會被銷毀 -
如果一個
service
通過startService
被start
之後,多次調用startService
的話,service
會多次調
用onStart
方法。多次調用stopService
的話,service
只會調用一次onDestroyed
方法。 -
如果一個
service
通過bindService
被start
之後,多次調用bindService
的話,service
只會調用一次onBind
方法。多次調用unbindService
的話會拋出異常。
# 第三篇:Service 與 Thread
3.1 Service 和 Thread 的區別
3.1.1 首先第一點定義上
thread
是程序執行的最小單元,他是分配cpu
的基本單位安卓系統中,我們常說的主線程,UI
線程,也是線程的一種。當然,線程裏面還可以執行一些耗時的異步操作。- 而
service
大家記住,它是安卓中的一種特殊機制,service
是運行在主線程當中的,所以說它不能做耗時操作,它是由系統進程託管,其實service
也是一種輕量級的IPC
通信,因為activity
可以和service
綁定,可以和service
進行數據通信。 - 而且有一種情況,
activity
和service
是處於不同的進程當中,所以說它們之間的數據通信,要通過IPC
進程間通信的機制來進行操作。
3.1.2 第二點是在實際開發的過程當中
- 在安卓系統當中,線程一般指的是工作線程,就是後台線程,做一些耗時操作的線程,而主線程是一種特殊的線程,它只是負責處理一些
UI
線程的繪製,UI
線程裏面絕對不能做耗時操作,這裡是最基本最重要的一點。(這是Thread
在實際開發過程當中的應用) - 而
service
是安卓當中,四大組件之一,一般情況下也是運行在主線程當中,因此service
也是不可以做耗時操作的,否則系統會報 ANR 異常(ANR
全稱:Application Not Responding
),就是程序無法做出響應。 - 如果一定要在
service
裏面進行耗時操作,一定要記得開啟單獨的線程去做。
3.1.3 第三點是應用場景上
- 當你需要執行耗時的網絡,或者這種文件數據的查詢,以及其它阻塞
UI
線程的時候,都應該使用工作線程,也就是開啟一個子線程的方式。 - 這樣才能保證
UI
線程不被佔用,而影響用戶體驗。 - 而
service
來說,我們經常需要長時間在後台運行,而且不需要進行交互的情況下才會使用到服務,比如說,我們在後台播放音樂,開啟天氣預報的統計,還有一些數據的統計等等。
3.2 為什麼要用 Service 而不是 Thread
Thread
的運行是獨立於Activity
的,也就是當一個Activity
被finish
之後,如果沒有主動停止Thread
或者Thread
中的run
沒有執行完畢時那麼這個線程會一直執行下去。- 因此這裡會出現一個問題:當
Activity
被finish
之後,你不再持有該Thread
的引用。 - 另一方面,你沒有辦法在不同的
Activity
中對同一Thread
進行控制。
3.3 Service 裏面是否能執行耗時的操作
-
service 裏面不能執行耗時的操作(網絡請求,拷貝數據庫,大文件 )
-
Service
不是獨立的進程,也不是獨立的線程,它是依賴於應用程序的主線程的,也就是說,在更多時候不建議在Service
中編寫耗時的邏輯和操作(比如:網絡請求,拷貝數據庫,大文件),否則會引起ANR
。 -
如果想在服務中執行耗時的任務。有以下解決方案:
- 在
service
中開啟一個子線程
new Thread(){}.start();
- 可以使用
IntentService
異步管理服務( 有關IntentService
的內容在後文中給出 )
3.4 Service 是否在 main thread 中執行
- 默認情況, 如果沒有顯示的指
service
所運行的進程,Service
和activity
是運 行在當前app
所在進程的main thread
(UI
主線程)裏面。 Service
和Activity
在同一個線程,對於同一app
來說默認情況下是在同一個線程中的main Thread
(UI Thread
)- 特殊情況 ,可以在清單文件配置
service
執行所在的進程 ,讓service
在另 外的進程中執行Service
不死之身
3.4.1 在 onStartCommand
方法中將 flag
設置為 START_STICKY
;
<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" > </service>
return Service.START_STICKY;
3.4.2 在 xml 中設置了 android:priority
<!--設置服務的優先級為MAX_VALUE--> <service android:name=".MyService" android:priority="2147483647" > </service>
3.4.3 在 onStartCommand
方法中設置為前台進程
@Override public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = new Notification(R.mipmap.ic_launcher, "服務正在運行",System.currentTimeMillis()); Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent,0); RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification); remoteView.setImageViewResource(R.id.image, R.mipmap.ic_launcher); remoteView.setTextViewText(R.id.text , "Hello,this message is in a custom expanded view"); notification.contentView = remoteView; notification.contentIntent = pendingIntent; startForeground(1, notification); return Service.START_STICKY; }
3.4.4 在 onDestroy
方法中重啟 service
@Override public void onDestroy() { super.onDestroy(); startService(new Intent(this, MyService.class)); }
3.4.5 用 AlarmManager.setRepeating(…)
方法循環發送鬧鐘廣播, 接收的時候調用 service
的 onstart
方法
Intent intent = new Intent(MainActivity.this,MyAlarmReciver.class); PendingIntent sender = PendingIntent.getBroadcast( MainActivity.this, 0, intent, 0); // We want the alarm to go off 10 seconds from now. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.add(Calendar.SECOND, 1); AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE); //重複鬧鐘 /** * @param type * @param triggerAtMillis t 鬧鐘的第一次執行時間,以毫秒為單位 * go off, using the appropriate clock (depending on the alarm type). * @param intervalMillis 表示兩次鬧鐘執行的間隔時間,也是以毫秒為單位 * of the alarm. * @param operation 綁定了鬧鐘的執行動作,比如發送一個廣播、給出提示等等 */ am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2 * 1000, sender);
3.4.6 目前市場面的很多三方的消息推送 SDK
喚醒 APP
, 例如 Jpush
PS
: 以上這些方法並不代表着你的Service
就永生不死了,只能說是提高了進程的優先級。迄今為止我沒有發現能夠通過常規方法達到流氓需求 (通過長按home
鍵清除都清除不掉) 的方法,目前所有方法都是指通過Android
的內存回收機制和普通的第三方內存清除等手段後仍然保持運行的方法,有些手機廠商把這些知名的app
放入了自己的白名單中,保證了進程不死來提高用戶體驗(如微信、app
一樣躲避不了被殺的命運。
# 第四篇:InterService
- 作為一個老司機,如果連
Interservice
都沒聽說過,那就有點那個啥了
4.1 什麼是 IntentService
-
IntentService
是Service
的子類,比普通的Service
增加了額外的功能。 -
我們常用的
Service
存在兩個問題:
-
Service
不會專門啟動一條單獨的進程,Service
與它所在應用位於同一個進程中 -
Service
也不是專門一條新線程,因此不應該在Service
中直接處理耗時的任務
4.2 IntentService 的特徵
-
會創建獨立的
worker
線程來處理所有的Intent
請求 -
會創建獨立的
worker
線程來處理onHandleIntent()
方法實現的代碼,無需處理多線程問題 -
所有請求處理完成後,
IntentService
會自動停止,無需調用stopSelf()
方法停止Service
-
為
Service
的onBind()
提供默認實現,返回null
-
為
Service
的onStartCommand
提供默認實現,將請求Intent
添加到隊列中
4.3 Service 和 IntentService 區別
4.3.1 Service
是用於後台服務的
- 當應用程序被掛到後台的時候,為了保證應用某些組件仍然可以工作而引入了
Service
這個概念 - 那麼這裏面要強調的是:
Service
不是獨立的進程,也不是獨立的線程,它是依賴於應用程序的主線程的,也就是說,在更多時候不建議在Service
中編寫耗時的邏輯和操作,否則會引起ANR
。
也就是,service 裏面不可以進行耗時的操作。雖然在後台服務。但是也是在主線程裏面。
4.3.2 當我們編寫的耗時邏輯,不得不被 service
來管理的時候,就需要引入 IntentService
。
IntentService
是繼承Service
的,那麼它包含了Service
的全部特性,當然也包含service
的生命周期。- 那麼與
service
不同的是,IntentService
在執行onCreate
操作的時候,內部開了一個線程,去你執行你的耗時操作。
4.3.3 使用:
- 重寫
protected abstract void onHandleIntent(Intent intent)
4.3.4 IntentService
是一個通過 Context.startService(Intent)
啟動可以處理異步請求的 Service
- 使用時你只需要繼承
IntentService
和重寫其中的onHandleIntent(Intent)
方法接收一個Intent
對象 , 在適當的時候會停止自己 ( 一般在工作完成的時候 ) 。 - 所有的請求的處理都在一個工作線程中完成 , 它們會交替執行 ( 但不會阻塞主線程的執行 ) ,一次只能執行一個請求。
4.3.5 是一個基於消息的服務
- 每次啟動該服務並不是馬上處理你的工作,而是首先會創建對應的
Looper
,Handler
並且在MessageQueue
中添加的附帶客戶Intent
的Message
對象。 - 當
Looper
發現有Message
的時候接着得到Intent
對象通過在onHandleIntent((Intent)msg.obj)
中調用你的處理程序,處理完後即會停止自己的服務。 - 意思是
Intent
的生命周期跟你的處理的任務是一致的,所以這個類用下載任務中非常好,下載任務結束後服務自身就會結束退出。
4.3.6 總結 IntentService
的特徵有:
-
會創建獨立的
worker
線程來處理所有的Intent
請求; -
會創建獨立的
worker
線程來處理onHandleIntent()
方法實現的代碼,無需處理多線程問題; -
所有請求處理完成後,
IntentService
會自動停止,無需調用stopSelf()
方法停止Service
;
# 第五篇:Service 與 Activity
5.1 Activity 怎麼和 Service 綁定,怎麼在 Activity 中啟動對應的 Service
-
Activity
通過bindService(Intent service, ServiceConnection conn, int flags)
跟Service
進行綁定,當綁定成功的時候Service
會將代理對象通過回調的形式傳給conn
,這樣我們就拿到了Service
提供的服務代理對象。 -
在
Activity
中可以通過startService
和bindService
方法啟動Service
。一般情況下如果想獲取Service
的服務對象那麼肯定需要通過bindService()
方法,比如音樂播放器,第三方支付等。 -
如果僅僅只是為了開啟一個後台任務那麼可以使用
startService()
方法。
5.2 說說 Activity 、Intent 、Service 是什麼關係
-
他們都是
Android
開發中使用頻率最高的類。其中Activity
和Service
都屬於Android
的四大組件。他倆都是Context
類的子類ContextWrapper
的子類,因此他倆可以算是兄弟關係吧。 -
不過他們各有各自的本領,
Activity
負責用戶界面的顯示和交互,Service
負責後台任務的處理。 -
Activity
和Service
之間可以通過Intent
傳遞數據,因此可以把Intent
看作是通信使者。
5.3 Service 和 Activity 在同一個線程嗎
對於同一 app
來說默認情況下是在同一個線程中的,main Thread
( UI Thread
)。
5.4 Service 裏面可以彈吐司么
- 可以
- 彈吐司有個條件是:得有一個
Context
上下文,而Service
本身就是Context
的子類 - 因此在
Service
裏面彈吐司是完全可以的。比如我們在Service
中完成下載任務後可以彈一個吐司通知給用戶。
5.5 與 Service 交互方式
5.5.1 廣播交互
Server
端將目前的下載進度,通過廣播的方式發送出來,Client
端註冊此廣播的監聽器,當獲取到該廣播後,將廣播中當前的下載進度解析出來並更新到界面上。- 定義自己的廣播,這樣在不同的
Activity
、Service
以及應用程序之間,就可以通過廣播來實現交互。
5.5.2 共享文件交互
- 我們使用
SharedPreferences
來實現共享,當然也可以使用其它IO
方法實現,通過這種方式實現交互時需要注意,對於文件的讀寫的時候,同一時間只能一方讀一方寫,不能兩方同時寫。 Server
端將當前下載進度寫入共享文件中,Client
端通過讀取共享文件中的下載進度,並更新到主界面上。
5.5.3 Messenger
交互 ( 信使交互 )
Messenger
翻譯過來指的是信使,它引用了一個Handler
對象,別人能夠向它發送消息 ( 使用mMessenger.send ( Message msg )
方法)。- 該類允許跨進程間基於
Message
通信,在服務端使用Handler
創建一個Messenger
,客戶端只要獲得這個服務端的Messenger
對象就可以與服務端通信了 - 在
Server
端與 Client 端之間通過一個Messenger
對象來傳遞消息,該對象類似於信息中轉站,所有信息通過該對象攜帶
5.5.4 自定義接口交互
- 其實就是我們自己通過接口的實現來達到
Activity
與Service
交互的目的,我們通過在Activity
和Service
之間架設一座橋樑,從而達到數據交互的目的,而這種實現方式和AIDL
非常類似 - 自定義一個接口,該接口中有一個獲取當前下載進度的空方法。
Server
端用一個類繼承自Binder
並實現該接口,覆寫了其中獲取當前下載進度的方法。Client
端通過ServiceConnection
獲取到該類的對象,從而能夠使用該獲取當前下載進度的方法,最終實現實時交互。
5.5.5 AIDL
交互
- 遠程服務一般通過
AIDL
來實現,可以進行進程間通信,這種服務也就是遠程服務。 AIDL
屬於Android
的IPC
機制,常用於跨進程通信,主要實現原理基於底層Binder
機制。
# 第六篇:使用
6.1 什麼情況下會使用 Service
6.1.1 經驗總結:
Service
其實就是背地搞事情,又不想讓別人知道- 舉一個生活當中的例子,你想知道一件事情不需要直接去問,你可以通過側面了解。這就是
Service
設計的初衷
6.1.2 Service
為什麼被設計出來
- 根據
Service
的定義,我們可以知道需要長期在後台進行的工作我們需要將其放在Service
中去做。 - 得再通熟易懂一點,就是不能放在
Activity
中來執行的工作就必須得放到Service
中去做。 - 如:音樂播放、下載、上傳大文件、定時關閉應用等功能。這些功能如果放到
Activity
中做的話,那麼Activity
退出被銷毀了的話,那這些功能也就停止了,這顯然是不符合我們的設計要求的,所以要將他們放在Service
中去執行。
6.2 onStartCommand() 返回值 int 值的區別
- 有四種返回值,不同值代表的意思如下:
6.2.1 START_STICKY
:
- 如果
service
進程被 kill 掉,保留service
的狀態為開始狀態,但不保留遞送的intent
對象。 - 隨後系統會嘗試重新創建
service
, 由於服務狀態為開始狀態,所以創建服務後一定會調用onStartCommand ( Intent, int, int )
方法。 - 如果在此期間沒有任何啟動命令被傳遞到
service
, 那麼參數Intent
將為null
。
6.2.2 START_NOT_STICKY
:
- 「非粘性的」。
- 使用這個返回值時 , 如果在執行完
onStartCommand
後 , 服務被異常kill
掉 ,系統不會自動重啟該服務。
6.2.3 START_REDELIVER_INTENT
:
- 重傳
Intent
。 - 使用這個返回值時,如果在執行完
onStartCommand
後,服務被異常 kill 掉 - 系統會自動重啟該服務 , 並將 Intent 的值傳入。
6.2.4 START_STICKY_COMPATIBILITY
:
START_STICKY
的兼容版本 , 但不保證服務被kill
後一定能重啟。
6.3 在 service 的生命周期方法 onstartConmand() 可不可以執行網絡操作?如何在 service 中執行網絡操作?
- 可以直接在
Service
中執行網絡操作 - 在
onStartCommand()
方法中可以執行網絡操作
6.4 提高 service 的優先級
-
在
AndroidManifest.xml
文件中對於intent-filter
可以通過android:priority = 「1000」
這個屬性設置最高優先級,1000
是最高值,如果數字越小則優先級越低,同時實用於廣播。 -
在
onStartCommand
裏面調用startForeground()
方法把Service
提升為前台進程級別,然後再onDestroy
裏面要記得調用stopForeground ()
方法。 -
onStartCommand
方法,手動返回START_STICKY
。
- 在
onDestroy
方法里發廣播重啟service
。
service
+broadcast
方式,就是當service
走ondestory
的時候,發送一個自定義的廣播- 當收到廣播的時候,重新啟動
service
。( 第三方應用或是在setting
里-應用強制停止時,APP
進程就直接被幹掉了,onDestroy
方法都進不來,所以無法保證會執行 )
- 監聽系統廣播判斷
Service
狀態。
- 通過系統的一些廣播
- 比如:手機重啟、界面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的
Service
是否還存活。
Application
加上Persistent
屬性。
6.5 Service 的 onRebind ( Intent ) 方法在什麼情況下會執行
- 如果在
onUnbind()
方法返回true
的情況下會執行 , 否則不執行。
# 總結
- 本文基本涵蓋了
Android Service
相關的知識點。由於篇幅原因,諸如 InterService 具體使用方法等,沒辦法詳細的介紹,大家很容易就能在網上找到資料進行學習。 重點
:關於Android
的四大組件,到現在為止我才總結完Activity
和Service
,我將繼續針對,BroadcastRecevier
ContentProvider
等,以及四大組件之外的,事件分發、滑動衝突、新能優化等重要模塊,進行全面總結,歡迎大家關注_yuanhao 的 博客園 ,方便及時接收更新- 開始前還以為總結不難,實際寫文章的過程中,才知道什麼是艱辛。也不知道自己能不能咬牙堅持下去,希望大家給我鼓勵,就算只是一個贊,也是我堅持下去的理由!
碼字不易,你的點贊是我總結的最大動力!
-
由於我在「稀土掘金」「簡書」「
CSDN
」「博客園」等站點,都有新內容發佈。所以大家可以直接關注我的GitHub
倉庫,以免錯過精彩內容! -
1W
多字長文,加上精美思維導圖,記得點贊哦,歡迎關注 _yuanhao 的 博客園 ,我們下篇文章見! -
相關文章均可在我的主頁、GitHub 上看到,這裡限於篇幅原因,也為了保持界面整潔,讓大家能有跟舒心的閱讀體驗就不給出了,我們下篇文章不見不散!