android進階3step1:Android元件通訊——Service基礎
轉:https://www.jianshu.com/p/95ec2a23f300 Android Service使用詳解
轉:https://www.jianshu.com/p/4c798c91a613 Android Service兩種啟動方式詳解(總結版)
Service基礎
Service是Android系統中的四大元件之一
主要有兩個應用場景:後臺執行和跨程序訪問。
Service可以在後臺執行長時間執行操作而不提供使用者介面,除非系統必須回收記憶體資源,否則系統不會停止或銷燬服務。服務可由其他應用元件啟動,而且即使使用者切換到其他應用,服務仍將在後臺繼續執行。
此外,元件可以繫結到服務,以與之進行互動,甚至是執行程序間通訊 (IPC)
需要注意的是,Service是在主執行緒裡執行操作的,可能會因為執行耗時操作而導致ANR
一、基礎知識
Service可以分為以下三種形式:
- 啟動
當應用元件通過呼叫 startService()啟動服務時,服務即處於“啟動”狀態。一旦啟動,服務即可在後臺無限期執行,即使啟動服務的元件已被銷燬也不受影響。 已啟動的服務通常是執行單一操作,而且不會將結果返回給呼叫方 - 繫結
當應用元件通過呼叫 bindService()繫結到服務時,服務即處於“繫結”狀態。繫結服務提供了一個客戶端-伺服器介面,允許元件與服務進行互動、傳送請求、獲取結果,甚至是利用程序間通訊 (IPC) 跨程序執行這些操作。多個元件可以同時繫結服務,服務只會在元件與其繫結時執行,一旦該服務與所有元件之間的繫結全部取消,系統便會銷燬它 - 啟動且繫結
服務既可以是啟動服務,也允許繫結。此時需要同時實現以下回調方法:onStartCommand()和 onBind()。系統不會在所有客戶端都取消繫結時銷燬服務。為此,必須通過呼叫 stopSelf()或 stopService()顯式停止服務
無論應用是處於啟動狀態還是繫結狀態,或者處於啟動且繫結狀態,任何應用元件均可像使用 Activity 那樣通過呼叫 Intent 來使用服務(即使此服務來自另一應用),也可以通過清單檔案將服務宣告為私有服務,阻止其他應用訪問
要使用服務,必須繼承Service類(或者Service類的現有子類),在子類中重寫某些回撥方法,以處理服務生命週期的某些關鍵方面並提供一種機制將元件繫結到服務
- onStartCommand()
當元件通過呼叫 startService()請求啟動服務時,系統將呼叫此方法(如果是繫結服務則不會呼叫此方法)。一旦執行此方法,服務即會啟動並可在後臺無限期執行。 在指定任務完成後,通過呼叫 stopSelf()或 stopService()來停止服務 - onBind()
當一個元件想通過呼叫 bindService()與服務繫結時,系統將呼叫此方法(如果是啟動服務則不會呼叫此方法)。在此方法的實現中,必須通過返回 IBinder提供一個介面,供客戶端用來與服務進行通訊 - onCreate()
首次建立服務時,系統將呼叫此方法來執行初始化操作(在呼叫 onStartCommand()或 onBind()之前)。如果在啟動或繫結之前Service已在執行,則不會呼叫此方法 - onDestroy()
當服務不再使用且將被銷燬時,系統將呼叫此方法,這是服務接收的最後一個呼叫,在此方法中應清理佔用的資源
僅當記憶體過低必須回收系統資源以供前臺 Activity 使用時,系統才會強制停止服務。如果將服務繫結到前臺 Activity,則它不太可能會終止,如果將服務宣告為在前臺執行,則它幾乎永遠不會終止。或者,如果服務已啟動並要長時間執行,則系統會隨著時間的推移降低服務在後臺任務列表中的位置,而服務也將隨之變得非常容易被終止。如果服務是啟動服務,則必須將其設計為能夠妥善處理系統對它的重啟。 如果系統終止服務,那麼一旦資源變得再次可用,系統便會重啟服務(這還取決於 onStartCommand() 的返回值)
二、宣告Service
如同其他元件一樣,想要使用Service,必須在清單檔案中對其進行宣告
宣告方式是新增 < service >元素作為 < application >元素的子元素
例如
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service android:name=".MyService" />
</application>
android:name屬性是唯一必需的屬性,用於指定服務的類名,還可將其他屬性包括在 < service >元素中以定義一些特性
為了確保應用的安全性,最好始終使用顯式 Intent 啟動或繫結 Service,且不要為服務宣告 Intent 過濾器。 啟動哪個服務存在一定的不確定性,而如果對這種不確定性的考量非常有必要,則可為服務提供 Intent 過濾器並從 Intent 中排除相應的元件名稱,但隨後必須使用 setPackage()方法設定 Intent 的軟體包,這樣可以充分消除目標服務的不確定性
此外,還可以通過新增 android:exported屬性並將其設定為 "false",確保服務僅適用於本應用。這可以有效阻止其他應用啟動本應用內的服務,即便在使用顯式 Intent 時也是如此
Service包含的屬性有
<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
</service>
屬性 | 說明 |
---|---|
description | 對服務進行描述,屬性值應為對字串資源的引用,以便進行本地化 |
directBootAware | 設定是否可以在使用者解鎖裝置之前執行,預設值為“false” |
enabled | 設定是否可以由系統來例項化服務。< application >元素有自己的enabled屬性,適用於包括服務在內的所有應用程式元件。要啟用服務,< application >和< service >屬性必須都為“true”(預設情況下都為true)。如果其中一個是“false”,則服務被禁用 |
exported | 設定其他應用程式的元件是否可以呼叫本服務或與其互動,如果可以,則為“true”。當值為“false”時,只有同一個應用程式或具有相同使用者ID的應用程式的元件可以啟動該服務或繫結到該服務。該屬性的預設值取決於服務是否包含Intent filters。沒有任何過濾器意味著它只能通過指定其確切的類名來呼叫,這意味著該服務僅用於應用程式內部使用(因為其他人不知道類名)。所以在這種情況下,預設值為“false”。 另一方面,如果存在至少一個過濾器,意味著該服務打算供外部使用,因此預設值為“true” |
icon | 服務的圖示,屬性值應是對drawable資源的引用。如果未設定,則將使用應用程式圖示 |
isolatedProcess | 設定該服務是否作為一個單獨的程序執行,如果設定為true,此服務將在與系統其餘部分隔離的特殊程序下執行,並且沒有自己的許可權,與它唯一的通訊是通過服務API(繫結和啟動) |
label | 可以向用戶顯示的服務的名稱,屬性值應是對字串資源的引用 |
name | 服務類的完全限定名 |
permission | 設定元件必須具有的許可權,得以啟動服務或繫結服務。如果startService(),bindService()或stopService()的呼叫者沒有被授予此許可權,則該方法將不會工作,並且Intent物件不會傳遞到服務中 |
process | 用來執行服務的程序的名稱。通常,應用程式的所有元件都執行在應用程式建立的預設程序中,它與應用程式包名具有相同的名稱。 < application >元素的process屬性可以為所有元件設定不同的預設值,但元件可以使用自己的程序屬性覆蓋預設值,從而允許跨多個程序擴充套件應用程式 |
三、兩種方式啟動Service
三、啟動Service
啟動服務由元件通過呼叫 startService()啟動,服務啟動之後,其生命週期即獨立於啟動它的元件,並且可以在後臺無限期地執行,即使啟動服務的元件已被銷燬也不受影響。因此,服務應通過呼叫 stopSelf()來自行停止執行,或者由另一個元件呼叫 stopService()來停止
可以通過擴充套件兩個類來建立啟動服務:
- Service
這是所有服務的父類。擴充套件此類時,如果要執行耗時操作,必須建立一個用於執行操作的新執行緒,因為預設情況下服務將運行於UI執行緒 - IntentService
這是 Service 的子類,它使用工作執行緒逐一處理所有啟動請求。如果應用不需要同時處理多個請求,這是最好的選擇。IntentService只需實現建構函式與 onHandleIntent()方法即可,onHandleIntent()方法會接收每個啟動請求的 Intent
3.1、繼承Service
這裡舉一個音樂播放器的例子
繼承Service類實現自定義Service,提供在後臺播放音樂、暫停音樂、停止音樂的方法
public class MyService extends Service {
private final String TAG = "MyService";
private MediaPlayer mediaPlayer;
private int startId;
public enum Control {
PLAY, PAUSE, STOP
}
public MyService() {
}
@Override
public void onCreate() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.music);
mediaPlayer.setLooping(false);
}
Log.e(TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.startId = startId;
Log.e(TAG, "onStartCommand---startId: " + startId);
Bundle bundle = intent.getExtras();
if (bundle != null) {
Control control = (Control) bundle.getSerializable("Key");
if (control != null) {
switch (control) {
case PLAY:
play();
break;
case PAUSE:
pause();
break;
case STOP:
stop();
break;
}
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy");
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
super.onDestroy();
}
private void play() {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
private void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
private void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
stopSelf(startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind");
throw new UnsupportedOperationException("Not yet implemented");
}
}
在佈局中新增三個按鈕,用於控制音樂播放、暫停與停止
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void playMusic(View view) {
Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable("Key", MyService.Control.PLAY);
intent.putExtras(bundle);
startService(intent);
}
public void pauseMusic(View view) {
Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable("Key", MyService.Control.PAUSE);
intent.putExtras(bundle);
startService(intent);
}
public void stopMusic(View view) {
Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable("Key", MyService.Control.STOP);
intent.putExtras(bundle);
startService(intent);
//或者是直接如下呼叫
//Intent intent = new Intent(this, MyService.class);
//stopService(intent);
}
}
在清單檔案中宣告Service,為其新增label標籤,便於在系統中識別Service
<service
android:name=".MyService"
android:label="@string/app_name" />
這裡寫圖片描述
點選“播放音樂”按鈕後,在後臺將會執行著名為“Service測試”的服務
這裡寫圖片描述
通過Log日誌可以發現
- 多次點選“播放音樂”按鈕,
- “onCreate()”方法只會在初始時呼叫一次
- “onStartCommand(Intent intent, int flags, int startId)”方法會在每次點選時都被呼叫
- 點選“停止音樂”按鈕,“onDestroy()”方法會被呼叫
- 當中,每次回撥onStartCommand()方法時,引數“startId”的值都是遞增的startId用於唯一標識每次對Service發起的處理請求
- 如果服務同時處理多個 onStartCommand() 請求,則不應在處理完一個啟動請求之後立即銷燬服務,因為此時可能已經收到了新的啟動請求,在第一個請求結束時停止服務會導致第二個請求被終止。
- 為了避免這一問題,可以使用 stopSelf(int) 確保服務停止請求始終基於最新一次的啟動請求。也就是說,如果呼叫 stopSelf(int) 方法的引數值與onStartCommand()接受到的最新的startId值不相符的話,stopSelf()方法就會失效,從而避免終止尚未處理的請求
如果服務沒有提供繫結,則使用 startService()傳遞的 Intent 是應用元件與服務之間唯一的通訊模式。如果希望服務返回結果,則啟動服務的客戶端可以為廣播建立一個 PendingIntent(使用 getBroadcast()),並通過啟動服務的 Intent 傳遞給服務。然後,服務就可以使用廣播傳遞結果
當中,onStartCommand()方法必須返回一個整數,用於描述系統應該如何應對服務被殺死的情況,返回值必須是以下常量之一:
- START_NOT_STICKY
如果系統在 onStartCommand() 返回後終止服務,則除非有掛起 Intent 要傳遞,否則系統不會重建服務。這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時執行服務 - START_STICKY
如果系統在 onStartCommand() 返回後終止服務,則會重建服務並呼叫 onStartCommand(),但不會重新傳遞最後一個 Intent。相反,除非有掛起 Intent 要啟動服務(在這種情況下,將傳遞這些 Intent ),否則系統會通過空 Intent 呼叫 onStartCommand()。這適用於不執行命令、但無限期執行並等待作業的媒體播放器(或類似服務) - START_REDELIVER_INTENT
如果系統在 onStartCommand() 返回後終止服務,則會重建服務,並通過傳遞給服務的最後一個 Intent 呼叫 onStartCommand()。任何掛起 Intent 均依次傳遞。這適用於主動執行應該立即恢復的作業(例如下載檔案)的服務
如何保證Service不被殺死?
1. onStartCommand方式中,返回START_STICKY
首先我們來看看onStartCommand都可以返回哪些值:
呼叫Context.startService方式啟動Service時,如果Android面臨記憶體匱乏,可能會銷燬當前執行的Service,待記憶體充足時可以重建Service。而Service被Android系統強制銷燬並再次重建的行為依賴於Service的onStartCommand()方法的返回值。
-
START_NOT_STICKY
如果返回START_NOT_STICKY,表示當Service執行的程序被Android系統強制殺掉之後,不會重新建立該Service。當然如果在其被殺掉之後一段時間又呼叫了startService,那麼該Service又將被例項化。那什麼情境下返回該值比較恰當呢?
如果我們某個Service執行的工作被中斷幾次無關緊要或者對Android記憶體緊張的情況下需要被殺掉且不會立即重新建立這種行為也可接受,那麼我們便可將 onStartCommand的返回值設定為START_NOT_STICKY。
舉個例子,某個Service需要定時從伺服器獲取最新資料:通過一個定時器每隔指定的N分鐘讓定時器啟動Service去獲取服務端的最新資料。當執行到Service的onStartCommand時,在該方法內再規劃一個N分鐘後的定時器用於再次啟動該Service並開闢一個新的執行緒去執行網路操作。假設Service在從伺服器獲取最新資料的過程中被Android系統強制殺掉,Service不會再重新建立,這也沒關係,因為再過N分鐘定時器就會再次啟動該Service並重新獲取資料。 -
START_STICKY
如果返回START_STICKY,表示Service執行的程序被Android系統強制殺掉之後,Android系統會將該Service依然設定為started狀態(即執行狀態),但是不再儲存onStartCommand方法傳入的intent物件,然後Android系統會嘗試再次重新建立該Service,並執行onStartCommand回撥方法,但是onStartCommand回撥方法的Intent引數為null,也就是onStartCommand方法雖然會執行但是獲取不到intent資訊。如果你的Service可以在任意時刻執行或結束都沒什麼問題,而且不需要intent資訊,那麼就可以在onStartCommand方法中返回START_STICKY,比如一個用來播放背景音樂功能的Service就適合返回該值。 -
START_REDELIVER_INTENT
如果返回START_REDELIVER_INTENT,表示Service執行的程序被Android系統強制殺掉之後,與返回START_STICKY的情況類似,Android系統會將再次重新建立該Service,並執行onStartCommand回撥方法,但是不同的是,Android系統會再次將Service在被殺掉之前最後一次傳入onStartCommand方法中的Intent再次保留下來並再次傳入到重新建立後的Service的onStartCommand方法中,這樣我們就能讀取到intent引數。只要返回START_REDELIVER_INTENT,那麼onStartCommand重的intent一定不是null。如果我們的Service需要依賴具體的Intent才能執行(需要從Intent中讀取相關資料資訊等),並且在強制銷燬後有必要重新建立執行,那麼這樣的Service就適合返回START_REDELIVER_INTENT。
2.提高Service的優先順序
在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority = "1000"這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時適用於廣播。
3.提升Service程序的優先順序
當系統程序空間緊張時,會依照優先順序自動進行程序的回收。
Android將程序分為6個等級,按照優先順序由高到低依次為:
- 前臺程序foreground_app
- 可視程序visible_app
- 次要服務程序secondary_server
- 後臺程序hiddena_app
- 內容供應節點content_provider
- 空程序empty_app
可以使用startForeground將service放到前臺狀態,這樣低記憶體時,被殺死的概率會低一些。
4.在onDestroy方法裡重啟Service
當service走到onDestroy()時,傳送一個自定義廣播,當收到廣播時,重新啟動service。
5.系統廣播監聽Service狀態
6.將APK安裝到/system/app,變身為系統級應用
3.2、IntentService
由於大多數啟動服務都不必同時處理多個請求,因此使用 IntentService 類實現服務也許是最好的選擇
IntentService 執行以下操作:
- 建立預設的工作執行緒,用於在應用的主執行緒外執行傳遞給 onStartCommand() 的所有 Intent
- 建立工作佇列,用於將 Intent 逐一傳遞給 onHandleIntent() 實現,這樣就不必擔心多執行緒問題
- 在處理完所有啟動請求後停止服務,因此不必自己呼叫 stopSelf()方法
- 提供 onBind() 的預設實現(返回 null)
- 提供 onStartCommand() 的預設實現,可將 Intent 依次傳送到工作佇列和 onHandleIntent()
因此,只需實現建構函式與 onHandleIntent() 方法即可
這裡舉一個關於輸出日誌的例子
public class MyIntentService extends IntentService {
private final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, bundle.getString("key", "預設值"));
}
}
}
}
public class StartIntentServiceActivity extends AppCompatActivity {
private int i = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_intent_service);
}
public void startService(View view) {
Intent intent = new Intent(this, MyIntentService.class);
Bundle bundle = new Bundle();
bundle.putString("key", "當前值:" + i++);
intent.putExtras(bundle);
startService(intent);
}
}
當中,startService(View view)方法與一個Button繫結,連續快速地多次點選Button,驗證IntentService當中的日誌是否依次輸出,還是交叉著輸出
可以看到是依次輸出的,即IntentService的工作執行緒是逐一處理所有啟動請求的
這裡寫圖片描述
此外,檢視後臺,可以看到當前後臺應用程式程序中有兩個服務
這裡寫圖片描述
四、繫結Service
bindService啟動服務特點:
- bindService啟動的服務和呼叫者之間是典型的client-server模式。呼叫者是client,service則是server端。service只有一個,但繫結到service上面的client可以有一個或很多個。這裡所提到的client指的是元件,比如某個Activity。
- client可以通過IBinder介面獲取Service例項,從而實現在client端直接呼叫Service中的方法以實現靈活互動,這在通過startService方法啟動中是無法實現的。
- bindService啟動服務的生命週期與其繫結的client息息相關。當client銷燬時,client會自動與Service解除繫結。當然,client也可以明確呼叫Context的unbindService()方法與Service解除繫結。當沒有任何client與Service繫結時,Service會自行銷燬。
應用元件(客戶端)通過呼叫 bindService()繫結到服務,繫結是非同步的,系統隨後呼叫服務的 onBind()方法,該方法返回用於與服務互動的 IBinder。要接收 IBinder,客戶端必須提供一個 ServiceConnection例項用於監控與服務的連線,並將其傳遞給 bindService()。當 Android 系統建立了客戶端與服務之間的連線時,會回撥ServiceConnection物件的onServiceConnected()方法,向客戶端傳遞用來與服務通訊的 IBinder多個客戶端可同時連線到一個服務。
不過,只有在第一個客戶端繫結時,系統才會呼叫服務的 onBind() 方法來檢索 IBinder。系統隨後無需再次呼叫 onBind(),便可將同一 IBinder 傳遞至其他繫結的客戶端。當所有客戶端都取消了與服務的繫結後,系統會將服務銷燬(除非 startService() 也啟動了該服務)
另外,只有 Activity、服務和內容提供者可以繫結到服務,無法從廣播接收器繫結到服務
可以通過以下三種方法定義IBinder介面:
- 擴充套件 Binder 類
如果服務是供本應用專用,並且執行在與客戶端相同的程序中,則應通過擴充套件 Binder 類並從 onBind() 返回它的一個例項來建立介面。客戶端收到 Binder 後,可利用它直接訪問 Service 中可用的公共方法 - 使用 Messenger
如需讓介面跨不同的程序工作,則可使用 Messenger 為服務建立介面。服務可以這種方式定義對應於不同型別 Message 物件的 Handler。此 Handler 是 Messenger 的基礎,後者隨後可與客戶端分享一個 IBinder,從而讓客戶端能利用 Message 物件向服務傳送命令。此外,客戶端還可定義自有 Messenger,以便服務回傳訊息。這是執行程序間通訊 (IPC) 的最簡單方法,因為 Messenger 會在單一執行緒中建立包含所有請求的佇列,這樣就不必對服務進行執行緒安全設計 - 使用 AIDL
AIDL(Android 介面定義語言)執行所有將物件分解成原語的工作,作業系統可以識別這些原語並將它們編組到各程序中,以執行 IPC。 之前採用 Messenger 的方法實際上是以 AIDL 作為其底層結構。 如上所述,Messenger 會在單一執行緒中建立包含所有客戶端請求的佇列,以便服務一次接收一個請求。 不過,如果想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,服務必須具備多執行緒處理能力,並採用執行緒安全式設計。如需直接使用 AIDL,必須建立一個定義程式設計介面的 .aidl 檔案。Android SDK 工具利用該檔案生成一個實現介面並處理 IPC 的抽象類,隨後可在服務內對其進行擴充套件
4.1、繫結服務的具體步驟:
4.1.1、擴充套件 Binder 類
如果服務僅供本地應用使用,不需要跨程序工作,則可以實現自有 Binder 類,讓客戶端通過該類直接訪問服務中的公共方法。此方法只有在客戶端和服務位於同一應用和程序內這一最常見的情況下方才有效
以下是具體的設定方法:
-
在服務中建立一個可滿足下列任一要求的 Binder 例項:
- 包含客戶端可呼叫的公共方法
- 返回當前 Service 例項,其中包含客戶端可呼叫的公共方法
- 或返回由服務承載的其他類的例項,其中包含客戶端可呼叫的公共方法
-
從 onBind() 回撥方法返回此 Binder 例項
-
在客戶端中,從 onServiceConnected() 回撥方法接收 Binder,並使用提供的方法呼叫繫結服務
4.1.2、實現 ServiceConnection介面
重寫兩個回撥方法:
- onServiceConnected()
系統會呼叫該方法以傳遞服務的onBind() 方法返回的 IBinder- onServiceDisconnected()
Android 系統會在與服務的連線意外中斷時,例如當服務崩潰或被終止時呼叫該方法。當客戶端取消繫結時,系統不會呼叫該方法
4.1.3、呼叫 bindService(),傳遞 ServiceConnection 物件
4.1.4、當系統呼叫了 onServiceConnected() 的回撥方法時,就可以通過IBinder物件操作服務了
4.1.5、要斷開與服務的連線需呼叫 unbindService()方法。如果應用在客戶端仍處於繫結狀態時銷燬客戶端,會導致客戶端取消繫結,更好的做法是在客戶端與服務互動完成後立即取消繫結客戶端,這樣可以關閉空閒服務
示例程式碼:
public class MyBindService extends Service {
private IBinder myBinder;
private Random mGenerator;
private final String TAG = "MyBindService";
//Binder是實現了IBinder介面的一個空實現類
public class MyBinder extends Binder {
MyBindService getService() {
return MyBindService.this;
}
}
@Override
public void onCreate() {
Log.e(TAG, "onCreate");
myBinder = new MyBinder();
mGenerator = new Random();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind");
return myBinder;
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.e(TAG, "onRebind");
super.onRebind(intent);
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
public class BindServiceActivity extends AppCompatActivity {
private MyBindService mService;
private boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//onbinder之後接收到傳過來的IBinder物件
MyBindService.MyBinder binder = (MyBindService.MyBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
public void bindService(View view) {
Intent intent = new Intent(this, MyBindService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
public void unBindService(View view) {
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
public void getData(View view) {
if (mBound) {
Toast.makeText(this, "獲取到的隨機數:" + mService.getRandomNumber(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "服務未繫結", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
4.2、繫結服務的生命週期
繫結服務的生命週期在同時啟動服務的情況下比較特殊,想要終止服務,除了需要取消繫結服務外,還需要服務通過 stopSelf() 自行停止或其他元件呼叫 stopService()
其中,如果服務已啟動並接受繫結,則當系統呼叫了onUnbind() 方法,想要在客戶端下一次繫結到服務時呼叫 onRebind() 方法的話,則onUnbind() 方法需返回 true。onRebind() 返回空值,但客戶端仍可以在其 onServiceConnected() 回撥中接收到 IBinder物件
這裡寫圖片描述
4.3、繫結時機
- 如果只需要在 Activity 可見時與服務互動,則應在 onStart() 期間繫結,在 onStop() 期間取消繫結
- 如果希望 Activity 在後臺停止執行狀態下仍可接收響應,則可在 onCreate() 期間繫結,在 onDestroy() 期間取消繫結。這意味著 Activity 在其整個執行過程中(包括後臺執行期間)都需要使用此服務
- 通常情況下,切勿在 Activity 的 onResume() 和 onPause() 期間繫結和取消繫結,因為每一次生命週期轉換都會發生這些回撥,應該使發生在這些轉換期間的處理保持在最低水平。假設有兩個Activity需要繫結到同一服務,從Activity A跳轉到Activity B,這個過程中會依次執行A-onPause,B-onCreate,B-onStart,B-onResume,A-onStop。這樣系統會在A-onPause的時候銷燬服務,又在B-onResume的時候重建服務。當Activity B回退到Activity A時,會依次執行B-onPause,A-onRestart,A-onStart,A-onResume,B-onStop,B-onDestroy。此時,系統會在B-onPause時銷燬服務,又在A-onResume時重建服務。這樣就造成了多次的銷燬與重建,因此需要選定好繫結服務與取消繫結服務的時機
五、在前臺執行Service
前臺服務被認為是使用者主動意識到的一種服務,因此在記憶體不足時,系統也不會考慮將其終止。 前臺服務必須在狀態列提供通知,放在“正在進行”標題下方,這意味著除非服務停止或從前臺移除,否則不能清除通知
要請求讓服務運行於前臺,要呼叫 startForeground()方法,兩個引數分別是:唯一標識通知的int型別整數和Notification物件
修改MyService當中的play()方法
private void play() {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
mBuilder.setSmallIcon(R.drawable.bird);
mBuilder.setContentTitle("這是標題吧~葉應是葉");
mBuilder.setContentText("http://blog.csdn.net/new_one_object");
startForeground(1, mBuilder.build());
}
}
點選播放音樂後,狀態列就出現了一個通知
這裡寫圖片描述
當中,提供給 startForeground() 的整型引數不得為 0。要從前臺移除服務,需呼叫 stopForeground()方法,此方法不會停止服務。 但是,如果前臺服務被停止,則通知也會被移除
六、Service的生命週期
服務生命週期從建立到銷燬可以遵循兩條不同的路徑:
- 啟動服務
該服務在其他元件呼叫 startService() 時建立,然後無限期執行,必須通過呼叫 stopSelf() 來自行停止執行或通過其他元件呼叫 stopService() 來停止服務。服務停止後,系統會將其銷燬 - 繫結服務
服務在另一個元件(客戶端)呼叫 bindService() 時建立。然後,客戶端通過 IBinder 介面與服務進行通訊。客戶端可以通過呼叫 unbindService() 關閉連線。多個客戶端可以繫結到相同服務,而且當所有繫結全部取消後,系統即會銷燬該服務(服務不必自行停止執行)
這兩條路徑並非完全獨立。也就是說,可以繫結到已經使用 startService() 啟動的服務。例如,可以通過使用 Intent(標識要播放的音樂)呼叫 startService() 來啟動後臺音樂服務。隨後,可能在使用者需要稍加控制播放器或獲取有關當前播放歌曲的資訊時,Activity 可以通過呼叫 bindService() 繫結到服務。在這種情況下,除非所有客戶端均取消繫結,否則 stopService() 或 stopSelf() 不會實際停止服務
這裡提供示例程式碼下載:Android Service使用詳解