23 個重難點突破,帶你吃透 Service 知識點「長達 1W+ 字」
前言
- 學
Android
有一段時間了,想必不少人也和我一樣,平時經常東學西湊,感覺知識點有些凌亂難成體系。所以趁著這幾天忙裡偷閒,把學的東西歸納下,捋捋思路。
這篇文章主要針對
Service
相關的知識點,進行詳細的梳理,祝大家食用愉快!
文章目錄
方便大家學習,我在 GitHub 建立了 倉庫
倉庫內容與部落格同步更新。由於我在
稀土掘金
簡書
CSDN
部落格園
等站點,都有新內容釋出。所以大家可以直接關注該倉庫,以免錯過精彩內容!倉庫地址:
超級乾貨!精心歸納Android
、JVM
、演算法等,各位帥氣的老鐵支援一下!給個 Star !
# 第一篇: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
機制。
- Android 面試,與Service互動方式
# 第六篇:使用
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
倉庫,以免錯過精彩內容!倉庫地址:
超級乾貨!精心歸納Android
、JVM
、演算法等,各位帥氣的老鐵支援一下!給個 Star !1W
多字長文,加上精美思維導圖,記得點贊哦,歡迎關注 _yuanhao 的 部落格園 ,我們下篇文章見!相關文章均可在我的主頁、GitHub 上看到,這裡限於篇幅原因,也為了保持介面整潔,讓大家能有跟舒心的閱讀體驗就不給出了,我們下篇文章不見不散!