Service之三种服务方式
(一)StartService
運(yùn)行Service的方法之一。任何繼承于android.content.Context的Android組件(component)都可以使用一個Intent(android.content.Intent)來開啟一個Service。Intent里面可以以類對象(Class<?>)或者action進(jìn)行構(gòu)造,使用action構(gòu)造對于初學(xué)者來說不太直觀,而且action構(gòu)造的適用范圍比類對象構(gòu)造使用范圍更廣,所以action構(gòu)造的方法將在本系列文章后面的內(nèi)容進(jìn)行介紹,現(xiàn)在就都使用類對象進(jìn)行構(gòu)造。
如果Service并沒有運(yùn)行,則新建一個Service(這里涉及到Service指派Task的問題將在以后的內(nèi)容中介紹),并運(yùn)行之,相繼Service的回調(diào)onCreate(),onStart()/onStartCommand()(onStartCommand()是在Android2.0及之后SDK加入推薦使用的,用來代替老版的onStart()方法)被調(diào)用;如果Service已經(jīng)在運(yùn)行了,則只有回調(diào)onStart()/onStartCommand()被調(diào)用。
參看Android官方文檔可以發(fā)現(xiàn),onStart()和onStartCommand()均會傳入一個Intent對象參數(shù),這個Intent對象就是使用startService()時(shí)傳入的同一個Intent對象(同一個表示使用“==”比較返回true)。因?yàn)镮ntent對象可以攜帶Extra數(shù)據(jù),所以啟用Service的組件可以隨意的向Service傳遞Extra數(shù)據(jù)(使用putXXExtra()/putExtra()等方法,最常見的就是使用Java基本類型、String對象或者封裝在Bundle對象中作為Extra數(shù)據(jù),關(guān)于Extra數(shù)據(jù)的類型限制請自行參見文檔)。當(dāng)組件要傳數(shù)據(jù)到Service時(shí),只需要調(diào)用startService()并傳入相應(yīng)的攜帶了Extra數(shù)據(jù)的Intent對象,并在Service的onStart()/onStartCommand()中接收取出Extra數(shù)據(jù),而不用理會Service是否已經(jīng)運(yùn)行,數(shù)據(jù)始終能夠到達(dá)Service并進(jìn)行處理。這樣,就可以完成單向的IPC(進(jìn)程間通信)功能(組件->Service)。
當(dāng)不想再讓Service運(yùn)行的時(shí)候,只需要(任一)組件調(diào)用stopService()并傳入相應(yīng)的Intent對象,如果Service正在運(yùn)行,則會停止,如果Service沒有運(yùn)行,則系統(tǒng)會自動當(dāng)什么事也沒發(fā)生。如果Service自己不想再運(yùn)行,可以在Service里使用stopSelf()自殺,可以看出,如果可以運(yùn)行到自殺代碼的話,那么Service肯定在運(yùn)行。
onStart()/onStartCommand()都會傳入?yún)?shù)startId:int,用來標(biāo)識Service的啟用號。每次startService()系統(tǒng)會自動為開啟的Service產(chǎn)生一個不同的startId,之前賦予它的startId(如果有)將會被覆蓋,并且這個新產(chǎn)生的startId會成為這個Service的新的startId,無論Service是否正在運(yùn)行。
考慮如下情況,當(dāng)多個組件啟用了同一個Service,Service提供互斥的服務(wù)(使用synchronized關(guān)鍵字),且要保證在Service把所有工作完成之前不能自殺,這個時(shí)候,startId就相當(dāng)有用了,在Service onStart()/onStartCommand()時(shí)把startId保存起來,因?yàn)榛コ獾氖褂梅?wù),則Service是按順序提供服務(wù)的,則Service自殺的時(shí)候只用檢查當(dāng)前Service的startId與保存的這個startId是否相同,不同則說明Service之后還有任務(wù),不能自殺,相同則說明正在運(yùn)行的任務(wù)已經(jīng)是最后一個任務(wù)了,運(yùn)行完后就可以自殺(使用stopSelf(int startId)方法)。
啟動服務(wù):
Intentintent=newIntent(getApplicationContext(),MyService.class);
startService(intent);
Service中代碼:
@Override
publicvoidonCreate(){
}
@Override
publicintonStartCommand(Intentintent,intflags,intstartId){
//接受傳遞過來的intent的數(shù)據(jù)等
returnSTART_STICKY;
}
@Override
publicvoidonDestroy(){
}
(二)bindService
綁定(bind)Service是開啟Service的另一種方法,而且綁定Service幾乎可以被認(rèn)為是專門為IPC(進(jìn)程間交互)準(zhǔn)備的。綁定Serivce是通過Context.bindService()方法實(shí)現(xiàn)的,bindService和startService有一定的區(qū)別,首先就反應(yīng)在生命周期上。bindService不會回調(diào)onStart()/onStartCommand()方法,而會回調(diào)onBind()方法;Service要停止綁定則需要要調(diào)用unbindService()方法,而不用stopService()或者stopSelf()方法。
/**
* bindService
*
* @param service
* 用顯示的組件名(Class<?>方式)或者邏輯描述(action等)的Service的Intent
* @param conn
* 在Service開啟或停止時(shí)接收信息的組件
* @param flags
* 綁定選項(xiàng),可以是0,BIND_AUTO_CREATE,BIND_DEBUG_UNBIND,BIND_NOT_FOREGROUND,BIND_ABOVE_CLIENT,BIND_ALLOW_OOM_MANAGEMENT或者BIND_WAIVE_PRIORITY
* @return 綁定成功為true,否則為false
*/
public abstract boolean bindService(Intent service, ServiceConnection conn,int flags);
ServiceConnection可以監(jiān)聽服務(wù)的狀態(tài),在進(jìn)行服務(wù)綁定的時(shí),其標(biāo)志位可以為以下幾種(這里列出3種):
1).Context.BIND_AUTO_CREATE
說明:表示收到綁定請求的時(shí)候,如果服務(wù)尚未創(chuàng)建,則即刻創(chuàng)建,在系統(tǒng)內(nèi)存不足需要先摧毀優(yōu)先級組件來釋放內(nèi)存,且只有駐留該服務(wù)的進(jìn)程成為被摧毀對象時(shí),服務(wù)才被摧毀
2).Context.BIND_DEBUG_UNBIND
說明:通常用于調(diào)試場景中判斷綁定的服務(wù)是否正確,但容易引起內(nèi)存泄漏,因此非調(diào)試目的的時(shí)候不建議使用
3).Context.BIND_NOT_FOREGROUND
說明:表示系統(tǒng)將阻止駐留該服務(wù)的進(jìn)程具有前臺優(yōu)先級,僅在后臺運(yùn)行,該標(biāo)志位位于Froyo中引入。
Service的綁定方法bindService()中除了用了Service的Intent外,還使用到了ServiceConnection對象,這個對象除了可以為Service綁定者(caller)回調(diào)方法,還是解綁定(unbind)時(shí)需要提供的參數(shù)。bindService()方法中最后一個參數(shù)flag則是表明綁定Service時(shí)的一些設(shè)置,一般情況下可以直接使用0,有關(guān)這個問題將在本系列文章以后的內(nèi)容中介紹?! ndroid.content.ServiceConnection是一個接口,實(shí)現(xiàn)(implementate)這個接口有2個方法需要重寫(Override)。一個是當(dāng)Service成功綁定后會被回調(diào)的onServiceConnected()方法,另一個是當(dāng)Service解綁定或者Service被關(guān)閉時(shí)被回調(diào)的onServiceDisconnected()。前者(onServiceConnected()方法)會傳入一個IBinder對象參數(shù),這個IBinder對象就是在Service的生命周期回調(diào)方法的onBind()方法中的返回值,它對Service的綁定式IPC起到非常重要的作用。
以下代碼是bindService()及unbindService()的慣常用法,代碼如下:
booleanmIsBound=false;
/**綁定服務(wù)*/
publicvoiddoBindService(){
bindService(newIntent(MainActivity.this,LocalService.class),mConnection,Context.BIND_AUTO_CREATE);
mIsBound=true;
}
/**解除綁定服務(wù)*/
publicvoiddoUnbindService(){
if(mIsBound){
//Detachourexistingconnection.
unbindService(mConnection);
mIsBound=false;
}
}
privateServiceConnectionmConnection=newServiceConnection(){
@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
mBoundService=((LocalService.LocalBinder)service).getService();
Toast.makeText(MainActivity.this,"服務(wù)連接",Toast.LENGTH_SHORT)
.show();
}
@Override
publicvoidonServiceDisconnected(ComponentNamename){
mBoundService=null;
Toast.makeText(MainActivity.this,"服務(wù)未連接",Toast.LENGTH_SHORT)
.show();
}
};
服務(wù)類中:
@Override
publicvoidonCreate(){
}
/**綁定的IBinder*/
privatefinalIBindermBinder=newLocalBinder();
publicclassLocalBinderextendsBinder{
publicLocalServicegetService(){
returnLocalService.this;
}
}
@Override
publicIBinderonBind(Intentintent){
returnmBinder;
}
@Override
publicbooleanonUnbind(Intentintent){
//TODOAuto-generatedmethodstub
returnsuper.onUnbind(intent);
}
http://t.zoukankan.com/需要注意的是,onServiceDisconnected()方法在Service被顯示的(explicitly)unbind或者被停止時(shí)都會被回調(diào)。比如,當(dāng)Android系統(tǒng)資源(主要是RAM)嚴(yán)重不足時(shí),Service是很有可能被結(jié)束(kill)掉的,如果被kill掉,則onServiceDisconnected()方法會被回調(diào),但這個時(shí)候Service是沒有走完所有的生命周期的(比如不會回調(diào)onDestroy()方法)。當(dāng)然,無論Service的開啟是使用bind還是start,一旦當(dāng)系統(tǒng)資源恢復(fù)之后,這些被kill掉的Service會以可能的最短的時(shí)間內(nèi)被系統(tǒng)自動恢復(fù)(重新進(jìn)行新的生命周期,從回調(diào)onCreate()方法開始)。
(三)ForegroundService
Foreground Service(意譯為前臺服務(wù))并不完全像其意譯的意思那樣是工作在前臺的Service,因?yàn)镾ervice實(shí)際上始終是工作在后臺的。由于Service工作在后臺的原因,使用者并不知道它在運(yùn)行,有時(shí)候開發(fā)者需要使用者知道某個Service在運(yùn)行時(shí),就需要設(shè)計(jì)一種方案來解決這個問題,F(xiàn)oreground Service就出現(xiàn)了。Foreground Service說簡單點(diǎn)其實(shí)就是在Service開啟的時(shí)候使用通知(Notification),這也是Android官方推薦的方式,或者一些其它的方式(甚至可以是Activity,但使用Activity是基本沒有必要的)來告知用戶這個Service正在運(yùn)行。
在程序開啟了Service,則使用一個“正在運(yùn)行”的通知表明服務(wù)正在運(yùn)行就可以了,也就是在Service的onCreate()回調(diào)或者onStart()/onStartCommand()回調(diào)中即可。雖然通知并不是一定需要的,或者說故意不提示用戶有服務(wù)正在運(yùn)行(稍稍流氓一點(diǎn)的程序就會這樣),但是某些應(yīng)用商場的應(yīng)用審核就把通知提示做為了審核項(xiàng)目的。為了在Service周期(Life Cycle)結(jié)束的時(shí)候通知也能自動消失,所以需要在Service的onDestroy()回調(diào)里面寫上取消通知的代碼。以上就是配合通知自己實(shí)現(xiàn)的Foreground Service了。
當(dāng)然,除了自己處理通知的方法外,Google在Android 2.0(SDK level 5)以上的SDK提供了一個直接而簡單的方法。直接使用Service.startForeground()和Service.stopForeground()進(jìn)行處理(注意,這兩個方法是Service類的)。下面看下Google提供的兩個接口:
/**
* 讓service成為Foreground Service,并且產(chǎn)生一個“正在運(yùn)行”
* 的通知。默認(rèn)情況下,service是后臺的,這意味著service在系統(tǒng)
* 回收內(nèi)存(比如在瀏覽器里顯示大圖片)的時(shí)候可以被毫無顧忌的
* kill掉。如果你比較在意這個service的掛掉,比如像后臺音樂播放
* 器這種突然掛了會影響用戶的情況,就可以使用Foreground
* Service來提示用戶。
*
* 參數(shù)
* id The identifier for this notification as per
* NotificationManager.notify(int, Notification).
* notification The Notification to be displayed.
*/
publicfinalvoid startForeground (int id, Notification notification)
/**
* 去掉service的foreground屬性,允許在低內(nèi)存時(shí)被kill掉
*
* Parameters
* removeNotification If true, the notification previouslyprovided to startForeground(int,Notification)
* will be removed. Otherwise it willremain until a later call removes
* it (or the service is destroyed).
*/
publicfinalvoid stopForeground (boolean removeNotification)
使用startForeground()之后,給出的Notification對象會發(fā)布,使用stopForeground()之后,通知會被撤銷,當(dāng)Service銷毀(比如stopService()被調(diào)用)之后,通知也會被撤銷。stopForeground()僅僅只是去掉service的foreground屬性,并不會讓service停止。
android官方描述如下:
Running a Service in the Foreground
A foreground service is a service that's considered to be something the
user is actively aware of and thus not a candidate for the system to
kill when low on memory. A foreground service must provide a
notification for the status bar, which is placed under
the "Ongoing" heading, which means that the notification cannot be
dismissed unless the service is either stopped or removed from the
foreground.
For example, a music player that plays music from a service should be
set to run in the foreground, because the user is explicitly aware of
its operation. The notification in the status bar might indicate the
current song and allow the user to launch an activity
to interact with the music player.
To request that your service run in the foreground, callstartForeground().
This method takes two parameters: an integer that uniquely identifies the notification and theNotificationfor
the status bar. For example:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION, notification);
To remove the service from the foreground, callstopForeground(). This method takes a boolean, indicating whether to remove the status bar notification as well. This method doesnotstop the service. However, if you stop the service while it's still running in the foreground, then the notification is also removed.
Note:The methodsstartForeground()andstopForeground()were introduced in Android 2.0 (API Level 5). In order to run your service in the foreground on older versions of the platform, you must use the previoussetForeground()method—see thestartForeground()documentation for information about how to provide backward compatibility.
For more information about notifications, seeCreating Status Bar Notifications.
我們只需要在onStartCommand里面調(diào)用 startForeground方法讓服務(wù)前臺運(yùn)行,然后再onDestroy里面調(diào)用stopForeground解除前臺運(yùn)行既可!
引申閱讀:
1.檢查Android后臺服務(wù)是否正在運(yùn)行?
通過系統(tǒng)級提供的方法ActivityManager.getRunningServices獲取到當(dāng)前正在運(yùn)行的服務(wù)
privatebooleanisServiceRunning(){
ActivityManagermanager=(ActivityManager)getSystemService(ACTIVITY_SERVICE);
for(RunningServiceInfoservice:manager.getRunningServices(Integer.MAX_VALUE)){
if("com.example.MyService".equals(service.service.getClassName())){
returntrue;
}
}
returnfalse;
}
2.Service和Thread的區(qū)別?
我們拿服務(wù)來進(jìn)行一個后臺長時(shí)間的動作,為了不阻塞線程,然而,Thread就可以達(dá)到這個效果,為什么我們不直接使用Thread去代替服務(wù)呢?
1). Thread:Thread 是程序執(zhí)行的最小單元,它是分配CPU的基本單位??梢杂?Thread 來執(zhí)行一些異步的操作。
2). Service:Service
是android的一種機(jī)制,當(dāng)它運(yùn)行的時(shí)候如果是Local Service,那么對應(yīng)的 Service 是運(yùn)行在主進(jìn)程的 main
線程上的。如:onCreate,onStart 這些函數(shù)在被系統(tǒng)調(diào)用的時(shí)候都是在主進(jìn)程的 main 線程上運(yùn)行的。如果是Remote
Service,那么對應(yīng)的 Service 則是運(yùn)行在獨(dú)立進(jìn)程的 main 線程上。因此請不要把 Service
理解成線程,它跟線程半毛錢的關(guān)系都沒有!
既然這樣,那么我們?yōu)槭裁匆?br />
Service 呢?其實(shí)這跟 android 的系統(tǒng)機(jī)制有關(guān),我們先拿 Thread 來說。Thread 的運(yùn)行是獨(dú)立于 Activity
的,也就是說當(dāng)一個 Activity 被 finish 之后,如果你沒有主動停止
Thread 或者 Thread 里的 run 方法沒有執(zhí)行完畢的話,Thread 也會一直執(zhí)行。因此這里會出現(xiàn)一個問題:當(dāng) Activity
被 finish 之后,你不再持有該 Thread 的引用。另一方面,你沒有辦法在不同的 Activity 中對同一 Thread 進(jìn)行控制。
舉個例子:如果你的
Thread 需要不停地隔一段時(shí)間就要連接服務(wù)器做某種同步的話,該 Thread 需要在 Activity
沒有start的時(shí)候也在運(yùn)行。這個時(shí)候當(dāng)你 start 一個 Activity 就沒有辦法在該 Activity 里面控制之前創(chuàng)建的
Thread。因此你便需要創(chuàng)建并啟動一個 Service ,在 Service 里面創(chuàng)建、運(yùn)行并控制該
Thread,這樣便解決了該問題(因?yàn)槿魏?br />
Activity 都可以控制同一 Service,而系統(tǒng)也只會創(chuàng)建一個對應(yīng) Service 的實(shí)例)。
因此你可以把 Service 想象成一種消息服務(wù),而你可以在任何有 Context
的地方調(diào)用
Context.startService、Context.stopService、Context.bindService,Context.unbindService,來控制它,你也可以在
Service 里注冊 BroadcastReceiver,在其他地方通過發(fā)送 broadcast 來控制它,當(dāng)然這些都是 Thread
做不到的。
最后感謝分享,轉(zhuǎn)載于:https://www.juwends.com/tech/android/android-service-1.html(1-3)
總結(jié)
以上是生活随笔為你收集整理的Service之三种服务方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何注销淘宝店铺会员卡(如何注销淘宝店)
- 下一篇: 头脑不清醒反应迟钝是怎么回事儿(头脑不清