一、Service簡介
Service是Android程序中四大基礎(chǔ)組件之一睛榄,是在后臺運行的組件梅垄。
Service是Android中實現(xiàn)程序后臺運行的解決方案,它非常適用于去執(zhí)行那些不需要和用戶交互而且還要求長期運行的任務餐屎。Service默認并不會運行在子線程中檀葛,它也不運行在一個獨立的進程中,它同樣執(zhí)行在UI線程中腹缩,因此驻谆,不要在Service中執(zhí)行耗時的操作,除非你在Service中創(chuàng)建了子線程來完成耗時操作庆聘。
<br />
二胜臊、Service分類
按運行類型分類:
按使用方式分類:
<br />
三、Service生命周期
OnCreate()系統(tǒng)在service第一次創(chuàng)建時執(zhí)行此方法伙判,來執(zhí)行只運行一次的初始化工作象对。如果service已經(jīng)運行,這個方法不會被調(diào)用宴抚。
<br />
onStartCommand()每次客戶端調(diào)用startService()方法啟動該Service都會回調(diào)該方法(多次調(diào)用)勒魔。一旦這個方法執(zhí)行,service就啟動并且在后臺長期運行菇曲。通過調(diào)用stopSelf()或stopService()來停止服務冠绢。
<br />
OnBind()當組件調(diào)用bindService()想要綁定到service時(比如想要執(zhí)行進程間通訊)系統(tǒng)調(diào)用此方法(一次調(diào)用,一旦綁定后常潮,下次再調(diào)用bindService()不會回調(diào)該方法)弟胀。在你的實現(xiàn)中,你必須提供一個返回一個IBinder來以使客戶端能夠使用它與service通訊喊式,你必須總是實現(xiàn)這個方法孵户,但是如果你不允許綁定,那么你應返回null岔留。
<br />
OnUnbind()當前組件調(diào)用unbindService()夏哭,想要解除與service的綁定時系統(tǒng)調(diào)用此方法(一次調(diào)用,一旦解除綁定后献联,下次再調(diào)用unbindService()會拋出異常)竖配。
<br />
OnDestory()系統(tǒng)在service不再被使用并要銷毀時調(diào)用此方法(一次調(diào)用).service應在此方法中釋放資源,比如線程里逆,已注冊的偵聽器进胯,接收器等等.這是service收到的最后一個調(diào)用。
<br />
彩蛋/(●'?'●)
下面重點關(guān)注下onStartCommand(Intent intent, int flags, int startId)方法运悲。
其中參數(shù)flags默認情況下是0龄减,對應的常量名為START_STICKY_COMPATIBILITY。startId是一個唯一的整型班眯,用于表示此次Client執(zhí)行startService(...)的請求請求標識希停,在多次startService(...)的情況下烁巫,呈現(xiàn)0,1,2....遞增。另外宠能,此函數(shù)具有一個int型的返回值亚隙,具體的可選值及含義如下:
START_NOT_STICKY:當Service因為內(nèi)存不足而被系統(tǒng)kill后,接下來未來的某個時間內(nèi)违崇,即使系統(tǒng)內(nèi)存足夠可用阿弃,系統(tǒng)也不會嘗試重新創(chuàng)建此Service。除非程序中Client明確再次調(diào)用startService(...)啟動此Service羞延。
START_STICKY:當Service因為內(nèi)存不足而被系統(tǒng)kill后渣淳,接下來未來的某個時間內(nèi),當系統(tǒng)內(nèi)存足夠可用的情況下伴箩,系統(tǒng)將會嘗試重新創(chuàng)建此Service入愧,一旦創(chuàng)建成功后將回調(diào)onStartCommand(...)方法,但其中的Intent將是null嗤谚,pendingintent除外棺蛛。
START_REDELIVER_INTENT:與START_STICKY唯一不同的是,回調(diào)onStartCommand(...)方法時巩步,其中的Intent將是非空旁赊,將是最后一次調(diào)用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因為內(nèi)存不足而被系統(tǒng)kill后“一定要非常注意声离,因為此函數(shù)的返回值設(shè)定只是針對此種情況才有意義的,換言之瘫怜,當認為的kill掉Service進程,此函數(shù)返回值無論怎么設(shè)定本刽,接下來未來的某個時間內(nèi)鲸湃,即使系統(tǒng)內(nèi)存足夠可用,Service也不會重啟子寓。
<br />
小米手機針對此處做了變更:
另外暗挑,需要注意的是,小米手機針對此處做了一定的修改斜友。在“自啟動管理”中有一個自啟動應用列表炸裆,默認情況下,只有少應用(如微信鲜屏、QQ烹看、YY国拇、360等)默認是可以自啟動的,其他應用默認都是禁止的惯殊。用戶可以手動添加自啟動應用酱吝,添加后的應用中如果Started Service onStartCommand(...)回調(diào)返回值是START_STICKY或START_REDELIVER_INTENT,當用戶在小米手機上長按Home鍵結(jié)束App后土思,接下來未來的某個時間內(nèi)务热,當系統(tǒng)內(nèi)存足夠可用時,Service依然可以按照上述規(guī)定重啟己儒。當然崎岂,如果用戶在 設(shè)置 >> 應用 >> 強制kill掉App進程,此時Service是不會重啟的闪湾。
注:以上實驗結(jié)論基于小米2S親測该镣。
下面介紹三種不同情況下Service的生命周期情況。
-
1-startService-stopService)1.startService / stopService
生命周期順序:onCreate->onStartCommand->onDestroy
如果一個Service被某個Activity 調(diào)用 Context.startService方法啟動响谓,那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service损合,該Service都在后臺運行,直到被調(diào)用stopService娘纷,或自身的stopSelf方法嫁审。當然如果系統(tǒng)資源不足,android系統(tǒng)也可能結(jié)束服務赖晶,還有一種方法可以關(guān)閉服務律适,在設(shè)置中,通過應用->找到自己應用->停止遏插。
注意點:
①第一次 startService 會觸發(fā) onCreate 和 onStartCommand捂贿,以后在服務運行過程中,每次 startService 都只會觸發(fā) onStartCommand
②不論 startService 多少次胳嘲,stopService 一次就會停止服務 -
2-bindService-unbindService)2.bindService / unbindService
生命周期順序:onCreate->onBind->onUnBind->onDestroy
如果一個Service在某個Activity中被調(diào)用bindService方法啟動厂僧,不論bindService被調(diào)用幾次,Service的onCreate方法只會執(zhí)行一次了牛,同時onStartCommand方法始終不會調(diào)用颜屠。
當建立連接后,Service會一直運行鹰祸,除非調(diào)用unbindService來接觸綁定甫窟、斷開連接或調(diào)用該Service的Context不存在了(如Activity被Finish——即通過bindService啟動的Service的生命周期依附于啟動它的Context),系統(tǒng)在這時會自動停止該Service蛙婴。
注意點:
第一次 bindService 會觸發(fā) onCreate 和 onBind粗井,以后在服務運行過程中,每次 bindService 都不會觸發(fā)任何回調(diào) -
3.混合型(上面兩種方式的交互)
當一個Service在被啟動(startService)的同時又被綁定(bindService),該Service將會一直在后臺運行浇衬,并且不管調(diào)用幾次懒构,onCreate方法始終只會調(diào)用一次,onStartCommand的調(diào)用次數(shù)與startService調(diào)用的次數(shù)一致(使用bindService方法不會調(diào)用onStartCommand)径玖。同時痴脾,調(diào)用unBindService將不會停止Service,必須調(diào)用stopService或Service自身的stopSelf來停止服務梳星。
在什么情況下使用 startService 或 bindService 或 同時使用startService 和 bindService赞赖?
①如果你只是想要啟動一個后臺服務長期進行某項任務那么使用 startService 便可以了。
②如果你想要與正在運行的 Service 取得聯(lián)系冤灾,那么有兩種方法前域,一種是使用 broadcast ,另外是使用 bindService 韵吨,前者的缺點是如果交流較為頻繁匿垄,容易造成性能上的問題,并且 BroadcastReceiver 本身執(zhí)行代碼的時間是很短的(也許執(zhí)行到一半归粉,后面的代碼便不會執(zhí)行)椿疗,而后者則沒有這些問題,因此我們肯定選擇使用 bindService(這個時候你便同時在使用 startService 和 bindService 了糠悼,這在 Activity 中更新 Service 的某些運行狀態(tài)是相當有用的)届榄。
③如果你的服務只是公開一個遠程接口,供連接上的客服端(android 的 Service 是C/S架構(gòu))遠程調(diào)用執(zhí)行方法倔喂。這個時候你可以不讓服務一開始就運行铝条,而只用 bindService ,這樣在第一次 bindService 的時候才會創(chuàng)建服務的實例運行它席噩,這會節(jié)約很多系統(tǒng)資源班缰,特別是如果你的服務是Remote Service,那么該效果會越明顯(當然在 Service 創(chuàng)建的時候會花去一定時間悼枢,你應當注意到這點)埠忘。這部分主要應用是AIDL,可以看這篇文章萧芙。
<br />
四给梅、Service的幾種典型使用實例
-
1.不可交互的后臺服務
不可交互的后臺服務即是普通的Service,通過startService()方式開啟双揪。Service的生命周期很簡單,分別為onCreate包帚、onStartCommand渔期、onDestroy這三個。
創(chuàng)建服務類:
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) {
//執(zhí)行耗時操作
mThread = new Thread() {
@Override
public void run() {
try {
while (true) {
//等待停止線程
if (this.isInterrupted()) {
throw new InterruptedException();
}
//耗時操作。
System.out.println("執(zhí)行耗時操作");
}
} 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類拘哨,只需要在前臺,調(diào)用startService()方法信峻,就會啟動耗時操作倦青。
注意:
①不運行在一個獨立的進程中,它同樣執(zhí)行在UI線程中盹舞,因此产镐,在Service中創(chuàng)建了子線程來完成耗時操作。
②當Service關(guān)閉后踢步,如果在onDestory()方法中不關(guān)閉線程癣亚,你會發(fā)現(xiàn)我們的子線程進行的耗時操作是一直存在的,此時關(guān)閉該子線程的方法需要直接關(guān)閉該應用程序获印。因此述雾,在onDestory()方法中要進行必要的清理工作。
-
2.可交互的后臺服務
可交互的后臺服務是指前臺頁面可以調(diào)用后臺服務的方法兼丰,通過bindService()方式開啟玻孟。Service的生命周期很簡單,分別為onCreate鳍征、onBind黍翎、onUnBind、onDestroy這四個蟆技⊥婷簦可交互的后臺服務實現(xiàn)步驟是和不可交互的后臺服務實現(xiàn)步驟是一樣的,區(qū)別在于啟動的方式和獲得Service的代理對象质礼。
創(chuàng)建服務類和普通Service不同在于這里返回一個代理對象旺聚,返回給前臺進行獲取,即前臺可以獲取該代理對象執(zhí)行后臺服務的方法
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();
}
}
前臺調(diào)用通過以下方式綁定服務:
bindService(mIntent,con,BIND_AUTO_CREATE);
其中第二個參數(shù):
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的返回值砰粹。如此便可以調(diào)用后臺服務類的方法,實現(xiàn)交互造挽。
當調(diào)用unbindService()停止服務碱璃,同時要在onDestory()方法中做好清理工作。
注意:通過bindService啟動的Service的生命周期依附于啟動它的Context饭入。因此當前臺調(diào)用bindService的Context銷毀后嵌器,那么服務會自動停止。
-
3.混合型后臺服務
將上面兩種啟動方式結(jié)合起來就是混合性交互的后臺服務了谐丢,即可以單獨運行后臺服務爽航,也可以運行后臺服務中提供的方法蚓让,其完整的生命周期是:onCreate->onStartCommand->onBind->onUnBind->onDestroy - 4.前臺服務
所謂前臺服務只不是通過一定的方式將服務所在的進程級別提升了。前臺服務會一直有一個正在運行的圖標在系統(tǒng)的狀態(tài)欄顯示讥珍,非常類似于通知的效果历极。
由于后臺服務優(yōu)先級相對比較低,當系統(tǒng)出現(xiàn)內(nèi)存不足的情況下衷佃,它就有可能會被回收掉趟卸,所以前臺服務就是來彌補這個缺點的,它可以一直保持運行狀態(tài)而不被系統(tǒng)回收氏义。
創(chuàng)建服務類前臺服務創(chuàng)建很簡單锄列,其實就在Service的基礎(chǔ)上創(chuàng)建一個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() {
//創(chuàng)建通知
Notification.Builder mBuilder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("2017-2-27")
.setContentText("您有一條未讀短信...");
//創(chuàng)建點跳轉(zhuǎn)的Intent(這個跳轉(zhuǎn)是跳轉(zhuǎn)到通知詳情頁)
Intent intent = new Intent(this,NotificationShow.class);
//創(chuàng)建通知詳情頁的棧
TaskStackBuilder stackBulider = TaskStackBuilder.create(this);
//為其添加父棧 當從通知詳情頁回退時右蕊,將退到添加的父棧中
stackBulider.addParentStack(NotificationShow.class);
PendingIntent pendingIntent = stackBulider.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
//設(shè)置跳轉(zhuǎn)Intent到通知中
mBuilder.setContentIntent(pendingIntent);
//獲取通知服務
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//構(gòu)建通知
Notification notification = mBuilder.build();
//顯示通知
nm.notify(0,notification);
//啟動前臺服務
startForeground(0,notification);
}
}
啟動前臺服務
startService(new Intent(this, ForeService.class));
關(guān)于TaskStackBuilder 這一段,可能不是看的很明白吮螺,下面詳細介紹饶囚。
TaskStackBuilder在Notification通知欄中的使用
首先是用一般的PendingIntent來進行跳轉(zhuǎn)
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來跳轉(zhuǎn)到NotificationShow。
在運行效果上來看鸠补,首先發(fā)送了一條Notification到通知欄上萝风,然后這時,退出程序紫岩,即MainActivity已經(jīng)不存在了规惰,回到home主菜單,看到Notification仍然存在泉蝌,當然歇万,我們還沒有點擊或者cancel它,現(xiàn)在去點擊Notification勋陪,跳轉(zhuǎn)到NotificationShow界面贪磺,然后我們按下Back鍵,發(fā)現(xiàn)直接回到home菜單了∽缬蓿現(xiàn)在大多數(shù)android應用都是在通知欄中如果有Notification通知的話寒锚,點擊它,然后會直接跳轉(zhuǎn)到對應的應用程序的某個界面违孝,這時如果回退刹前,即按下Back鍵,會返回到該應用程序的主界面雌桑,而不是系統(tǒng)的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)創(chuàng)建一個stackBuilder實例轧飞,接下來addParentStack();
關(guān)于這個方法衅鹿,我們查一下官方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
這句話意思是:為跳轉(zhuǎn)后的activity添加一個父activity撒踪,在activity中的manifest中添加parentActivityName即可过咬。
那么我們就在manifest文件中添加這個屬性
<activity
android:name="com.lvr.service.NotificationShow"
android:parentActivityName=".MainActivity" >
</activity>
這里我讓它的parentActivity為MainActivity,也就是說在NotificationShow這個界面點擊回退時制妄,會跳轉(zhuǎn)到MainActivity這個界面掸绞,而不是像上面一樣直接回到了home菜單。
<br />
注意:通過 stopForeground()方法可以取消通知耕捞,即將前臺服務降為后臺服務衔掸。此時服務依然沒有停止。通過stopService()可以把前臺服務停止俺抽。