【Android官方文檔】翻譯Android官方文檔-Services(二)

Service是運行在後臺的組件,它不能直接給用戶提供服務。其他組件可以啓動Service,即使是切換至別的應用,Service仍然可以在後臺運行,組件可以與Service綁定並且與之交互,甚至可以跨進程通信(IPC)。如,Service可以在後臺進行網絡請求,播放音樂,或者進行文件讀寫操作,又或者和Content Provider交互等等。

這篇博客將介紹Service的定義,創建,啓動,綁定等內容。

Service的兩種方式啓動:
Started:組件通過調用startService()方法啓動Service,啓動後,Service會在後臺一直運行,就算是喚起Service的啓動者被銷燬,Service也依然可以在後臺運行。一般情況下,一個被start的Service會在後臺執行單獨的操作,也並不會給啓動者返回結果。例如:一個start的Service在後臺執行下載或者是上傳文件的操作,完成之後,Service應當自己停止。
Bound:其他組件調用bindService()方法綁定一個Service。綁定啓動的Service是Service-Client的結構,也就是說Service可以與多個組件進行交互。一個bound Service只在有組件綁定時纔會運行,無組件綁定時,服務被銷燬。

Service也可以同時以上面兩種方式下啓動。這裏涉及到serivice中的兩個方法:onStartCommand(),onBind()

無論是哪種方式啓動Service(單獨start , 單獨 bind , 同時 bind 和 start),任何組件都可以喚起 Service,通過Intent對象傳遞參數,不過,我們也可以將Service在配置文件(manifest.xml)中配置爲私有,這樣不允許其他應用訪問它。

注:Service運行在主線程中,所以說Service不是個新的線程,也不是新的進程。也就是說,如果我們需要在Service中執行比較耗時的操作時(如音樂播放,網絡請求等等),這就需要在Service中另外創建一個新的線程。這麼做可以防止異常,保證主線程的流暢操作。

那麼Service和Thread處理,哪個更優呢?

Service 是運行在後臺的組件,並不能直接與用戶交互。我們只需要在需要用上的時候才創建Service.

當用戶與UI進行交互時,如果要執行一個主線程無法完成的操作時,就應該創建一個線程。如:當Activity在運行,而我們需要播放音樂,這時候就應該創建線程。最後在Activity消耗時,別忘了銷燬這個線程。我們也可以選擇AsyncTask或者是HandlerThread來替代Thread。


Service基礎內容
我們爲了創建新的Service,就要繼承Service。並且重寫一些方法。這些方法就代表了Service的生命週期,並且這些方法中提供了綁定Service的機制。如下:

  • onStartCommand(): 當其他組件調用StartService()喚起Service時,這個方法會被調用。只要Service啓動,Service便會在後臺一直獨立的運行。在Service執行完後,應該調用stopSelf()或者是stopService()去銷燬Service。
  • onBind(): 當其他組件調用bindService()綁定Service時,這個方法會被調用,並且會返回一個IBinder接口,IBinder接口是Service與其綁定者進行交互的橋樑。如果Service沒有綁定者,則返回null。
  • onCreate(): 在Service首次創建時,啓動這個方法。方法只被觸發一次, 並且是在上述兩種方法啓動前被調用。如果Service已經在運行,那麼該方法不會執行。
  • onDestory() : 在service 消耗時調用,在消耗的方法中,我們應當要釋放之前佔用的資源。比如:停止線程。解除綁定的監聽器或者是廣播接收器等等。這個方法是Service的最後一個調用方法。

如果有組件調用startService()啓動Service(那麼系統會調用onStartCommand()方法),直到調用stopSelf()或者是調用stopService()方法,Service纔會被停止。
如果有組件調用bindService()綁定了Service(這裏系統並不會調用onStartCommand()方法),只要有組件綁定着Service,那麼這個Service就會一直運行,當無組件綁定Service時,Service會被消耗。

在系統內存不夠時,系統將強制銷燬Service。如果Service綁定了Activity,而這個Activity正在和用戶交互,那麼Service將有可能不會被系統清理掉。假如創建的是前臺Service,那麼Service幾乎不會被殺死。當創建的Service運行了很長時間後,那麼系統將可能會降低它在棧中的級別–從而使得它更容易被銷燬掉。因此,在做Service時,應當讓Service時常被restart,那麼這樣就可以保持Service的級別。


註冊Service
在manifest.xml文件中:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

另外,在標籤中還可以配置別的屬性,例如:需要啓動Service所需要的權限,該Service應在哪個進程中運行等等。這裏 android:name是不能缺少的,這是指定Service的類名。一旦發佈了應用,類名將不可更改。

注:爲了保證應用的安全性,推薦使用顯式的Intent啓動或者綁定Service,不要在中配置 intent-filter。

將android:exported設置爲false時,表示不允許別的應用啓動本應用的組件,即使是顯示的Intent也是不可以的。


繼承IntentService
在很多情況下,啓動Service 並不會去同時處理多個請求,因爲處理多個線程是非常危險的,這個時候選擇繼承IntentService就是不錯的解決方案。
使用IntentService要注意以下幾點:

  • 默認在子線程中處理回傳到onStartCommand()方法中的Intent;
  • 在OnHanleIntent()中處理按時間排序的Intent隊列,因此不必擔心多線程問題。
  • 當請求處理後,停止Service,無需手動調用stopSelf()
  • 默認是實現了onBind()方法,返回爲空、
  • 默認是實現了onStartCommand方法,並且將回傳的Intent以序列的形式發送給onHandleIntent(),只需要重寫該方法並且處理Intent就可以了。

綜上,我們只需要重寫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) {
              }
          }
      }
  }
}

如果你想在IntnetService的繼承類中重寫其他生命週期方法,如:onCreate(),onstartCommand(),onDestroy(),那麼先調用各自父類方法來保證子線程能夠正常啓動。
比如,要實現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);
}

除了onhandleIntent,onBind方法也不需要調用父類方法。


繼承Service

如果你需要在Service中執行多線程而不是處理一個請求隊列,那麼就需要繼承Service類,分別處理每個Intent。

在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返回一個整形變量,該變量必須是下列常量之一:

  • START_NOT_STICKY:如果執行完了onStartCommand後,系統就會消耗Service,不用再重新創建Service,除非系統回傳了一惡搞pending intent。這樣可以避免在不必要的時候運行Service。

  • START_STICKY:如果系統在onStartCommand()執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()。dangerous不要重新傳遞最後一個Intent(do not redeliver the last intent)。相反,系統回調onStartCommand()時會回傳一個空的Intent,除非有 pending intents傳遞,否則Intent將爲null。該模式適合做一些類似播放音樂的操作。

  • START_REDELIVER_INTENT:如果系統在onStartCommand()執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()並將最後一個Intent回傳至該方法。任何 pending intents都會被輪流傳遞。該模式適合做一些類似下載文件的操作。


啓動Service
若需要啓動Service,如:

Intent intent = new Intent(this, HelloService.class);
startService(intent);

startService(intent)方法將會立即返回,並且回調onStartCommand()(請不要手動調用該方法),如果該Service未處於運行狀態,那麼系統將會首先回調onCreate(),接着再回調onStartCommand()。若希望Service可以返回結果,那麼需要通過調用getBroadcast返回的PendingIntent啓動Service(將PendingIntent包裝爲Intent),service可使用broadcast 傳遞結果。

多個啓動Service的請求可能導致onStartCommand()多次調用,但只需調用stopSelf() 、 stopService()這兩個方法之一,就可停止該服務。


停止Service
一個啓動的Service必須管理自己的生命週期。系統不會主動stop或destroy一個正在運行的Service,除非系統內存緊張,不然的話,執行完onStartCommand()方法後,Service會一直運行。停止Service必須手動調用stopSelf()(在Service中)或調用stopService()(在啓動組件中)。

一旦調用了上述兩種方法之一,系統會盡快destroy該Service。

若系統正在處理多個調用onStartCommand()請求,那麼在啓動一個請求時,您不應當在此時停止該Service。爲了避免這個問題,可以調用stopSelf()方法,以確保請求停止的Service時最新的啓動請求。當調用stopSelf()方法時,傳入的ID表示啓動請求(該ID會傳遞至onStartCommand()),該ID與請求停止的ID一致。則如果在調用stopSelf()之前,Service收到一個新的Start請求,ID將無法匹配,Service並不會停止。

爲了節省內存和電量,當Service完成其工作後將其stop很有必要。如有必要,可以在其他組件中調用stopService()方法,即便Service處於綁定狀態,只要它回調過onStartCommand(),也應當主動停止該Service。


綁定Service
通過其他組件調用bindService()方法可以綁定一個Service以保持長連接,這時一般不允許其他組件調用startService()啓動Service。

當其他組件需要與Service交互或者需要跨進程通信時,可以創建一個bound Service。

爲創建一個bound Service,必須重寫onbind回調,該方法返回一個IBinder接口。該接口時組件與Service通信的橋樑。組件調用bindService()與Service綁定,該組件可獲取IBinder接口,一旦獲取該接口,就可以調用Service中的方法。一旦沒有組件與Service綁定,系統將destroy它,我們不必手動停止它。

爲創建一個bound Service,必須定義一個接口 ,該接口指定組件與Service如何通信。定義的接口在組件與Service之間,且必須實現IBinder接口。這正是onBind()的返回值。一旦組件接收了IBinder,組件與Service便可以開始通信。

多個組件可同時與Service綁定,當組件與Service交互結束後,可調用unbindService()方法解綁。bound Service比start Service要複雜,故我將在後續單獨翻譯。


發通知
運行中的Service可以通過Toast Notifications 或 Status Bar Notifications 向用戶發送通知。Toast是一個可以短時間彈出的提醒框。Status Bar是頂部狀態欄中出現的太有圖標的信息,用戶可以通過下拉狀態欄獲得具體信息並執行某些操作(如啓動Activity)。

通常,Status Bar用於通知某些操作已經完成,如下載文件完成。當用戶下拉狀態欄後,點擊該通知,可獲取詳細內容,如查看該下載的文件。


前臺Service
前臺Service用於動態通知消息,如天氣預報。該Service不易被kill。前臺Service必須提供status bar,只有前臺Service被destroy後,status bar才能消失。

舉例來說,一個播放音樂的Service必須是前臺Service,只有這樣用戶才能確知其運行狀態。爲前臺Service提供的status bar可以顯示當前音樂的播放狀態,並可以啓動播放音樂的Activity。

調用startForeground()可以啓動前臺Service。該方法接收兩個參數,參數一是一個int型變量,用戶指定該通知的唯一性標識,而參數而是一個Notification用於配置status bar,示例如下:

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);

調用stopForeground()來移除(remove)前臺Service。該方法需傳入一個boolean型變量,表示是否也一併清除status bar上的notification(indicating whether to remove the status bar notification as well)。該方法並不停止Service,如果停止正在前臺運行的Service,那麼notification 也一併被清除。


管理Service的生命週期
從Service的啓動到銷燬,有兩種流程:
A started service:需手動停止
A bound service:可自動停止

如下圖所示 :
這裏寫圖片描述
這兩條流程並不是毫無關係的:當調用startService()啓動一個Service後,我們仍可以bind該Service。例如,當播放音樂時,需調用startService()啓動指定播放的音樂,當需要獲取該音樂的播放進度時,又需要調用bindService(),在這種情況下,知道Service被unbind ,調用stopService() 或stopSelf()都不能停止該Service。


以上是Android官方文檔-Service的翻譯,翻譯了個大概,部分參考了別人的解讀方式。
附:Android官方文檔-Service

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章