Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android-Service組件

Android-Service組件

編輯:關於Android編程

 

Service是一個android的四大組件之一,它沒有UI界面,可以在後台執行長時間的操作。其他的組件可以start一個Service,service啟動以後會在後台持續運行,不管用戶是否切換到了其他的應用程序。此外,其他的組件還可以綁定到service上,並和service做交互,甚至還可以執行跨進程的操作(IPC)。比如說,Service可以在後台處理網絡請求、播放音樂,執行文件i/o或者跟content provider交互。

有兩種形式的service:
Started
當其他的組件通過調用startService()啟動的service是“Started”。一旦啟動以後,service就在後台無限的運行下去,就算是啟動他的組件已經被銷毀了。通常來說,Started service只會做單一的操作,並且不會返回給調用者返回值。比如:它可以通過網絡下載或者是上傳文件。當操作結束的時候,service要手動把自己停止掉。

Bound
當其他組件通過調用bindService()綁定到service的時候,service就是 bound的。bound service提供了客戶端/服務端的接口,允許組件和service進行交互,發送請求,獲取結果,甚至是做跨進程的操作。bound service只存活於其他組件綁定在它上面的時候,多個組件可以同時綁定到一個service,但是,當所有的組件同時解綁以後,service就被銷毀了。

盡管本文檔是分開討論這兩種類型的service的,但是你的service可以同時支持這兩種類型,service可以是started(無限運行)同時還允許綁定。這僅僅取決於你是否實現了下面的回調:onStartCommand()允許其他的組件start一個service,onBind()允許綁定service。

不管你的service是started還是bound,應用的其他組件都可以使用這個service(甚至是別的應用)。就如同別的組件都可以使用一個Activity一樣,通過Intent來啟動它。但是,你也可以在manifest文件中把service聲明成應用私有的,這就阻止了別的應用訪問它。

 

更多信息請參考“在manifest文件中聲明service”(http://developer.android.com/guide/components/services.html#Declaring)這一節。

注意:service是運行在宿主進程的主線程的,service並不會創建它自己的線程,並不會運行在單獨的進程中,除非你指定了。這意味著,如果你的service要做一些耗CPU或者是阻塞的操作(比如:播放MP3或者是網絡請求),你需要在service內部手動創建一個新的線程來做這樣的操作。通過使用單獨的線程,會降低ANR的風險,讓主線程可以專注於UI操作。

service基礎

要創建一個service,必須要創建Service(或者是Service子類)的子類。在你的實現中,你要重寫一些回調方法,它們用來處理service的一些關鍵的生命周期,並且提供組件綁定到service的機制,如果合適的話。

要重寫的最重要的回調是:

onStartCommand()

當別的組件通過調用startService()來啟動service的時候,系統會調用這個方法。一旦這個方法開始執行,service就啟動起來並在後台無限運行。如果你覆蓋了這個方法,你還要負責在service的工作結束以後通過調用stopSelf()或者stopService()銷毀掉service。(如果你只是想提供綁定就不需要實現這個方法)

onBind()
當其他的組件通過調用bindService()綁定到service(比如做RPC)的時候,系統會調用這個方法。實現這個方法的時候,必須要通過返回一個IBinder來給客戶端提供一個用來和service進行交互的接口。一般都要實現這個方法,但是如果你不允許綁定的話,可以返回null。

onCreate()
當service首次啟動的時候,系統會調用這個方法,只會執行一次(在調用onStartCommand()或者onBind()之前)。如果service已經在運行了,這個方法是不會被調用的。

onDestroy()
當service不再被使用或者是被銷毀的時候系統會調用這個方法。你的service要實現這個方法來做一些清理資源的工作,比如:線程啊,監聽啊,廣播接收器啊等等。這是service收到的最後一個回調。

如果一個組件通過startService()啟動一個service(會導致系統調用onStartCommand()),service會一直運行,一直到它調用stopSelf()或者是別的組件調用stopService()來把它停止掉。

如果一個組件通過bindService()啟動一個service(onStartCommand()不會被調用),service只存活於綁定到它的組件上。一旦service跟所有的客戶端解綁,它就會被系統結束掉。

Android系統只會在系統的內存不夠用的時候才會強制殺掉service,系統必須能夠給擁有用戶焦點的activity提供系統資源。
如果service被綁定到了擁有用戶焦點的activity上,它很可能不會被殺死。
如果service聲明成了前台運行的,一般不會被殺死。
其他情況下,如果service是被started並且是長時間運行的,系統會隨著時間的推移把它放到後台任務列表的靠後的位置,它就會變得易於被殺掉。
如果你的service是被started,你必須要讓它能優雅的處理系統的restart。
如果系統殺掉了你的service,一旦資源可用,系統會立馬restart被殺掉的service(當然這也取決於onStartCommand()的返回值)。
想了解更多關於系統可能會殺掉service的信息,參考:進程和線程(http://developer.android.com/guide/components/processes-and-threads.html)。

下面的章節,你會學到如何創建各種類型的service,還有如何在其他組件中使用service。

在manifest文件中聲明service

跟Activity類似(還有其他組件),你必須要在manifest文件中聲明所有的service。

要聲明一個service,添加元素作為的子元素,比如:


...


...




想了解更多關於在manifest聲明service的信息,參考元素大全(http://developer.android.com/guide/topics/manifest/service-element.html)。

元素還有很多其他的屬性,可以用來定義啟動service的權限或者是service運行的進程。android:name屬性是唯一必須的,它指定了service的類名。一旦你發布了應用以後,就不應該再修改這個名字了。假如你修改了,就會讓用明確Intent來start或者是bind service的代碼有crash的風險。

為了確保你的應用是安全的,當start或者是bind一個service的時候總是使用明確的intent,並且不要給service聲明intent filter。如果允許不明確的方式來啟動service非常重要,你可以給service提供intent filter,排除掉某些組件名字。但是,你必須要用setPackage()給intent設置pachage,這給要調用的目標service提供了足夠的確定性。

此外,你也可以用過引入android:exported這個屬性,並把它的值設置為false來確保你的service只是對你的應用可用。這就有效地防止了其他的應用啟動你的service,就算是使用明確intent也沒有用。

創建一個Started Service

如果別的組件通過調用startService()啟動service的話,service就是一個started service,這會導致系統調用servicve的onStartCommand()方法。

當service啟動以後,它的生命周期跟啟動它的組件的生命周期是相互獨立的,service可以在後台無限的運行,就算啟動它的組件已經銷毀了。這種情況下,當它的任務完成以後,service就需要調用stopSelf()來停掉自己,其他的組件可以調用stopService()來停掉service。

應用程序的組件比如Activity可以通過調用 startService()來啟動一個service,並且給service傳遞一個Intent,Intent就指定了要啟動的service和傳遞給service使用的數據。service會在onStartCommand()方法中收到這個Intent。

舉個例子,假如一個Activity需要把數據保存到網絡的數據庫上,Activity可以啟動一個service,通過intent傳遞給service要保存的數據。service在onStartCommand()方法中收到intent,連上網絡,做數據庫操作。當操作完成以後,service要停掉自己,然後servive就被銷毀了。

注意:service是運行在應用的同一個進程中,默認是運行在應用的主線程的。因此,如果當用戶跟應用進行交互的時候,service做一些耗時或者是阻塞的操作的話,service會拖慢Activity的性能。為了避免影響應用的性能,你要在service內部新開一個線程。

一般來說,可以通過繼承兩個類來創建started service:

一個是繼承Service
Service是所有service的基類。當你繼承這個類的時候,要在service內部新開一個線程做繁重的操作,因為service默認是運行在應用的主線程的,它會影響應用的性能。

還一個是繼承IntentService
它是Service的子類,它內部使用了一個工作線程來處理所有的請求,一次一個。如果你的service不需要同步與處理多個請求的話,這將是最佳的選擇。你所要做的僅僅是實現onHandleIntent()方法,它會接收請求的intent,因此你就可以做後台任務。

下面的章節講述了如何用這兩個類實現service。

繼承IntentService類

因為大多數的started service都不需要處理並發的請求,因此使用IntentService可能是最優的方案。

IntentService會做如下的事情:

(1)創建一個默認的工作線程,它用來執行傳遞到onStartCommand()的intent,並且是跟應用的主線程獨立的。
(2)創建一個工作隊列。每次只傳遞一個intent到onHandleIntent()方法,因此就不需要擔心多線程的問題了。
(3)當所有的請求都處理完以後,service會自動停止掉,因此不需要調用stopSelf()。
(4)提供了onBind()的默認實現,方法會返回null。
(5)提供了 onStartCommand()的默認實現,會把intent發送到工作隊列,然後發送給onHandleIntent()。

所有這些以後,你所需要做的僅僅是實現onHandleIntent()來完成客戶的工作(當然你也可以給service提供構造函數)。

下面是一個實現了IntentService的例子:

 

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) {
              }
          }
      }
  }
}

這就是你要做的全部的事情:一個構造函數和實現onHandleIntent()。

如果你想覆蓋別的回調比如onCreate(), onStartCommand(),或者onDestroy(),一定要記得調用父類的實現,這樣IntentService才可以正確的處理工作線程的生命周期。

比如下面,onStartCommand()必須要返回默認的實現(跟intent被傳遞到onHandleIntent()一樣)。

@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類

正如在上節中看到的那樣,使用IntentService讓實現一個started service變得很簡單。但是,如果你的service需要處理多線程(不是通過工作隊列來處理請求),那麼你可以繼承Service類來處理intent。

作為一個對比,下面的例子是一個實現了Service類來完成上面使用IntentService同樣的功能,也就是,給每一個請求,都使用工作線程來完成工作,一次只處理一個請求。

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

可以看出來,與使用IntentService相比要做很多額外的工作。

但是,因為你在onStartCommand()中處理請求,所以你可以並發處理多個請求。這個就超出這個例子的范圍了。但是如果你需要這麼做,你可以給每個請求創建一個線程然後立即執行(而不是等待前一個請求執行結束)。

要注意的是,onStartCommand()必須要返回一個整數返回值。返回值描述了service被系統殺掉以後又被restart的時候,系統如何處理service(正如之前討論的那樣,IntentService默認會幫你做這個事,當然你也可以修改)。返回值必須是下面的幾個常量:

START_NOT_STICKY
如果onStartCommand()返回以後系統殺掉了service,restart的時候不會重新創建service,除非有pending intent要發送。這是最安全的方式來避免運行不必要的service,並且應用可以簡單地重新開始任何沒完成的任務。

START_STICKY
如果onStartCommand()返回以後系統殺掉了service,restart的時候會重新創建service,然後調用onStartCommand(),但是不會再次發送最後的intent。相反,系統會用null的intent來調用onStartCommand(),除非是有pending intent來啟動service,這種情況下,這些intent是會傳遞進去的。這對媒體播放器(或者類似的service)來說是非常合適的,因為它們不執行命令,只是在無限的運行,等待新的任務。

START_REDELIVER_INTENT

如果onStartCommand()返回以後系統殺掉了service,restart的時候會重新創建service,然後調用onStartCommand(),並且會再次發送最後的intent。pending intents會被依次發送,這適合於那種需要立即被喚醒的情況,比如:下載文件。

了解更多關於返回值的信息,參考每一個常量的文檔。

啟動Service

你可以在Activity中後者是其他組件中通過給startService()傳遞intent(指定要啟動的service)來啟動service。Android系統會調用service的onStartCommand()方法然後把Intent傳遞進去(禁止直接調用onStartCommand())。

比如,Activity可以在startService()中用明確的intent啟動前面章節中的例子service(HelloService)。

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

startService()方法會立即返回,Android系統會調用service的onStartCommand()方法。如果service還沒有執行過,系統會首先調用onCreate()然後再調用onStartCommand()。

如果service不提供綁定,startService()方法傳遞進來的intent是唯一的跟外部組件進行通訊的方式。但是,如果你想讓service發送結果出去,啟動service的客戶端可以創建一個做廣播用的PendingIntent,(用getBroadcast()),把PendingIntent傳遞給service。service就可以使用廣播來傳遞結果。

多個啟動service的請求會導致相應的對service的多個onStartCommand()調用。但是,只需要一個停止service的請求就可以停掉service。

停掉service

started service必須要自己管理生命周期。也就是說,系統不會停止或者銷毀service除非是為了釋放系統資源,service在onStartCommand()返回以後會繼續運行。因此,service必須要調用stopSelf()停掉自己或者是其他組件調用stopService()來停掉service。

一旦用stopSelf()或者stopService()請求停掉service,系統就會盡快的將service銷毀掉。

但是,如果你的service在onStartCommand()中並發的處理請求,當你處理完一個請求以後你不應該就停掉service,因為你可能又收到了一個新的請求(第一個請求結束後停掉service也會停掉第二個請求)。為了避免這樣的事情,你可以使用stopSelf(int)來確保停止service的請求總是基於最近的請求。也就是說,當你調用stopSelf(int)的時候,你要傳遞對應停止請求的啟動請求ID(startId會被傳遞到onStartCommand()中)。然後,如果service在調用stopSelf(int)之前,又收到了一個啟動請求,ID匹配不上,service就不會停止。

注意:在service工作完成的時候把service停掉對於避免浪費系統資源和節省電量是很重要的。如果需要的話,其他的組件可以調用stopService()來停掉service。啟用了binding的service,如果收到了onStartCommand()調用的話,也要手動停掉service。

了解更多關於service生命周期的知識,查看“管理Service的生命周期”下面的章節。

創建Bound Service

bound service允許應用的組件調用bindService()綁定到service上,為了創建長時間存在的連接(一般不允許組件調用startService()來啟動)。

當你想從Activity或者是其他組件跟service進行交互,或者是通過IPC暴漏應用的某些功能給其他應用的時候,你應該創建bound service。

要創建一個bound service,必須要實現onBind()回調,並且返回IBinder,它定義了跟service通訊的接口。其他的組件可以調用bindService()來檢索出接口,然後調用service的方法。service只存活於它綁定到的組件上,所以,當沒有組件綁定的時候,系統會銷毀它(你不需要像停掉started service那樣來停掉bound service)。

要創建一個bound service,首先要做的是定義客戶端跟service交互的接口。service和客戶端之間的接口必須是IBinder的實現,並且service必須要在onBind()回調中返回,一旦客戶端收到了IBinder,它就可以通過接口跟service交互。

多個客戶端可以同時綁定到同一個service上,當一個客戶端跟service交互完成以後,它會調用unbindService()來解綁。如果沒有客戶端綁定到service上,系統就會把service銷毀掉。

有很多種實現bound service的方式,一般實現要比started service更復雜,所以bound service會在單獨的文章(http://developer.android.com/guide/components/bound-services.html)中做討論。

給用戶發送Notification

一旦運行以後,service可以使用彈出Notification或者是狀態欄Notification給用戶發送提醒事件。

彈出提醒是出現在當前窗口之上停留一段時間然後消失的消息提示,狀態欄提醒在狀態欄的消息中提供了一個icon,用戶可以選中做一些操作(比如啟動一個Activity)。

一般來說,當後台任務完成以後,用狀態欄提醒是最佳的方式(比如:文件下載完成),然後用戶可以做一些動作。當用戶在展開的視圖中選擇一個提醒以後,提醒可以開啟一個Activity(比如跳轉到下載文件的view)。

前台運行service

 


前台service是被認為是用戶可感知的,當系統內存低的時候不是候選被殺掉的service。前台service必須在狀態欄有提醒,放在“正在進行”的頭部之下,也就是說,提醒不會消失除非service被停止或者被從前台移除。

比如說,service的音樂播放器應該運行在前台,因為用戶明確感知到它的操作,狀態欄的提醒可以表明當前正在播放的歌曲,並且允許用戶跳轉到音樂播放器的Activity做一些交互。

讓service運行在前台需要調用startForeground()方法。這個方法接受2個參數,一個唯一標識提醒的整數,一個是狀態欄的提醒。比如:
 

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

注意: ID一定不能為0.

要從前台移除service,需要調用stopForeground()。這個方法接收一個boolean參數,用以表明是否移除狀態欄的提醒。這個方法不會停掉service,但是,如果你想在service運行在前台的時候停掉它,提醒也會被移除掉。

管理service的生命周期

service的生命周期要遠比Activity的生命周期簡單。但是,你需要更加注意service的創建和銷毀,因為service可以在用戶不知曉的情況下在後台運行。

service生命周期-從創建到銷毀-遵循下面的2中不同的形式:

(1)started service
當其他組件調用startService()的時候service被創建出來,然後就無限的運行,它必須要手動調用stopSelf()來停掉自己。其他的組件可以調用stopService()來停掉它。當service停掉以後,系統會銷毀它。

 


(2)bound service
當其他組件(客戶端)調用bindService()時候service被創建出來,然後客戶端通過IBinder接口跟service進行交互。客戶端可以調用unbindService()來關掉與service的連接。多個客戶端可以同時連接到一個service上,當所有的客戶端都解綁以後,系統會銷毀掉service(service不需要自己手動去停掉)。

這兩種形式並不是完全獨立的。也就是說,你可以綁定到一個用startService()啟動的started service上。比如:後台的音樂service可以通過startService()傳遞要播放的歌曲的intent來啟動起來,然後,當用戶希望對播放器做一些操作或者是獲取當前歌曲的信息的時候,可以把Activity通過bindService()綁定到service上。這種情況下,stopService()或者stopSelf()並不會停掉service,直到所有的客戶端都解綁以後。

實現生命周期回調

類似於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生命周期內部的2個循環:

(1)service的完整的生命周期是從調用onCreate()到onDestroy()返回。跟activity類似,service也是在onCreate()中做初始化,在onDestroy()中釋放資源。比如,音樂播放器service可以在onCreate()中創建播放音樂的線程,在onDestroy()中停掉這個線程。所有的service都會調用onCreate()和onDestroy(),不管service是用startService()還是bindService()創建出來。

(2)service的活動的時間是從調用onStartCommand()或者onBind()開始的.與此對應,這兩個方法會處理通過startService()或者bindService()傳遞進去的intent。如果service是被started,當service的生命周期結束的時候也就是活動時間結束的時候(從onStartCommand()返回以後,service仍然是活動的)。如果service是bound的,onUnbind()返回的時候,service的生命也就結束了。

注意:如果一個started service是通過調用stopSelf()或者stopService()被停掉的,這時候並沒有與之對應的回調(沒有onStop()回調)。
因此,除非service是被綁定到了一個客戶端上,否則一旦service停掉,系統就會去銷毀它,onDestroy()是service收到的唯一的回調。


 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved