謹以文章記錄學習歷程,如有錯誤還請指明勾徽。
前言
記得我的第一部手機是Nokia的迫横,當時的Symbian系統(tǒng)相較于其他手機最突出的一點就是,Symbian系統(tǒng)支持后臺功能吨凑,畢竟這使得我們可以一邊聽著音樂一邊聊著qq捍歪,在當時這可是很酷的一件事,畢竟Symbian系統(tǒng)出現(xiàn)以前鸵钝,只能用一部mp3聽音樂糙臼,另外再拿一部手機打電話。
Android顯然看到了這個發(fā)光點恩商,自始至終就支持后臺功能变逃,IOS隨著時間的推移也發(fā)現(xiàn)這個功能的重要性,在后續(xù)版本加入了后臺功能怠堪。
本文就將針對這一炫酷的后臺功能(Service
)開始講解揽乱,深入總結Service
的方方面面的知識。
簡介
- 服務(
Service
) 是Android實現(xiàn)程序后臺運行的解決方案研叫,是Android四大組件之一锤窑。 - 適合于執(zhí)行不需要和用戶交互而且需要長期運行的任務。
生命周期
首先放上Google官方文檔給出的生命周期示意圖:
(單獨使用startService()
和單獨使用bindService()
時的生命周期)
回調方法介紹
回調方法 | 說明 |
---|---|
onCreate() | 首次創(chuàng)建服務時調用嚷炉,執(zhí)行一次性設置程序(在調用 onStartCommand() 或 onBind() 之前)渊啰。如果服務已在運行,則不會調用此方法。 |
onStartCommand() | 當另一個組件(如 Activity )通過調用 startService() 請求啟動服務時绘证,系統(tǒng)將調用此方法隧膏。一旦執(zhí)行此方法,服務即會啟動并可在后臺無限期運行嚷那。 如果您實現(xiàn)此方法胞枕,則在服務工作完成后,需要由您通過調用 stopSelf() 或 stopService() 來停止服務魏宽。(如果您只想提供綁定腐泻,則無需實現(xiàn)此方法。) |
onDestroy() | 當服務不再使用且將被銷毀時队询,系統(tǒng)將調用此方法派桩。服務應該實現(xiàn)此方法來清理所有資源,如線程蚌斩、注冊的偵聽器铆惑、接收器等。 這是服務接收的最后一個調用送膳。 |
onBind() | 當另一個組件想通過調用 bindService() 與服務綁定(例如執(zhí)行 RPC )時员魏,系統(tǒng)將調用此方法。在此方法的實現(xiàn)中叠聋,您必須通過返回 IBinder 提供一個接口撕阎,供客戶端用來與服務進行通信。請務必實現(xiàn)此方法晒奕,但如果您并不希望允許綁定闻书,則應返回 null 。 |
onUnbind() | 解綁服務 |
特別事項
-
onCreate()
只調1次 -
startService()
調用次數(shù)=onStartCommand()
調用次數(shù)脑慧。 -
Service
通過bindService()
被綁定啟動后魄眉,將一直運行,直到調用unBindService()
或者調用bindService()
的Context
被銷毀(即 使用bindService()
建立的連接斷開)闷袒。
盡管
Context
銷毀時坑律,Service
會自動停止,但只要調用bindService()
進行綁定囊骤,仍然還是需要在某處調用unBindService()
解除綁定
-
onStartCommand()
方法必須返回一個整數(shù),描述系統(tǒng)在殺死服務后如何繼續(xù)運行晃择。- START_NOT_STICKY:
不會重新創(chuàng)建服務,除非有未發(fā)送的intent
也物。當應用程序可以簡單地重新啟動任何未完成的工作時宫屠,這是避免在不必要的情況下運行服務的最安全的選項。 - START_STICKY
重新創(chuàng)建服務并調用onStartCommand()
滑蚯,但不會再次送入上一個intent
浪蹂。相反除非有未發(fā)送完的啟動服務的intent
抵栈,否則使將用null intent
調用onStartCommand()
。這適用于不執(zhí)行命令的媒體播放器(或類似的服務)坤次,但它們會持續(xù)運行并隨時待命古劲。 - START_REDELIVER_INTENT
重新創(chuàng)建服務并調用onStartCommand()
,并將上一個intent
交付給服務缰猴。任何未處理的intent
都將依次傳遞产艾。這適用于需要立即恢復工作的活躍服務,例如下載文件滑绒。
- START_NOT_STICKY:
手機屏幕旋轉時闷堡,Activity會銷毀 & 重新創(chuàng)建,因此使用
bindService()
建立的連接會斷開
startService()與bindService()混合使用時的生命周期:
我們給出兩個例子:
startService()
->bindService()
或者bindService()
->startService()
蹬挤,這二者順序僅僅影響onBind()
和onStartCommand()
的順序- 多次重復
startService()
或bindService()
也同樣遵循上述 特別事項
- 按順序1,2,3,4執(zhí)行
(1)startService()
:調用onCreate()
->onStartCommand()
(2)bindService()
:調用onBind()
(3)stopService()
:沒有調用onDestory()
缚窿,Service
仍然在運行棘幸!
(4)unbindService()
:調用onUnbind()
->onDestory()
焰扳,此時Service關閉!
官方解釋:若被停止的服務依然有ServiceConnection
與其綁定误续,則服務不能銷毀吨悍,直至我們把所有ServiceConnection
解綁
- 將上述3,4調換
(1)startService()
:調用onCreate()
->onStartCommand()
(2)bindService()
:調用onBind()
(3)unbindService()
:調用onUnbind()
,Service
仍然在運行蹋嵌!
(4)stopService()
:調用onDestory()
育瓜,此時Service關閉!
官方解釋:
當所有ServiceConnection
解綁后栽烂,系統(tǒng)會自動銷毀服務(不包括同時用startService()
啟動的情況)躏仇。此時,我們不得不再調用一次stopService()
來銷毀它
給出Google官方的圖腺办,方便直觀的理解:
分類
按啟動方式:
(上面生命周期已經(jīng)介紹的很詳細了焰手,只簡要概括)
類別 | 區(qū)別 | 應用場景 |
---|---|---|
Started Service ( startService() 啟動的服務) |
停止服務使用stopService()
|
不需要與Service 通信服務長期運行 |
Bound Service ( bindService() 啟動的服務) |
停止服務使用unbindService()
|
需要與Service 通信 |
混合服務Service ( startService() 同時也 bindService() 啟動的服務) |
停止服務應同時使用stopService(() 與unbindService()
|
需要與Service 通信服務長期運行 |
(官方的分類只有前兩種,上面的混合服務Service只是我個人的看法怀喉,也是為了后續(xù)方便介紹如何使用)
按運行地點
類別 | 區(qū)別 | 優(yōu)點 | 缺點 | 應用 |
---|---|---|---|---|
本地服務(Local) | 該服務依附在主進程上 | 節(jié)約了資源 通信方便不需要IPC书妻,也不需要AIDL |
主進程被Kill后,服務便會終止躬拢。 | 我們大部分寫的Service就是這種服務躲履,如音樂后臺播放 |
遠程服務(Remote) | 該服務是獨立的進程 | 有較高的靈活性,Activity所在進程被Kill的時候聊闯,該服務依然在運行 | 獨立進程工猜,占用一定資源 使用AIDL進行IPC麻煩 |
一些提供系統(tǒng)服務的Service,這種Service是常駐的菱蔬。 |
按運行類型
類別 | 區(qū)別 | 應用 |
---|---|---|
前臺服務 | 通知欄顯示Notification | 當服務被終止的時候篷帅,通知欄的 Notification 也會消失,這樣對于用戶有一定的通知作用。常見的如音樂播放服務犹褒。 |
后臺服務 | 默認的服務即為后臺服務抵窒,用戶不可見 | 當服務被終止的時候,用戶是看不到效果的叠骑。某些不需要運行或終止提示的服務李皇,如天氣更新,日期同步宙枷,郵件同步等掉房。 |
具體實現(xiàn)
實際上,在上述不同場景下的分類多多少少都有所重疊慰丛,比如:
- Bound Service包括 本地(
Binder
) 和 遠程服務(Message
或AIDL
)- Started Service/Bound Service都可以在位于前臺卓囚,也都可以在后臺
Demo地址
1. Started Service
- 繼承
Service
類 - 復寫
onCreate()
,onStartCommand()
,onDestroy()
,onBind()
方法 - 在
AndroidManifest.xml
中注冊 - 通過
startService()
,stopService()
啟動和停止服務 - 前臺通知只需要再合適的地方調用
startForeground()
,Demo 通知欄適配Android8.0
下面以Demo具體說明:
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private Button mButton1;
private Button mButton2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton1 = findViewById(R.id.button1);
mButton2 = findViewById(R.id.button2);
mButton1.setOnClickListener(this);
mButton2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button1:
//啟動服務
startService(new Intent(MainActivity.this,StartedService.class));
break;
case R.id.button2:
//停止服務
stopService(new Intent(MainActivity.this,StartedService.class));
break;
}
}
}
StartedService.java
//繼承Service類
public class StartedService extends Service {
//TAG標記
private static final String TAG = "StartedService";
//復寫onCreate()方法诅病,輸出一段內(nèi)容
@Override
public void onCreate() {
Log.d(TAG, "onCreate: "+ TAG);
super.onCreate();
}
//復寫onStartCommand(方法),輸出一段內(nèi)容
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: "+ TAG);
//適配Android 8.0的通知
Intent notificationIntent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//實例化通道channel
final String ID = "channalID";
final String name = "name";
NotificationChannel channel = new NotificationChannel(ID,name, NotificationManager.IMPORTANCE_HIGH);
//調用該方法之后哪亿,該channel的ID才能被通知使用
manager.createNotificationChannel(channel);
//添加建立好的通道的ID
Notification notification = new NotificationCompat.Builder(this,ID)
.setContentTitle("前臺服務標題")
.setContentText("前臺服務內(nèi)容")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setChannelId(ID)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build();
//開啟前臺服務
startForeground(1,notification);
return super.onStartCommand(intent, flags, startId);
}
//復寫onDestroy()方法,輸出一段內(nèi)容
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: "+TAG);
stopForeground(true);
super.onDestroy();
}
//不需要通信贤笆,返回空即可
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidMenifest.xml
<service android:name=".StartedService"/><service android:name=".StartedService"/>
前臺Service優(yōu)先級較高蝇棉,不會由于系統(tǒng)內(nèi)存不足而被回收;后臺Service優(yōu)先級較低芥永,當系統(tǒng)出現(xiàn)內(nèi)存不足情況時篡殷,很有可能會被回收前臺Service優(yōu)先級較高,不會由于系統(tǒng)內(nèi)存不足而被回收埋涧;后臺Service優(yōu)先級較低板辽,當系統(tǒng)出現(xiàn)內(nèi)存不足情況時,很有可能會被回收
Androidmanifest里Service的常見屬性說明
屬性 | 說明 | 備注 |
---|---|---|
android:name | Service的類名 | |
android:label | Service的名字 | 若不設置棘催,默認為Service類名 |
android:icon | Service的圖標 | |
android:permission | 申明此Service的權限 | 有提供了該權限的應用才能控制或連接此服務 |
android:process | 表示該服務是否在另一個進程中運行(遠程服務) | 不設置默認為本地服務 remote則設置成遠程服務 |
android:enabled | 系統(tǒng)默認啟動 | true:Service 將會默認被系統(tǒng)啟動 不設置則默認為false |
android:exported | 該服務是否能夠被其他應用程序所控制或連接 | 不設置默認此項為 false |
結果展示
2. Bound Service
2.1 本地服務-Binder實現(xiàn)
- 繼承
Service
類劲弦,并新建子類繼承Binder
類,寫入關聯(lián)方法巧鸭,創(chuàng)建實例 - 調用方通過
ServiceConnection
實例與Service
子類建立聯(lián)系 - 復寫
onCreate()
,onBind()
,onUnBind()
,onDestroy()
下面給出部分Demo代碼作介紹:
MainActivity.java
- 在Activity通過調用MyBinder類中的public方法來實現(xiàn)Activity與Service的聯(lián)系
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private LocalService.LocalBinder mBinder;
private LocalService mLocalService;
...
private Button mButton3;
private Button mButton4;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mButton3 = findViewById(R.id.button3);
mButton4 = findViewById(R.id.button4);
...
mButton3.setOnClickListener(this);
mButton4.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
...
//調用bindService()
case R.id.button3:
//這里傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯(lián)后自動創(chuàng)建Service
//這會使得LocalService中的onCreate()方法得到執(zhí)行瓶您,但onStartCommand()方法不會執(zhí)行
bindService(new Intent(MainActivity.this,LocalService.class),mConnection,BIND_AUTO_CREATE);
break;
//調用unBindService()
case R.id.button4:
unbindService(mConnection);
break;
}
}
//創(chuàng)建ServiceConnection的匿名類
private ServiceConnection mConnection = new ServiceConnection() {
//在建立關聯(lián)的時候調用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//得到MyBinder的實例,可以調用MyBinder中的方法
mBinder = (LocalService.LocalBinder) service;
//得到LocalService的實例纲仍,可以調用其中方法
mLocalService = mBinder.getService();
//調用MyBinder類中的方法
mBinder.showBinderInfo();
//調用LocalService服務中的方法
mLocalService.showServiceInfo();
}
//注意:調用unBindService()方法時呀袱,不會調用該方法!V5夜赵!
//該方法只有在系統(tǒng)回收該服務時候才會調用!O绺铩寇僧!
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
LocalService
- 在新建子類繼承Service類摊腋,并新建一個子類繼承自Binder類、寫入與Activity關聯(lián)需要的方法嘁傀、創(chuàng)建實例
//繼承Service類
public class LocalService extends Service {
private static final String TAG = "LocalService";
//創(chuàng)建LocalBinder實例
private final LocalBinder mBinder = new LocalBinder();
//新建LocalBinder類繼承自Binder類
public class LocalBinder extends Binder{
//返回當前服務對象的方法
LocalService getService(){
return LocalService.this;
}
//LocalBinder中的公開方法兴蒸,供建立聯(lián)系的Activity訪問
void showBinderInfo(){
Log.d(TAG, "showInfo: 我是來自Binder的方法");
}
}
//復寫方法
@Override
public void onCreate() {
Log.d(TAG, "onCreate: "+ TAG);
super.onCreate();
}
//復寫方法
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: "+TAG);
return super.onStartCommand(intent, flags, startId);
}
//復寫方法
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
//復寫方法
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: "+TAG);
super.onDestroy();
}
//返回當前LocalBinder實例
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//服務中的公開方法,供建立聯(lián)系的Activity訪問
public void showServiceInfo(){
Log.d(TAG, "showServiceInfo: 我是來自LocalService的方法");
}
}
AndroidManifest.xml
<service android:name=".LocalService" />
結果展示
注意:多次解綁會報錯(
java.lang.IllegalArgumentException
)细办,原因大概是解綁之后橙凳,找不到該注冊的服務。詳細需要深入源碼找原因笑撞。
2.2遠程服務-IPC&AIDL
具體見文章:Android多線程基礎解析
總結
- 本文對Android中的服務Service的方方面面做出詳細的總結岛啸。
- 筆者水平有限,如有錯漏茴肥,歡迎指正坚踩。
- 接下來我也會將所學的知識分享出來,有興趣可以繼續(xù)關注whd_Alive的Android開發(fā)筆記
歡迎關注whd_Alive的簡書
- 不定期分享Android開發(fā)相關的技術干貨瓤狐,期待與你的交流瞬铸,共勉。