一、Service簡介
Service是Android程序中四大基礎組件之一,是在后臺運行的組件。
Service是Android中實現程序后臺運行的解決方案,它非常適用于去執行那些不需要和用戶交互而且還要求長期運行的任務。Service默認并不會運行在子線程中,它也不運行在一個獨立的進程中,它同樣執行在UI線程中,因此,不要在Service中執行耗時的操作,除非你在Service中創建了子線程來完成耗時操作。
<br />
二、Service分類
按運行類型分類:
按使用方式分類:
<br />
三、Service生命周期
OnCreate()系統在service第一次創建時執行此方法,來執行只運行一次的初始化工作。如果service已經運行,這個方法不會被調用。
<br />
onStartCommand()每次客戶端調用startService()方法啟動該Service都會回調該方法(多次調用)。一旦這個方法執行,service就啟動并且在后臺長期運行。通過調用stopSelf()或stopService()來停止服務。
<br />
OnBind()當組件調用bindService()想要綁定到service時(比如想要執行進程間通訊)系統調用此方法(一次調用,一旦綁定后,下次再調用bindService()不會回調該方法)。在你的實現中,你必須提供一個返回一個IBinder來以使客戶端能夠使用它與service通訊,你必須總是實現這個方法,但是如果你不允許綁定,那么你應返回null。
<br />
OnUnbind()當前組件調用unbindService(),想要解除與service的綁定時系統調用此方法(一次調用,一旦解除綁定后,下次再調用unbindService()會拋出異常)。
<br />
OnDestory()系統在service不再被使用并要銷毀時調用此方法(一次調用).service應在此方法中釋放資源,比如線程,已注冊的偵聽器,接收器等等.這是service收到的最后一個調用。
<br />
彩蛋/(●'?'●)
下面重點關注下onStartCommand(Intent intent, int flags, int startId)方法。
其中參數flags默認情況下是0,對應的常量名為START_STICKY_COMPATIBILITY。startId是一個唯一的整型,用于表示此次Client執行startService(...)的請求請求標識,在多次startService(...)的情況下,呈現0,1,2....遞增。另外,此函數具有一個int型的返回值,具體的可選值及含義如下:
START_NOT_STICKY:當Service因為內存不足而被系統kill后,接下來未來的某個時間內,即使系統內存足夠可用,系統也不會嘗試重新創建此Service。除非程序中Client明確再次調用startService(...)啟動此Service。
START_STICKY:當Service因為內存不足而被系統kill后,接下來未來的某個時間內,當系統內存足夠可用的情況下,系統將會嘗試重新創建此Service,一旦創建成功后將回調onStartCommand(...)方法,但其中的Intent將是null,pendingintent除外。
START_REDELIVER_INTENT:與START_STICKY唯一不同的是,回調onStartCommand(...)方法時,其中的Intent將是非空,將是最后一次調用startService(...)中的intent。
START_STICKY_COMPATIBILITY:compatibility version of {@link #START_STICKY} that does not guarantee that {@link #onStartCommand} will be called again after being killed。此值一般不會使用,所以注意前面三種情形就好。
<br />
以上的描述中,”當Service因為內存不足而被系統kill后“一定要非常注意,因為此函數的返回值設定只是針對此種情況才有意義的,換言之,當認為的kill掉Service進程,此函數返回值無論怎么設定,接下來未來的某個時間內,即使系統內存足夠可用,Service也不會重啟。
<br />
小米手機針對此處做了變更:
另外,需要注意的是,小米手機針對此處做了一定的修改。在“自啟動管理”中有一個自啟動應用列表,默認情況下,只有少應用(如微信、QQ、YY、360等)默認是可以自啟動的,其他應用默認都是禁止的。用戶可以手動添加自啟動應用,添加后的應用中如果Started Service onStartCommand(...)回調返回值是START_STICKY或START_REDELIVER_INTENT,當用戶在小米手機上長按Home鍵結束App后,接下來未來的某個時間內,當系統內存足夠可用時,Service依然可以按照上述規定重啟。當然,如果用戶在 設置 >> 應用 >> 強制kill掉App進程,此時Service是不會重啟的。
注:以上實驗結論基于小米2S親測。
下面介紹三種不同情況下Service的生命周期情況。
-
1-startService-stopService)1.startService / stopService
生命周期順序:onCreate->onStartCommand->onDestroy
如果一個Service被某個Activity 調用 Context.startService方法啟動,那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在后臺運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務,還有一種方法可以關閉服務,在設置中,通過應用->找到自己應用->停止。
注意點:
①第一次 startService 會觸發 onCreate 和 onStartCommand,以后在服務運行過程中,每次 startService 都只會觸發 onStartCommand
②不論 startService 多少次,stopService 一次就會停止服務 -
2-bindService-unbindService)2.bindService / unbindService
生命周期順序:onCreate->onBind->onUnBind->onDestroy
如果一個Service在某個Activity中被調用bindService方法啟動,不論bindService被調用幾次,Service的onCreate方法只會執行一次,同時onStartCommand方法始終不會調用。
當建立連接后,Service會一直運行,除非調用unbindService來接觸綁定、斷開連接或調用該Service的Context不存在了(如Activity被Finish——即通過bindService啟動的Service的生命周期依附于啟動它的Context),系統在這時會自動停止該Service。
注意點:
第一次 bindService 會觸發 onCreate 和 onBind,以后在服務運行過程中,每次 bindService 都不會觸發任何回調 -
3.混合型(上面兩種方式的交互)
當一個Service在被啟動(startService)的同時又被綁定(bindService),該Service將會一直在后臺運行,并且不管調用幾次,onCreate方法始終只會調用一次,onStartCommand的調用次數與startService調用的次數一致(使用bindService方法不會調用onStartCommand)。同時,調用unBindService將不會停止Service,必須調用stopService或Service自身的stopSelf來停止服務。
在什么情況下使用 startService 或 bindService 或 同時使用startService 和 bindService?
①如果你只是想要啟動一個后臺服務長期進行某項任務那么使用 startService 便可以了。
②如果你想要與正在運行的 Service 取得聯系,那么有兩種方法,一種是使用 broadcast ,另外是使用 bindService ,前者的缺點是如果交流較為頻繁,容易造成性能上的問題,并且 BroadcastReceiver 本身執行代碼的時間是很短的(也許執行到一半,后面的代碼便不會執行),而后者則沒有這些問題,因此我們肯定選擇使用 bindService(這個時候你便同時在使用 startService 和 bindService 了,這在 Activity 中更新 Service 的某些運行狀態是相當有用的)。
③如果你的服務只是公開一個遠程接口,供連接上的客服端(android 的 Service 是C/S架構)遠程調用執行方法。這個時候你可以不讓服務一開始就運行,而只用 bindService ,這樣在第一次 bindService 的時候才會創建服務的實例運行它,這會節約很多系統資源,特別是如果你的服務是Remote Service,那么該效果會越明顯(當然在 Service 創建的時候會花去一定時間,你應當注意到這點)。這部分主要應用是AIDL,可以看這篇文章。
<br />
四、Service的幾種典型使用實例
-
1.不可交互的后臺服務
不可交互的后臺服務即是普通的Service,通過startService()方式開啟。Service的生命周期很簡單,分別為onCreate、onStartCommand、onDestroy這三個。
創建服務類:
public class BackService extends Service {
private Thread mThread;
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
System.out.println("onBind");
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//執行耗時操作
mThread = new Thread() {
@Override
public void run() {
try {
while (true) {
//等待停止線程
if (this.isInterrupted()) {
throw new InterruptedException();
}
//耗時操作。
System.out.println("執行耗時操作");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
mThread.start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
//停止線程
mThread.interrupt();
}
}
配置服務:
<service android:name=".BackService"></service>
如果想配置成遠程服務,加如下代碼:
android:process="remote"
配置好Service類,只需要在前臺,調用startService()方法,就會啟動耗時操作。
注意:
①不運行在一個獨立的進程中,它同樣執行在UI線程中,因此,在Service中創建了子線程來完成耗時操作。
②當Service關閉后,如果在onDestory()方法中不關閉線程,你會發現我們的子線程進行的耗時操作是一直存在的,此時關閉該子線程的方法需要直接關閉該應用程序。因此,在onDestory()方法中要進行必要的清理工作。
-
2.可交互的后臺服務
可交互的后臺服務是指前臺頁面可以調用后臺服務的方法,通過bindService()方式開啟。Service的生命周期很簡單,分別為onCreate、onBind、onUnBind、onDestroy這四個。可交互的后臺服務實現步驟是和不可交互的后臺服務實現步驟是一樣的,區別在于啟動的方式和獲得Service的代理對象。
創建服務類和普通Service不同在于這里返回一個代理對象,返回給前臺進行獲取,即前臺可以獲取該代理對象執行后臺服務的方法
public class BackService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回MyBinder對象
return new MyBinder();
}
//需要返回給前臺的Binder類
class MyBinder extends Binder {
public void showTip(){
System.out.println("我是來此服務的提示");
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
前臺調用通過以下方式綁定服務:
bindService(mIntent,con,BIND_AUTO_CREATE);
其中第二個參數:
private ServiceConnection con = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BackService.MyBinder myBinder = (BackService.MyBinder) service;
myBinder.showTip();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
當建立綁定后,onServiceConnected中的service便是Service類中onBind的返回值。如此便可以調用后臺服務類的方法,實現交互。
當調用unbindService()停止服務,同時要在onDestory()方法中做好清理工作。
注意:通過bindService啟動的Service的生命周期依附于啟動它的Context。因此當前臺調用bindService的Context銷毀后,那么服務會自動停止。
-
3.混合型后臺服務
將上面兩種啟動方式結合起來就是混合性交互的后臺服務了,即可以單獨運行后臺服務,也可以運行后臺服務中提供的方法,其完整的生命周期是:onCreate->onStartCommand->onBind->onUnBind->onDestroy - 4.前臺服務
所謂前臺服務只不是通過一定的方式將服務所在的進程級別提升了。前臺服務會一直有一個正在運行的圖標在系統的狀態欄顯示,非常類似于通知的效果。
由于后臺服務優先級相對比較低,當系統出現內存不足的情況下,它就有可能會被回收掉,所以前臺服務就是來彌補這個缺點的,它可以一直保持運行狀態而不被系統回收。
創建服務類前臺服務創建很簡單,其實就在Service的基礎上創建一個Notification,然后使用Service的startForeground()方法即可啟動為前臺服務。
public class ForeService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
beginForeService();
}
private void beginForeService() {
//創建通知
Notification.Builder mBuilder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("2017-2-27")
.setContentText("您有一條未讀短信...");
//創建點跳轉的Intent(這個跳轉是跳轉到通知詳情頁)
Intent intent = new Intent(this,NotificationShow.class);
//創建通知詳情頁的棧
TaskStackBuilder stackBulider = TaskStackBuilder.create(this);
//為其添加父棧 當從通知詳情頁回退時,將退到添加的父棧中
stackBulider.addParentStack(NotificationShow.class);
PendingIntent pendingIntent = stackBulider.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
//設置跳轉Intent到通知中
mBuilder.setContentIntent(pendingIntent);
//獲取通知服務
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//構建通知
Notification notification = mBuilder.build();
//顯示通知
nm.notify(0,notification);
//啟動前臺服務
startForeground(0,notification);
}
}
啟動前臺服務
startService(new Intent(this, ForeService.class));
關于TaskStackBuilder 這一段,可能不是看的很明白,下面詳細介紹。
TaskStackBuilder在Notification通知欄中的使用
首先是用一般的PendingIntent來進行跳轉
mBuilder = new NotificationCompat.Builder(this).setContent(view)
.setSmallIcon(R.drawable.icon).setTicker("新資訊")
.setWhen(System.currentTimeMillis())
.setOngoing(false)
.setAutoCancel(true);
Intent intent = new Intent(this, NotificationShow.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);
這里是直接用PendingIntent來跳轉到NotificationShow。
在運行效果上來看,首先發送了一條Notification到通知欄上,然后這時,退出程序,即MainActivity已經不存在了,回到home主菜單,看到Notification仍然存在,當然,我們還沒有點擊或者cancel它,現在去點擊Notification,跳轉到NotificationShow界面,然后我們按下Back鍵,發現直接回到home菜單了。現在大多數android應用都是在通知欄中如果有Notification通知的話,點擊它,然后會直接跳轉到對應的應用程序的某個界面,這時如果回退,即按下Back鍵,會返回到該應用程序的主界面,而不是系統的home菜單。所以用上面這種PendingIntent的做法達不到目的。這里我們使用TaskStackBuilder來做。
mBuilder = new NotificationCompat.Builder(this).setContent(view)
.setSmallIcon(R.drawable.icon).setTicker("新資訊")
.setWhen(System.currentTimeMillis())
.setOngoing(false)
.setAutoCancel(true);
Intent intent = new Intent(this, NotificationShow.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(NotificationShow.class);
stackBuilder.addNextIntent(intent);
PendingIntent pendingIntent = stackBuilder.getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT);
// PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
// intent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);
顯示用TaskStackBuilder.create(this)創建一個stackBuilder實例,接下來addParentStack();
關于這個方法,我們查一下官方API文檔:
Add the activity parent chain as specified by the parentActivityName attribute of the activity (or activity-alias) element in the application’s manifest to the task stack builder
這句話意思是:為跳轉后的activity添加一個父activity,在activity中的manifest中添加parentActivityName即可。
那么我們就在manifest文件中添加這個屬性
<activity
android:name="com.lvr.service.NotificationShow"
android:parentActivityName=".MainActivity" >
</activity>
這里我讓它的parentActivity為MainActivity,也就是說在NotificationShow這個界面點擊回退時,會跳轉到MainActivity這個界面,而不是像上面一樣直接回到了home菜單。
<br />
注意:通過 stopForeground()方法可以取消通知,即將前臺服務降為后臺服務。此時服務依然沒有停止。通過stopService()可以把前臺服務停止。