Service背景執行程式

簡介

Service為Android上提供用來做背景服務用的應用程式架構。簡單來說就是一個在背景執行的程式,該程式不需要和使用者互動,所以也不會有操作介面。更重要的是,它的生命週期是和Activity各自分開的,Activity就算關閉了,Service 仍然可以繼續執行。

Service通常用來執行重覆性的事(例如每隔一段時間檢查是否有更新資料),或者需要執行很久的事情上(例如在背景從網路上下載檔案)。

Service的兩種模式

Started模式

當Service一旦透過Context.startService()啟動後,這個Service就可以無限的在系統背後執行,即使啟動這個Service的Activity或應用程式被關閉,這個Service仍可以繼續執行本身的工作。但一般來說,Service並不會和啟動它的應用程式有什麼互動,Service完成後也不會回傳或通知原本啟動它的應用程式。所以如果一旦Service執行完成,Service就會自動停止。

Bind模式

Service另一個功能就是負責跨Process(應用程式行程)的功能協作。各個不同的應用程式可以透過Context.bindService()的方法來使用此應用程式在Service裡所提供的功能,以達到兩個應用程式的互動。也就是Service在bind之後可以如同在同一個程式內直接去呼叫Service內的方法來使用。

一個Service可以被多個不同的應用程式透過Context.bindService()的方式綁定。只要有其它應用程式開始綁定這個Service,這個Service就會啟動。一直到所有的應用程式都不再使用這個Service,這個Service就會自動被銷毀。

備註:

以上兩種形式其實並不衝突, 一個服務可以透過startService()的方式被啟動,然後再被其它應用程式綁定。只要同時實作onStartCommand()onBind()兩個事件即可。

當在AndroidManifest.xml檔中將Service宣告成android:exported=”false”時,其它應用程式就不能啟動這個Service了。

Service生命週期

一個Service可以被系統啟動的原因有兩種

  1. 當有應用程式呼叫Context.startService()方法時,系統會呼叫Service物件內的onCreate()方法,接下來會呼叫onStartCommand()方法讓應用程式端提供參數來決定做哪些事情。一直到Context.stopService()方法被呼叫,或者是Service內自己呼叫stopSelf()方法才會停止。
  2. 另一個被啟動的方式是有其它的應用程式端透過Context.bindService()來取得與Service的連結。如果該Service不在啟動狀態的話,這種方式則會啟動這個Service.onCreate()的方式這時就會被呼叫,但這種方式並不會觸發onStartCommand()事件。啟動這個Service的應用程式會透過onBind()方法取得IBinder物件。接下來就可以透過IBinder物件來取得Service的事件(透過callback的方法)。只要連線不被中斷,Service就會保持執行狀態。

Service可以同時支援Started與Bind兩種模式。在這種情況下,Service會持續運作到被Started模式關閉,同時Bind模式中已經沒有任何連線時才會結束,結束時會觸發onDestroy()事件。

Service的運作優先權相當的高,一般來說除非系統資源耗盡,否則Android不會主動關閉一個已被啟動的Service。一旦系統有足夠的資源,被Android關閉的Service也會被重新啟動。

因為Service和應用程式預設是執行在同一個Process(應用程式行程)的主執行緒中,所以如果在Service中執行了耗時的工作,連帶的也會影響到應用程式的反應。

一般來說很少需要特別指定另一個Process來執行Service,除非這個Service會對其它應用程式提供服務時才需要。

建立Service(Started模式)

建立Started模式的Service需要三個步驟:
  1. 在AndroidManifest.xml檔中新增定義

    <service android:name=”” android:enabled=”true />
    
    這裡的android:name是你自訂的Service類別的完整名稱。
  2. 實作一個繼承自android.app.Service類別的物件

    public class MyService extends Service 
    {
        int mStartMode;       // indicates how to behave if the service is killed
        IBinder mBinder;      // interface for clients that bind
        boolean mAllowRebind; // indicates whether onRebind should be used
    
        @Override
        public void onCreate() 
        {
            // The service is being created
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) 
        {
            // The service is starting, due to a call to startService()
            return mStartMode;
        }
    
        @Override
        public IBinder onBind(Intent intent) 
        {
            // A client is binding to the service with bindService()
            return mBinder;
        }
    
        @Override
        public boolean onUnbind(Intent intent) 
        {
            // All clients have unbound with unbindService()
            return mAllowRebind;
        }
    
        @Override
        public void onRebind(Intent intent) 
        {
            // A client is binding to the service with bindService(), after onUnbind() has already been called
        }
    
        @Override
        public void onDestroy() 
        {
            // The service is no longer used and is being destroyed
        }
    }
    

    onStartCommand()中,最後需要回傳一個常數,這些常數定義在Service類別中。該回傳值是用在如果這個Service被Android作業系統終止後的行為。

    常數值分別為:
    Service.START_STICKY Service如果被中止的話會自動重啟。用在onStartCommand()方法中不需要依賴Intent傳入的資料就可以執行的時候(重新啟動時重新傳入的Intent會是null)。這也是預設使用super.onStartCommnad()的回傳值。
    Service.START_NOT_STICKY Servcie如果被中止的話不重新啟動,用在onStartCommand()方法中所執行的工作需要依賴Intent物件內帶進來的參數。
    Service.START_REDELIVER_INTENT 和START_STICKY差不多,但Android會在中止Service之前將Intent保留下來,等待重新啟動時再將原本的Intent物件交還給onStartCommand()事件。
    例如:
    public class MyService extends Service
    {
        public IBinder onBind(Intent intent)
        @Override
        { 
            return null;
        }
       
        @Override
        public int onStartCommand(Intent intent, int flags, int startId)
        {
            //這裡實作你想做的工作
            return Service.START_STICKY;
        }
    }
    
  3. 在Activity中呼叫startService()來執行
    @Override
    public View onCreate(Bundle savedInstanceState)
    {
        setContentView(R.layout.activity_main);
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(this, MyService.class); 
        this.startService(intent);
    }
    

以上三個步驟就可以啟動一個Started模式的Service。


建立Service(Bind模式)

另外一種啟動Service的方式,透過Bind方式來啟動Service。
  1. 在AndroidManifest.xml檔中新增定義

    <service android:name=”” android:enabled=”true />
    
    這裡的android:name是你自訂的Service類別的完整名稱。
  2. 實作onBind(Intent) 事件。IBinder 介面負責應用程式端與Service端的溝通,用來將Service的實體透過Binder的延伸方法的方式取得。

    public class MyService extends Service
    {
        private final IBinder mBinder = new MyBinder();
        private String info = "";
    
        @Override
        public IBinder onBind(Intent intent)
        {
            return mBinder;
        }
    
        public class MyBinder extends Binder
        {
            MyService getService()
            {
                return MyService.this;
            }
        }
    
        public String getInfo()
        {
            return info;
        }
    }
    
  3. 在應用程式端(例如Activity內)實作ServiceConnection介面

                                                  
    private ServiceConnection mServiceConnection = new ServiceConnection() 
    {
        @Override
        public void onServiceConnected(ComponentName className, IBinder binder)
        {
            // 取出Service實體
            service = ((MyService.MyBinder)binder).getService();
        }
    
        @Override
        public void onServiceDisconnected(ComponentName className)
        { 
            service = null;
        }
    }
    
  4. 在啟動該Service的Activity中,可以在onResume()onStop()的事件中管理與Service的連線

    public class MainActivity extends Activity
    {
        @Override
        public void onResume()
        {
            Intent intent = new Intent(this, MyService.class);
            super.onResume();
            this.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        public void onPause()
        {
            super.onPause(); 
            this.unbindService(mServiceConnection);
        }
    }