Service詳細解析
關鍵時刻,第一時間送達!
GIF/23K
Service詳細解析
十月
4
成長不是學會表達,而是學會咽下,當你終於可以剋制自己的時候,才能駕馭夢想,讓人生從此與眾不同。
1
什麼是服務?
Service是一個應用程序組件,它能夠在後台執行一些耗時較長的操作,並且不提供用戶界面。服務能被其它應用程序的組件啟動,即使用戶切換到另外的應用時還能保持後台運行。此外,應用程序組件還能與服務綁定,並與服務進行交互,甚至能進行進程間通信(IPC)。 比如,服務可以處理網路傳輸、音樂播放、執行文件I/O、或者與content provider進行交互,所有這些都是後台進行的。
Service 與 Thread 的區別
服務僅僅是一個組件,即使用戶不再與你的應用程序發生交互,它仍然能在後台運行。因此,應該只在需要時才創建一個服務。
如果你需要在主線程之外執行一些工作,但僅當用戶與你的應用程序交互時才會用到,那你應該創建一個新的線程而不是創建服務。 比如,如果你需要播放一些音樂,但只是當你的activity在運行時才需要播放,你可以在onCreate()中創建一個線程,在onStart()中開始運行,然後在onStop()中終止運行。還可以考慮使用AsyncTask或HandlerThread來取代傳統的Thread類。
由於無法在不同的 Activity 中對同一 Thread 進行控制,這個時候就要考慮用服務實現。如果你使用了服務,它默認就運行於應用程序的主線程中。因此,如果服務執行密集計算或者阻塞操作,你仍然應該在服務中創建一個新的線程來完成(避免ANR)。
服務的分類
本地服務
用於應用程序內部,實現一些耗時任務,並不佔用應用程序比如Activity所屬線程,而是單開線程後台執行。 調用Context.startService()啟動,調用Context.stopService()結束。在內部可以調用Service.stopSelf() 或 Service.stopSelfResult()來自己停止。
遠程服務
用於Android系統內部的應用程序之間,可被其他應用程序復用,比如天氣預報服務,其他應用程序不需要再寫這樣的服務,調用已有的即可。可以定義介面並把介面暴露出來,以便其他應用進行操作。客戶端建立到服務對象的連接,並通過那個連接來調用服務。調用Context.bindService()方法建立連接,並啟動,以調用 Context.unbindService()關閉連接。多個客戶端可以綁定至同一個服務。如果服務此時還沒有載入,bindService()會先載入它。
2
Service生命周期
Service生命周期方法:
public class ExampleService extends Service {
int mStartMode; // 標識服務被殺死後的處理方式
IBinder mBinder; // 用於客戶端綁定的介面
boolean mAllowRebind; // 標識是否使用onRebind
@Override
public void onCreate() {
// 服務正被創建
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 服務正在啟動,由startService()調用引發
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// 客戶端用bindService()綁定服務
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// 所有的客戶端都用unbindService()解除了綁定
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// 某客戶端正用bindService()綁定到服務,
// 而onUnbind()已經被調用過了
}
@Override
public void onDestroy() {
// 服務用不上了,將被銷毀
}
}
請注意onStartCommand()方法必須返回一個整數。這個整數是描述系統在殺死服務之後應該如何繼續運行。onStartCommand()的返回值必須是以下常量之一:
START_NOT_STICKY 如果系統在onStartCommand()返回後殺死了服務,則不會重建服務了,除非還存在未發送的intent。 當服務不再是必需的,並且應用程序能夠簡單地重啟那些未完成的工作時,這是避免服務運行的最安全的選項。 START_STICKY如果系統在onStartCommand()返回後殺死了服務,則將重建服務並調用onStartCommand(),但不會再次送入上一個intent, 而是用null intent來調用onStartCommand() 。除非還有啟動服務的intent未發送完,那麼這些剩下的intent會繼續發送。 這適用於媒體播放器(或類似服務),它們不執行命令,但需要一直運行並隨時待命。
START_REDELIVER_INTENT如果系統在onStartCommand()返回後殺死了服務,則將重建服務並用上一個已送過的intent調用onStartCommand()。任何未發送完的intent也都會依次送入。這適用於那些需要立即恢復工作的活躍服務,比如下載文件。
服務的生命周期與activity的非常類似。不過,更重要的是你需密切關注服務的創建和銷毀環節,因為後台運行的服務是不會引起用戶注意的。
服務的生命周期——從創建到銷毀——可以有兩種路徑
一個started服務
這類服務由其它組件調用startService()來創建。然後保持運行,且必須通過調用stopSelf()自行終止。其它組件也可通過調用stopService() 終止這類服務。服務終止後,系統會把它銷毀。
如果一個Service被startService 方法多次啟動,那麼onCreate方法只會調用一次,onStart將會被調用多次(對應調用startService的次數),並且系統只會創建Service的一個實例(因此你應該知道只需要一次stopService調用)。該Service將會一直在後台運行,而不管對應程序的Activity是否在運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。
一個bound服務
服務由其它組件(客戶端)調用bindService()來創建。然後客戶端通過一個IBinder介面與服務進行通信。客戶端可以通過調用unbindService()來關閉聯接。多個客戶端可以綁定到同一個服務上,當所有的客戶端都解除綁定後,系統會銷毀服務。(服務不需要自行終止。)
如果一個Service被某個Activity 調用 Context.bindService 方法綁定啟動,不管調用 bindService 調用幾次,onCreate方法都只會調用一次,同時onStart方法始終不會被調用。當連接建立之後,Service將會一直運行,除非調用Context.unbindService 斷開連接或者之前調用bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被調用。
這兩條路徑並不是完全隔離的。也就是說,你可以綁定到一個已經用startService()啟動的服務上。例如,一個後台音樂服務可以通過調用startService()來啟動,傳入一個指明所需播放音樂的 Intent。 之後,用戶也許需要用播放器進行一些控制,或者需要查看當前歌曲的信息,這時一個activity可以通過調用bindService()與此服務綁定。在類似這種情況下,stopService()或stopSelf()不會真的終止服務,除非所有的客戶端都解除了綁定。
當在旋轉手機屏幕的時候,當手機屏幕在「橫」「豎」變換時,此時如果你的 Activity 如果會自動旋轉的話,旋轉其實是 Activity 的重新創建,因此旋轉之前的使用 bindService 建立的連接便會斷開(Context 不存在了)。
在manifest中聲明服務
無論是什麼類型的服務都必須在manifest中申明,格式如下:
...
...
Service 元素的屬性有:
android:name-------------服務類名
android:label--------------服務的名字,如果此項不設置,那麼默認顯示的服務名則為類名
android:icon--------------服務的圖標
android:name是唯一必需的屬性——它定義了服務的類名。與activity一樣,服務可以定義intent過濾器,使得其它組件能用隱式intent來調用服務。如果你想讓服務只能內部使用(其它應用程序無法調用),那麼就不必(也不應該)提供任何intent過濾器。 此外,如果包含了android:exported屬性並且設置為"false", 就可以確保該服務是你應用程序的私有服務。即使服務提供了intent過濾器,本屬性依然生效。
startService 啟動服務
從activity或其它應用程序組件中可以啟動一個服務,調用startService()並傳入一個Intent(指定所需啟動的服務)即可。
Intent intent = new Intent(this, MyService.class);
startService(intent);
服務類:
public class MyService extends Service {
/**
* onBind 是 Service 的虛方法,因此我們不得不實現它。
* 返回 null,表示客服端不能建立到此服務的連接。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//接受傳遞過來的intent的數據
return START_STICKY;
};
@Override
public void onDestroy() {
super.onDestroy();
}
}
一個started服務必須自行管理生命周期。也就是說,系統不會終止或銷毀這類服務,除非必須恢復系統內存並且服務返回後一直維持運行。 因此,服務必須通過調用stopSelf()自行終止,或者其它組件可通過調用stopService()來終止它。
bindService 啟動服務
當應用程序中的activity或其它組件需要與服務進行交互,或者應用程序的某些功能需要暴露給其它應用程序時,你應該創建一個bound服務,並通過進程間通信(IPC)來完成。
方法如下:
Intent intent=new Intent(this,BindService.class);
bindService(intent, ServiceConnection conn, int flags)
注意bindService是Context中的方法,當沒有Context時傳入即可。
在進行服務綁定的時,其flags有:
Context.BIND_AUTO_CREATE
表示收到綁定請求的時候,如果服務尚未創建,則即刻創建,在系統內存不足需要先摧毀優先順序組件來釋放內存,且只有駐留該服務的進程成為被摧毀對象時,服務才被摧毀
Context.BIND_DEBUG_UNBIND
通常用於調試場景中判斷綁定的服務是否正確,但容易引起內存泄漏,因此非調試目的的時候不建議使用
Context.BIND_NOT_FOREGROUND
表示系統將阻止駐留該服務的進程具有前台優先順序,僅在後台運行。
服務類:
public class BindService extends Service {
// 實例化MyBinder得到mybinder對象;
private final MyBinder binder = new MyBinder();
/**
* 返回Binder對象。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return binder;
}
/**
* 新建內部類MyBinder,繼承自Binder(Binder實現IBinder介面),
* MyBinder提供方法返回BindService實例。
*/
public class MyBinder extends Binder{
public BindService getService(){
return BindService.this;
}
}
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
}
啟動服務的activity代碼:
public class MainActivity extends Activity {
/** 是否綁定 */
boolean mIsBound = false;
BindService mBoundService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doBindService();
}
/**
* 實例化ServiceConnection介面的實現類,用於監聽服務的狀態
*/
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BindService mBoundService = ((BindService.MyBinder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBoundService = null;
}
};
/** 綁定服務 */
public void doBindService() {
bindService(new Intent(MainActivity.this, BindService.class), conn,Context.BIND_AUTO_CREATE);
mIsBound = true;
}
/** 解除綁定服務 */
public void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(conn);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
doUnbindService();
}
}
注意在AndroidMainfest.xml中對Service進行顯式聲明
判斷Service是否正在運行:
作者:武漢尚觀,美工:Culture
程序員大咖整理髮布,轉載請聯繫作者獲得授權
※如何精確度量 iOS App 的啟動時間
※和離職程序員做工作交接
※重磅!HTC手機將成回憶了?
※iOS特效之仿Mac窗口最小化的神奇效果
TAG:程序員大咖 |