Android應用程序組件之Service

本節講述Android應用程序組件中的Service。所謂Service就是在後臺執行且沒有提供用戶界面的組件,即使用戶切換到其他應用程序,service仍然在後臺運行不受影響。通常service被用來在後臺執行耗時的操作,比如網絡通信、播放音樂、執行文件I/O、與content provider交互等。

Service有兩種類型,分爲啓動(started)和綁定(bound)兩種。啓動service由startService()完成,一旦service啓動,它能夠一直在後臺運行即使啓動它的組件已經被銷燬,通常被用來執行單獨的操作且並不返回結果給調用者。而綁定service是由bindService()完成,它的作用在於能夠讓組件之間進行交互,發送請求、獲取結果以及完成進程間通信的任務,這種service在其他組件綁定它時會一直運行,直到所有組件取消綁定才銷燬。當然,你也可以在實現service時支持這兩種模式,僅僅需要你實現對應的回調函數即可:onStartCommand()和onBind()。

注意,service運行在進程的主線程中,並不會創建新的線程也不會運行在單獨的進程中(除非你指定)。意味着如果你的service要完成CPU密集型的工作或阻塞的操作,那麼你應該在service中創建新的線程來完成,這樣可以降低ANR(應用程序無法響應)的風險,同時主線程仍然用來用戶界面交互。

創建service

創建service必須創建Service子類,在實現中要重寫一些關鍵的回調函數,如onStartCommand()、onBind()、onCreate()和onDestroy(),附上簡要介紹:

onStartCommand():調用startService()時系統會調用onStartCommand()方法,一旦該方法執行,service便已經啓動且能在後臺運行。如果實現了該方法,需要程序員自行調用stopSelf()或stopService()來停止服務。

onBind():調用bindService()時系統會調用onBind()方法,在該方法的實現中需要返回一個IBinder,爲客戶端與service通信提供接口。該方法必須要實現,如果不需要進行綁定則返回null。

onCreate():在service第一次創建時會調用onCreate()方法,執行一次性的設置操作,當服務已經在運行時,該方法不再被調用。

onDestroy():當service被銷燬時,需要實現onDestroy()方法來清除所有資源,如線程、註冊的監聽器等。

與activity類似,service組件也需要在AndroidManifest.xml中進行聲明,在<application>元素中添加<service>子標籤,如下:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>
同樣,啓動service需要使用Intent,也支持顯式(需要指定service類名)和隱式(需要指定<intent-filter>)兩種方式。

創建started service可以繼承Service或IntentService。其中,Service類是所有service的基類,繼承Service類需要在service中創建新的線程來完成任務,因爲它使用應用程序的主線程。IntentService類是Service類的子類,它使用工作線程依次處理所有啓動請求,如果不需要同時處理多個請求,這種方式會是最好的選擇,你需要做的只是實現onHandleIntent()方法,用來接收所有請求的intent從而完成相應工作。

IntentService完成了以下工作:

(1)默認創建一個工作線程來執行所有傳入onStartCommand()的intent請求;

(2)創建一個工作隊列,它會每次向onHandleIntent()傳入一個intent,這樣就不用擔心多線程的問題;

(3)在所有請求被處理完成後,停止service,因此你不需要自行調用stopSelf();

(4)默認提供的onBind()實現會返回null;

(5)默認提供的onStartCommand()會將intent傳向工作隊列,然後傳向你所實現的onHandleIntent()。

總之,你只需要實現onHandleIntent(),可能還需要實現構造函數,參考下面的例子:

public class HelloIntentService extends IntentService {

  /** 
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

注意:如果要重寫IntentService類的其他方法(onBind()除外),如onCreate()、onStartCommand()或onDestroy(),那麼一定要調用父類的實現,這樣IntentService才能正常執行工作線程的生命週期。如重寫onStartCommand()方法:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}
相對繼承IntentService而言,繼承Service會稍微複雜的多,但它所處理的情況通常是執行多線程任務,參考下面的例子:

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();
    
    // Get the HandlerThread's Looper and use it for our Handler 
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);
      
      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }
  
  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); 
  }
}
注意到onStartCommand()方法的返回值是int類型,文檔上規定onStartCommand()方法只能返回如下常量中一個:

START_NOT_STICKY:在onStartCommand()返回後系統殺死service時不會重建service,除非有pending intent要發送。避免在不必要時運行service,應用程序可以隨時重啓任何未完成的任務。

START_STICKY:在onStartCommand()返回後系統殺死service時會重建service並調用onStartCommand(),但不會重新傳送上次的intent。在這種情況下,onStartCommand()接受的intent爲null,除非有pending intent要啓動service。它適用於媒體播放器或類似服務,不執行命令,但會等待任務。

START_REDELIVER_INTENT:在onStartCommand()返回後系統殺死service時會重建service並調用onStartCommand(),傳入上次的intent,任何pending intent會輪流發送。它適用於需要立即重啓的任務,如下載文件。

啓動Service

調用startService()並傳入intent,如顯式啓動HelloService:

Intent intent = new Intent(this, HelloService.class);
startService(intent);
這種情況下,startService()會立即返回,系統會調用onStartCommand(),如果service未啓動會先調用onCreate(),再調用onStartCommand()。

如果希望service返回結果,創建PendingIntent傳入startService(),service會使用廣播來發送結果。

停止service

一個started service要維護它的生命週期。只有當系統需要回收內存時纔會停止或銷燬service,service在onStartCommand()返回後仍然保持運行狀態。因此,service自身要調用stopSelf()停止,或其他組件調用stopService(),這樣系統會盡快銷燬service。

當service同時處理多個啓動請求時,你不能在處理完一個啓動請求後停止service,因爲有可能已經接收到了一個新的啓動請求。爲了避免這個問題,可以調用stopSelf(int)方法,它會匹配最新請求的ID,保證所有請求處理完畢後停止service。

創建Bound Service

bound service允許應用程序組件調用bindService()進行綁定,允許應用程序其他組件與service進行交互,或者通過IPC使應用程序功能暴露給其他應用程序。創建bound service要實現onBind()回調函數,它會返回IBinder對象,該對象定義了與service通信的接口。當客戶端完成與service交互後,調用unbindService()解除綁定。由於創建Bound Service比創建Started Service更加複雜,後面會有單獨的一篇博文予以介紹。

在前臺運行Service

前臺service可以理解爲用戶所關心的,在內存緊缺時系統不會殺死的service。前臺service必須在狀態欄(status bar)提供一個notification,該notification不能夠被移除,除非service停止或者從前臺刪除。

請求service運行在前臺,要調用startForeground(),該方法有兩個參數:一個int值唯一標識notification(不能爲0),還有一個狀態欄的Notification,例如:

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_ID, notification);
從前臺刪除service,調用stopForeground(),該方法需要指定一個boolean值,表示是否要刪除狀態欄notification,該方法不會停止service。然而,如果service還在前臺運行時停止了service,此時notification也會被刪除。

Service生命週期

雖然Service生命週期比Activity生命週期要簡單得多,但是還是值得我們關注,因爲用戶無法感知到service的存在,它會在後臺運行。

鑑於service有started和bound兩種,簡單回顧一下:

started service:其他組件調用startService()時創建service,此後service會一直運行,直到自己調用stopSelf()或者其他組件調用stopService()停止service,系統最終會銷燬service。

bound service:其他組件調用bindService()時創建service,客戶端通過IBinder來與service進行通信,調用unbindService()可以關閉與service之間的連接,當所有組件解除與service之間的綁定時,系統會銷燬service。

這兩者也不是完全分離的,你可以綁定已經由startService()啓動的service。例如,後臺音樂服務可以由startService()啓動,intent指定要播放的音樂,接下來如果用戶要對播放器進行控制或者獲取當前歌曲信息,該activity可以調用bindService()綁定到service上。在這種情況下,stopService()和stopSelf()並不能停止服務,只有等到所有客戶端解除綁定纔會停止服務。

與activity類似,service也有一系列生命週期回調函數,實現這些函數能夠監聽service的狀態並在適當的時候執行任務,下面的例子給出了這些回調函數:

public class ExampleService 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
    }
}
需要注意的是,與activity不同之處在於,在自定義的service中不允許調用父類的這些回調函數。


如上圖爲service的生命週期圖。左邊表示調用startService()的生命週期,右邊表示調用bindService()的生命週期。

參考資料:Google ADT doc



Except as noted, this content is licensed under Creative Commons Attribution 2.5

發佈了23 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章