目錄
- 思維導圖
- 概述
- 啟動方式及生命周期
- startService 方式啟動
- bindService 方式啟動
- 混合啟動
- 疑問
- startCommand 返回值含義
- bindService 第三個參數(shù)含義
- startForegroundService
- FOREGROUND_SERVICE 權限申請
- onStartCommand 必須調(diào)用 startForeground 構造通知欄
- 非綁定服務
- 常見問題匯總
- 如何保證 Service 不被殺死
- 參考
思維導圖
概述
Service 是一種可以在后臺長時間運行而沒有用戶界面的應用組件阀湿。它有兩種啟動方式磨德,第一種方式是 startService 啟動服務杨蛋,服務啟動后惫恼,會在后臺無限期運行,直到通過 stopService 或者 stopSelf 停止服務琳骡。另一種方式是 bindService 綁定服務锅论,組件與服務綁定在一起,服務的生命周期受組件影響楣号,如果組件被銷毀了最易,服務也就停止了。
Service 是運行在主線程中的炫狱,因此不能執(zhí)行耗時任務耘纱,如果想執(zhí)行耗時任務,可以使用 IntentService毕荐。
啟動方式及生命周期
startService 方式啟動
當應用組件通過 startService 方法來啟動 Service 時,Service 則會處于啟動狀態(tài)艳馒,一旦服務啟動憎亚,它就會在后臺無限期的運行,生命周期獨立于啟動它的組件弄慰,即使啟動它的組件已經(jīng)銷毀了也不受任何影響第美,由于啟動的服務長期運行在后臺,這會大量的消耗電量陆爽,因此什往,我們應該在任務執(zhí)行完之后調(diào)用 stopSelf 來停止服務,或者通過其他應用組件調(diào)用 stopServcie 來停止服務慌闭。
通過這種方式啟動 Service 生命周期是:
onCreate别威、onStartCommand、onDestory驴剔。
首次啟動服務的時候省古,系統(tǒng)會調(diào)用 onCreate 方法,多次啟動不會在調(diào)用 onCreate 方法丧失,只會調(diào)用 onStartCommand豺妓,onStartCommand 被調(diào)用后,服務就啟動起來了布讹,將會在后臺無限期的運行琳拭,直到通過 stopService 或者 stopSelf 方法來停止服務。當服務被銷毀時描验,將會回調(diào) onDestory 方法白嘁。
多次調(diào)用 startService(Intent) 會回調(diào) onStartCommand 方法,而多次調(diào)用 stopService 只有第一次會回調(diào) onDestory 方法挠乳。
bindService 方式啟動
即將啟動組件和服務綁定在一起权薯,前面講的通過 startService 方式啟動的服務是與組件相獨立的姑躲,即使啟動服務的組件被銷毀了,服務仍然在后臺運行不受干擾盟蚣,但是通過 bindService 方式綁定的服務就不一樣了黍析,它與綁定組件的生命周期是有關的。
多個組件可以綁定到同一個服務上屎开,如果只有一個組件綁定服務阐枣,當綁定的組件被銷毀時,服務也就會停止了奄抽。如果是多個組件綁定到一個服務上蔼两,當綁定到該服務的所有組件都被銷毀時,服務才會停止逞度。
通過這種方式啟動 Service 生命周期是:
onCreate额划、onBind、onUnbind档泽、onDestory
onBind():當其他組件想通過 bindService 與服務綁定時俊戳,系統(tǒng)將會回調(diào)這個方法,在實現(xiàn)中馆匿,必須返回一個 IBinder 接口抑胎,供客戶端和服務進行通信,必須實現(xiàn)此方法渐北,這個方法是 Service 的一個抽象方法阿逃,但是如果不允許綁定,返回 null 就好了赃蛛。
onUnbind:當所有與服務綁定的組件都解除綁定時恃锉,就會調(diào)用此方法。
多次 bindService 并不會回調(diào)任何方法焊虏,多次 unBindService 則會 Crash淡喜。
實例:
-
創(chuàng)建服務
其實就是在 onBind 方法中返回 IBinder 實例
public class MyBindService extends Service { private static final String TAG = "MyBindService"; @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate: "); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: "); return new MyBinder(); } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "onUnbind: "); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy: "); } public class MyBinder extends Binder{ public String getInfo(){ return "Info"; } } }
-
綁定服務
綁定服務需要提供一個 ServiceConnection 接口,在接口回調(diào)中獲取 Binder 對象诵闭,與服務進行通信炼团。
private MyBindService.MyBinder mMyBinder; //綁定/解除綁定 Service 回調(diào)接口 private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //綁定成功后回調(diào) //1.獲取 Binder 接口對象 mMyBinder = (MyBindService.MyBinder) service; //2.從服務獲取數(shù)據(jù) String content = mMyBinder.getInfo(); //3.界面提示 Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show(); } @Override public void onServiceDisconnected(ComponentName name) { //解除綁定后回調(diào) mMyBinder = null; } }; bindService(intent, mConnection, Context.BIND_AUTO_CREATE); unbindService(mConnection);
混合啟動
如果一個服務被啟動又被綁定,onCreate 方法只會執(zhí)行一次疏尿,startService 調(diào)用多少次瘟芝,onStartCommand 就會執(zhí)行多少次,調(diào)用 stopService 并不會回調(diào) onDestory褥琐,unBindService 可以锌俱。
疑問
onStartCommand 返回值
返回值的取值其實已經(jīng)定義在了 Service 基類中了,常用的有:
-
START_NOT_STICKY
如果系統(tǒng)在 onStartCommand 返回后終止服務敌呈,則除非有掛起 Intent 要傳遞贸宏,否則系統(tǒng)不會重建服務造寝。這是最安全的選項,可以避免在不必要時以及應用能夠輕松重啟所有未完成的作業(yè)時運行服務吭练。
-
START_STICKT
如果系統(tǒng)在 onStartCommand 返回后終止服務诫龙,則會重建服務并調(diào)用 onStartCommadn,但絕對不會重新傳遞最后一個 Intent鲫咽。相反签赃,除非有掛起 Intent 要啟動服務,否則系統(tǒng)會通過空 Intent 調(diào)用 onStartCommand分尸。這適用于不執(zhí)行命令锦聊、但無限期運行并等待作業(yè)的媒體播放器等。
-
START_REDELIVER_INTENT
如果系統(tǒng)在 onStartCommand 返回后終止服務箩绍,則會重建服務孔庭,并通過傳遞給服務等最后一個 Intent 調(diào)用 onStartCommand。任何掛起 Intent 均依次傳遞材蛛。這適用于主動執(zhí)行應該立即恢復的作業(yè)的服務史飞,例如下載文件。
bindService 參數(shù)
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
return mBase.bindService(service, conn, flags);
}
前兩個參數(shù)就不用多說了仰税,第三個參數(shù)一般傳 Context.BIND_AUTO_CREATE。
它的值是一些枚舉值抽诉,
startForegroundService
單獨拿出來說陨簇,逼格高吧。
回到正題迹淌,最開始使用這個方法時河绽,不是 Crash 就是 ANR,頭大唉窃!
首先使用前臺服務耙饰,必須申請 FOREGROUND_SERVICE 權限,這是普通權限纹份,未申請則會引發(fā) SecurityException苟跪。
ANR 的解決
使用前臺服務,必須提供一個通知欄蔓涧,不然五秒就會 ANR件已。
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 通知渠道的id
String id = "my_channel_01";
// 用戶可以看到的通知渠道的名字.
CharSequence name = "Demo";
// 用戶可以看到的通知渠道的描述
String description = "Desc";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
// 配置通知渠道的屬性
mChannel.setDescription(description);
// 設置通知出現(xiàn)時的閃燈(如果 android 設備支持的話)
mChannel.enableLights(true);
mChannel.setLightColor(Color.RED);
// 設置通知出現(xiàn)時的震動(如果 android 設備支持的話)
mChannel.enableVibration(true);
mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
mNotificationManager.createNotificationChannel(mChannel);
// 通知渠道的id
String CHANNEL_ID = "my_channel_01";
// Create a notification and set the notification channel.
Notification notification = new Notification.Builder(this, CHANNEL_ID)
.setContentTitle("New Message").setContentText("You've received new messages.")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build();
startForeground(1, notification);
return super.onStartCommand(intent, flags, startId);
}
準確的來說,應該是必須調(diào)用 startForeground 方法元暴。
既然我是寫在 onStartCommand 方法里面篷扩,就說明這是一個啟動服務非綁定服務,所以可以多次調(diào)用 startForegroundService 方法茉盏,調(diào)用一次 stopService 即可停止服務鉴未。
Crash 的解決
Crash 的報錯如下:
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord
這是由于我在 startForegroundService 之后就 stopService枢冤,在 Service 中并沒有構造通知欄導致的。
總結
使用前臺服務铜秆,有三點需要注意:
- 申請 FOREGROUND_SERVICE 權限淹真,它是普通權限
- 在 onStartCommand 中必須要調(diào)用 startForeground 構造一個通知欄,不然 ANR
- 前臺服務只能是啟動服務羽峰,不能是綁定服務
常見問題匯總
- 如何保證 Service 不被殺死
- 在 Service 的 onStartCommand 中返回 START_STICKY趟咆,該標志使得 Service 被殺死后嘗試再次啟動 Service
- 提高 Service 優(yōu)先級,比如設置成前臺服務
- 在 Activity 的 onDestory 發(fā)送廣播梅屉,在廣播接收器的 onReceiver 重啟 Service
參考
Android Service和IntentService知識點詳細總結
Context.startForegroundService() did not then call Service.startForeground值纱?