混合使用startService,bindService,以及tartForegroud方法使用總結
一.上次的兩個問題:
1. 在BindService為什麼不呼叫onServiceDisConnection()
類ServiceConnection中的onServiceDisconnected()方法在正常情況下是不被呼叫的,它的呼叫時機是當Service服務被異外銷燬時,例如記憶體的資源不足時這個方法才被自動呼叫。
Android系統在同service的連線意外丟失時呼叫這個.比如當service崩潰了或被強殺了.當客戶端解除繫結時,這個方法不會被呼叫.
broadcastReceiver只能通過startService啟動Service,因為廣播本身生命週期很短,bind的話沒有意義
2. bindService(Intent,ServiceConnection物件,常量)
第一個引數:Intent指示對應的Service物件
第二個引數:實現了 ServiceConnection介面的物件
第三個引數:Flags
在進行服務繫結時,其標誌位可以為BIND_AUTO_CREATE、BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND等。其中BIND_AUTO_CREATE表示當收到繫結請求時,如果服務尚未建立,則即刻建立,在系統記憶體不足,需要先銷燬優先順序元件來釋放記憶體,且只有駐留該服務的程序成為被銷燬物件時,服務才可被銷燬;BIND_DEBUG_UNBIND
常量有:
Context. BIND_AUTO_CREATE:這樣就會在service不存在時建立一個
automatically create the service as long
* as the binding exists. Note that while this will create the service,
* its {@link android.app.Service#onStartCommand}
* method will still only be called due toan
* explicit call to {@link #startService}. Even without that, though,
* this still provides you with access tothe service object while the
* service iscreated.
Context. BIND_DEBUG_UNBIND
Flag for {@link #bindService}: include debugging help for mismatched
* calls to unbind. When this flag is set, thecallstackof the following
* {@link #unbindService} call is retained, tobe printed if a later
* incorrect unbind call is made. Note that doing this requires retaining
* information about the binding that wasmade for the lifetime of theapp,
* resulting ina leak -- this shouldonly be used for debugging.
Context. BIND_NOT_FOREGROUND
Flag for {@link #bindService}: don't allow this binding to raise
* the target service's process to theforeground scheduling priority.
* It will still be raised to at least thesame memory priority
* as the client (so that its process willnot bekillable in any
* situation where the client is notkillable),but for CPU scheduling
* purposes it may be left in thebackground. This only has an impact
* in the situation where the bindingclient is a foreground process
* and thetarget service is in a background process.
Context. BIND_ABOVE_CLIENT
Flag for {@link #bindService}: indicates that the client application
* binding to this service considers theservice to be more important than
* the app itself. When set, the platform will try to have theout of
* memory kill the app before itkills the service it is bound to, though
* this is notguaranteed to be the case.
/**
* Flag for {@link #bindService}: allow the processhosting the bound
* service to go through its normal memorymanagement. It will be
* treated more like a running service,allowing the system to
* (temporarily) expunge the process if lowon memory or for some other
* whim it may have, and being moreaggressive about making it a candidate
* to be killed (and restarted) if runningfor a long time.
*/
publicstaticfinalintBIND_ALLOW_OOM_MANAGEMENT = 0x0010;
/**
* Flag for {@link #bindService}: don't impact thescheduling or
* memory management priority of the targetservice's hosting process.
* Allows the service's process to bemanaged on the background LRU list
* just like a regular application processin the background.
*/
publicstaticfinalintBIND_WAIVE_PRIORITY = 0x0020;
/**
* Flag for {@link #bindService}: this service is veryimportant to
* the client, so should be brought to theforeground process level
* when the client is. Normally a process can only be raised to the
* visibility level by a client, even ifthat client is in the foreground.
*/
publicstaticfinalintBIND_IMPORTANT= 0x0040;
/**
* Flag for {@link #bindService}: If binding from anactivity, allow the
* target service's process importance tobe raised based on whether the
* activity is visible to the user,regardless whether another flag is
* used to reduce the amount that theclient process's overall importance
* is used to impact it.
*/
publicstaticfinalintBIND_ADJUST_WITH_ACTIVITY = 0x0080;
/**
* Flag for {@link #bindService}: Don't consider thebound service to be
* visible, even if the caller is visible.
* @hide
*/
publicstaticfinalintBIND_NOT_VISIBLE = 0x40000000;
========================================================================
二.混合使用startService和bindService方法(例子:ServiceFixDemo)
Service生命週期問題:onCreateonStartonDestroyonBind
1). StartService被啟動的服務的生命週期:如果一個Service被某個Activity呼叫 Context.startService方法啟動,那麼不管是否有Activity使用bindService繫結或unbindService解除繫結到該Service,該Service都在後臺執行。如果一個Service被startService方法多次啟動,那麼onCreate方法只會呼叫一次,onStart將會被呼叫多次(對應呼叫startService的次數),並且系統只會建立Service的一個例項(因此你應該知道只需要一次stopService呼叫)。該Service將會一直在後臺執行,而不管對應程式的Activity是否在執行,直到被呼叫stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。【實現啟動服務,服務可以在後臺長時間執行,不能和服務通訊】
2). bindService被繫結的服務的生命週期:如果一個Service被某個Activity呼叫 Context.bindService方法繫結啟動,不管呼叫 bindService 呼叫幾次,onCreate方法都只會呼叫一次,同時onStart方法始終不會被呼叫。當連線建立之後,Service將會一直執行,除非呼叫Context.unbindService斷開連線或者之前呼叫bindService 的 Context不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被呼叫。
【實現啟動服務,服務與其啟動元件有依賴關係,實現了和服務通訊--Binder】
3).混合使用--被啟動又被繫結的服務的生命週期:如果一個Service又被啟動又被繫結,則該Service將會一直在後臺執行。並且不管如何呼叫,onCreate始終只會呼叫一次,對應startService呼叫多少次,Service的onStart便會呼叫多少次。呼叫unbindService將不會停止Service,而必須呼叫stopService 或 Service的 stopSelf來停止服務
bindService和startService混合使用
a.在bind的Activity退出的時候,service會執行unBind()方法而不執行OnDestroy()方法,因為有startService方法呼叫過,所有Activity與Service解除繫結後會有一個與呼叫者沒有關聯的Service存在。
b.如果先bindService,再startService,再呼叫Context.stopService()
Service的OnDestroy()方法不會立即執行,因為有一個與Service繫結的Activity,但是在Activity退出的時候,會執行OnDestroy,如果要立即執行stopService,就得先解除繫結。[否則應用會報錯]
C.如果先執行startService,再執行bindService,結果是一樣的。
【實現啟動服務,服務可以在後臺長時間執行,服務與其啟動元件有依賴關係,實現了和服務通訊--Binder】
4). 當服務被停止時清除服務:當一個Service被終止(1、呼叫stopService;2、呼叫stopSelf;3、不再有繫結的連線(沒有被啟動))時,onDestroy方法將會被呼叫,在這裡你應當做一些清除工作,如停止在Service中建立並執行的執行緒。
特別注意:
1、你應當知道在呼叫bindService繫結到Service的時候,你就應當保證在某處呼叫 unbindService解除繫結(儘管 Activity被 finish 的時候繫結會自動解除,並且Service會自動停止);
2、你應當注意使用 startService啟動服務之後,一定要使用 stopService停止服務,不管你是否使用bindService;
3、同時使用 startService與 bindService 要注意到,Service 的終止,需要unbindService與stopService同時呼叫,才能終止Service,不管 startService與 bindService 的呼叫順序,如果先呼叫 unbindService此時服務不會自動終止,再呼叫 stopService之後服務才會停止,如果先呼叫 stopService此時服務也不會終止,而再呼叫 unbindService或者之前呼叫 bindService的 Context 不存在了(如Activity 被 finish 的時候)之後服務才會自動停止;
4、Service.onBind如果返回null,則呼叫bindService 會啟動 Service,但不會連線上 Service,因此 ServiceConnection.onServiceConnected不會被呼叫,但你任然需要使用unbindService函式斷開它,這樣Service 才會停止。
5.如果service已經啟動並且接受繫結,那麼當系統呼叫你的onUnbind()方法,你可以選擇返回true表示你想在客戶端下一次繫結到service時接受一個onRebind()的呼叫(而不是一個OnBind()的呼叫),OnRebind()返回void,但是客戶端依然在它的onServiceConnectionted()回撥中接收到IBinder。【例子:ServiceFixDemo】
6.問題:如果在一個Activity的onCreate方法中,先bindService(),再startService(),退出這個Activity時,會執行onUnBind,但是再次進入這個Activity的時候,為什麼不執行onBind方法了?【例子:ServiceFixTwoDemo】
只有在這個Service銷燬後(執行onDestory),再進這個Activity才會執行onBind。
還有就是當有兩個客戶端時,在第一個客戶端startServie啟動服務再bindService繫結服務(啟動時會呼叫onBind()),這時跳到第二個客戶端裡,再客戶端startServie啟動服務再bindService繫結服務,啟動時不會呼叫用onBind()了(因為之前客戶端已經啟動後沒有onDestory()銷燬Service,所以再客戶端第二次繫結服務時,只會返回IBinder物件給onServiceConnected()),而且要注意的是:當第一個服務啟動並繫結一個服務時,再跳去第二個服務端啟動並繫結這個服務時,第二個服務端再解綁時,不會呼叫onUnbind(),只有回到第一個客戶端時,解綁這是才會呼叫onUnbind(),順序反過來結果是一樣的。得出一個結論是:當一個服務沒被onDestory()銷燬之前,只有第一個啟動它的客戶端能呼叫它的onBind()和onUnbind()。
7、當在旋轉手機螢幕的時候,當手機螢幕在“橫”“豎”變換時,此時如果你的Activity 如果會自動旋轉的話,旋轉其實是 Activity的重新建立,因此旋轉之前的使用 bindService建立的連線便會斷開(Context不存在了),對應服務的生命週期與上述相同。
8、在 sdk 2.0及其以後的版本中,對應的 onStart已經被否決變為了 onStartCommand,不過之前的 onStart任然有效。這意味著,如果你開發的應用程式用的 sdk為 2.0 及其以後的版本,那麼你應當使用 onStartCommand而不是 onStart
三.服務按執行型別分類----前臺服務【建立前臺服務【ServiceForeGroundDemo】
類別 |
區別 |
應用 |
前臺服務 |
會在通知一欄顯示 ONGOING的 Notification, |
當服務被終止的時候,通知一欄的 Notification也會消失,這樣對於使用者有一定的通知作用。常見的如音樂播放服務。 |
後臺服務 |
預設的服務即為後臺服務,即不會在通知一欄顯示 ONGOING的 Notification。 |
當服務被終止的時候,使用者是看不到效果的。某些不需要執行或終止提示的服務,如天氣更新,日期同步,郵件同步等。 |
後臺服務我們可以自己建立 ONGOING的 Notification這樣就成為前臺服務嗎?答案是否定的,前臺服務是在做了上述工作之後需要呼叫 startForeground( android2.0及其以後版本)或 setForeground(android 2.0以前的版本)使服務成為前臺服務。這樣做的好處在於,當服務被外部強制終止掉的時候,ONGOING的 Notification也被移除掉。
前臺服務好處:系統在執行後臺服務的時候,發現在手機休眠一段時間後(1-2小時),後臺執行的服務被強行kill掉,有可能是系統回收記憶體的一種機制,要想避免這種情況可以通過startForeground讓服務前臺執行,當stopService的時候通過stopForeground去掉。
前臺服務只是提高了服務的優先順序。當然並不能保證你得 Service 永遠不被殺掉,只是提高了他的優先順序。【ForegroundService專案使用了反射機制來啟動前臺服務】
Public static intONGOING_NOTIFICATION=1;
Notification notification=newNotification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());
Intent notificationIntent=newIntent(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);
方法解釋:
為了使服務在前臺執行,需要呼叫startForeground(intnoticationID,Notication notication).這個引數有兩個引數,一個是通知的標示,第二個是顯示在狀態列中的通知。stopForeground(boolean).是否移除狀態列中的Notication。這個方法不能停止服務。但是,當這個服務正在執行的時候去停止服務(沒有呼叫stopForeground()方法),這個Notication任然會被移除。【setForeground(boolean)只是簡單的改變service的狀態為background】
四.BindService客戶端和服務端通訊的幾種方法總結:
.建立BindService
如果客戶端通過bindService()方法繫結服務,此時,客戶端必須提供ServiceConnection介面的實現類,該類的功能:監視客戶端和服務的連線。當Android系統建立客戶端與服務直接的連線,它呼叫ServiceConnection介面的OnServiceConnection()方法,來發送客戶端用來與服務通訊的IBinder物件。
在實現繫結服務時,最重要的方法是OnBinde()回撥方法返回的介面,有三種方法:
方法一:繼承Binder類(支援跨程序)
如果服務對應用程式私有並且與客戶端執行在相同的程序中,則應該繼承Binder類來建立介面,並且從onBind()方法返回其一個例項。客戶端接收到Binder物件並且用其來訪問Binder類實現類或者Service類中的公共方法。【支援跨程序原因:客戶端能夠轉型返回物件並且適當的呼叫其方法】
方法二:使用Messenger
Messenger:信使
官方文件解釋:它引用了一個Handler物件,以便others能夠向它傳送訊息(使用mMessenger.send(Messagemsg)方法)。該類允許跨程序間基於Message的通訊(即兩個程序間可以通過Message進行通訊),在服務端使用Handler建立一個Messenger,客戶端持有這個Messenger就可以與服務端通訊了。
以前我們使用Handler+Message的方式進行通訊,都是在同一個程序中,從執行緒持有一個主執行緒的Handler物件,並向主執行緒傳送訊息。
而Android既然可以使用bindler機制進行跨進行通訊,所以我們當然可以將Handler與bindler結合起來進行跨程序傳送訊息。
檢視API就可以發現,Messenger就是這種方式的實現。
如果需要介面跨程序工作,則可以使用Messenger類來建立介面。此時,服務定義的Handler物件來響應不同型別的Message物件。Handler是Messenger的基石,能與客戶端分享IBinder,允許客戶端使用Message物件向服務傳送命令。此外,客戶端能定義自己的Message物件,這樣服務能傳送回訊息。
使用Messenger是執行程序通訊(IPC)最簡單的方式。在單個執行緒中,Messenger類將所有的請求佇列化,這樣服務每次收到一個請求,這樣就不必設計服務為執行緒安全。
它引用一個Handler物件,以便others能夠像它傳送訊息(mMessenger.send(Messagemsg)方法)。該類允許程序間基於Message的通訊(即連個程序間的通訊)。在服務端使用Handler建立一個Messenger,客戶端持有這個Messenger就可以通訊了。
Messenger實現方法:只有客戶端向服務端傳送訊息,單向的【Demo4】
1. 遠端服務端通過:Messenger mMessenger=newMessenger(mHandler);建立一個信使物件
2. 客戶端通過使用bindService()請求連線連線遠端
3. 遠端OnBind()方法返回一個binder物件:mMessenger.getBinder();
4. 客戶端使用遠端返回的的binder得到一個信使(即得到遠端信使)
Public voidonServiceConnection(ComponentName name,IBinder service){
rMessenger=new Messenger(service);
}
這裡new了一個Messenger物件,其實現原始碼:
Messenger物件privatefinal IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public IBinder getBinder() {
return mTarget.asBinder();
}
Handler物件
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}
MessengerImpl物件
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
Handler.this.sendMessage(msg);
}
}
當我們呼叫mMessenger.getBinder()這個方法時,底層會通過mTarget物件asBinder()方法返回binder物件,而mTarget物件是我們在建立Messenger物件時通過傳入其中的handler物件的getIMessenger()獲取的,在Handler物件的getIMessenger()裡,建立了一個MessengerImpl物件,它實現了 IMessenger.Stub由此可知Messenger也是通過aidl實現程序間通訊的,mTarget=Messengerimpl,發現它的mTarget是通過aidl得到的,實際就是遠端建立的那個
5. 客戶端可以使用這個遠端信使物件向遠端傳送訊息:rMessenger.send(msg);
遠端服務端的Handler物件就能收到訊息了,然後可以呼叫handlerMessage(Message msg)方法中進行處理。【該Handler物件是第一步服務端建立Messenger是用的引數mHandler】
實現雙向傳遞訊息方法:
修改第5步:
//客戶端的物件,服務端可以通過此物件傳送訊息到客戶端
MessengermClientMessenger=new Messenger(new ClientHandler()); //建立客戶端信使
在rMessenger.send(msg)之前通過:msg.repleyTo=mClientMessenger將自己的信使設定到訊息中,這樣服務端接收到訊息時同時得到客戶端的信使物件了,然後服務端可以在自己的Handler物件的hanlerMessage方法中接收客戶端信使物件:MessengerclientMessenger=msg.replyTo得到客戶端的信使物件,並向它傳送訊息clientMessenger.send(message);
即完成了從服務端向客戶端傳送訊息的功能,這樣客戶端可以在自己的Handler物件的hanlerMessage方法中接收服務端傳送的message進行處理
方法三:aidl(後續講)
五.使用哪種方法啟動服務
在什麼情況下使用startService 或 bindService或同時使用startService和 bindService?
a.如果你只是想要啟動一個後臺服務長期進行某項任務那麼使用 startService便可以了。
b.如果你想要與正在執行的 Service取得聯絡,那麼有兩種方法,一種是使用 broadcast,另外是使用bindService,前者的缺點是如果交流較為頻繁,容易造成效能上的問題,並且 BroadcastReceiver本身執行程式碼的時間是很短的(也許執行到一半,後面的程式碼便不會執行),而後者則沒有這些問題,因此我們肯定選擇使用 bindService(這個時候你便同時在使用 startService和bindService 了,這在 Activity中更新 Service的某些執行狀態是相當有用的)。
C.另外如果你的服務只是公開一個遠端介面,供連線上的客服端(android的 Service 是C/S架構)遠端呼叫執行方法。這個時候你可以不讓服務一開始就執行,而只用 bindService,這樣在第一次bindService 的時候才會建立服務的例項執行它,這會節約很多系統資源,特別是如果你的服務是Remote Service,那麼該效果會越明顯(當然在 Service建立的時候會花去一定時間,你應當注意到這點)。
六.如何防止Android應用中的Service被系統回收?
對於Service被系統回收,一般做法是通過提高優先順序可以解決,在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority= "1000"這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時實用於廣播,推薦大家如果你的應用很重要,可以考慮通過系統常用intent action來觸發。
另外一種實現方法:上面已經講了----前臺服務