Android四大組件-Service

http://blog.csdn.net/guolin_blog/article/details/11952435android

http://www.jianshu.com/p/eeb2bd59853f服務器

 

  • 概述

定義、特色:網絡

Service是能夠在後臺執行長時間(長生命週期)而又不與用戶產生UI交互(沒有用戶界面)的操做app

 

注意事項:
一、只能在後臺運行,即使用戶切換了其餘應用,啓動的Service仍可在後臺運行。
二、能夠和其餘組件進行Service綁定並與之交互,甚至是跨進程通訊(IPC)。
三、不能運行在一個獨立的進程當中,而是依賴與建立服務時所在的應用組件進程。
四、服務不會自動開啓線程,咱們須要在服務的內部手動建立子線程,並在這裏執行具體的任務。ide

 

應用場景舉例:
音樂播放:播放多媒體的時候用戶啓動了其餘Activity,此時要在後臺繼續播放。
記錄檢測:好比檢測SD卡上文件的變化;在後臺記錄你的地理信息位置的改變等。
其餘操做:網絡請求、執行文件讀寫操做或者與 content provider交互。佈局

 

類型:
本地服務與遠程服務this

本地服務依附在主進程上,在必定程度上節約了資源。本地服務由於是在同一進程,所以不須要IPC,也不須要AIDL。相應bindService會方便不少。缺點是主進程被kill後,服務變會終止。spa

遠程服務是獨立的進程,對應進程名格式爲所在包名加上你指定的android:process字符串。因爲是獨立的進程,所以在Activity所在進程被kill的是偶,該服務依然在運行。缺點是該服務是獨立的進程,會佔用必定資源,而且使用AIDL進行IPC稍微麻煩一點。本文第六部分將會簡單的講述這一進程間通訊方式。.net

對於startService來講,無論是本地服務仍是遠程服務,咱們須要作的工做都同樣簡單。線程

 

生命週期

  • 用法

而後新建一個MyService繼承自Service,並重寫父類的onCreate()、onStartCommand()和onDestroy()方法,以下所示:

public class MyService extends Service {  
  
    public static final String TAG = "MyService";  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.d(TAG, "onCreate() executed");  
    }  
  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        Log.d(TAG, "onStartCommand() executed");  
        return super.onStartCommand(intent, flags, startId);  
    }  
      
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        Log.d(TAG, "onDestroy() executed");  
    }  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return null;  
    }  
  
}

在Activity裏面加入啓動Service和中止Service的邏輯

public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:  
            Intent startIntent = new Intent(this, MyService.class);  
            startService(startIntent);  
            break;  
        case R.id.stop_service:  
            Intent stopIntent = new Intent(this, MyService.class);  
            stopService(stopIntent);  
            break;  
        default:  
            break;  
        }  
    }

另外須要注意,項目中的每個Service都必須在AndroidManifest.xml中註冊才行

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.example.servicetest"  
    android:versionCode="1"  
    android:versionName="1.0" >  
  
    <uses-sdk  
        android:minSdkVersion="14"  
        android:targetSdkVersion="17" />  
  
    <application  
        android:allowBackup="true"  
        android:icon="@drawable/ic_launcher"  
        android:label="@string/app_name"  
        android:theme="@style/AppTheme" >  
          
    ……  
  
        <service android:name="com.example.servicetest.MyService" >  
        </service>  
    </application>  
  
</manifest> 

 

經過startService方式(繼承Service類) 總結
一、啓動服務對象屢次啓動同時只會產生一個,onCreate()方法只會在Service第一次被建立的時候調用,屢次點擊啓動會執行屢次onStartCommand()方法,onDestroy()方法只會在Service第一次被中止的時候調用,屢次點擊中止不會報異常,也再也不執行onDestroy()方法。

二、一旦啓動,Service將一直運行在後臺(run in the background indefinitely)即使啓動Service的組件已被destroy。

三、中止一個started服務有兩種方法:
(1)在外部使用stopService()手動中止。
(2)在服務內部(onStartCommand方法內部)使用stopSelf()方法,使服務執行完畢後自動中止。好比說,一個start的Service執行在後臺下載或上傳一個文件的操做,完成以後,Service應本身中止。

四、onStartCommand方法的返回值:
onStartCommand方法執行時,返回的是一個int型。這個整型能夠有三個返回值:START_NOT_STICKY、START_STICKY、START_REDELIVER_INTENT
START_NOT_STICKY:「非粘性的」。使用這個返回值時,若是在執行完onStartCommand方法後,服務被異常kill掉,系統不會自動重啓該服務。
START_STICKY:若是Service進程被kill掉,保留Service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試從新建立Service,因爲服務狀態爲開始狀態,因此建立服務後必定會調用onStartCommand(Intent,int,int)方法。若是在此期間沒有任何啓動命令被傳遞到Service,那麼參數Intent將爲null。
START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,系統會自動重啓該服務,並將Intent的值傳入。

五、默認狀況下,一個started的Service與啓動他的組件在同一個線程中。上面的實例中,服務就是在主線程中運行的,若是是在服務中完成耗時操做的話,容易形成主線程阻塞。因此咱們能夠在服務中開啓一個子線程來完成耗時操做。

public class MyService extends Service {  
    public static final String TAG = "MyService";   
   //服務執行的操做
   @Override  
   public int onStartCommand(Intent intent, int flags, int startId) {  
       new Thread(new Runnable() {
           public void run() {
                //在子線程中處理具體的邏輯
                //在這裏咱們只作打印子線程id的操做
               Log.i("MyService",Thread.currentThread().getId()+"");
               stopSelf();  //服務執行完畢後自動中止
           }
       }).start();        
       return super.onStartCommand(intent, flags, startId);  
   }

   @Override
   public IBinder onBind(Intent intent) {
       // TODO Auto-generated method stub
       return null;
   }      
}

若是咱們不手動開啓線程,I/MyService: 177將會變成它依賴的主線程1,這就不能作耗時操做了。雖然說上面的這種寫法並不複雜,但總會有一些程序猿忘記開啓線程,或者忘記調用stopSelf()方法。
爲了能夠簡單地建立一個可開啓單獨線程、會自動中止的服務,Android專門提供了一個IntentService類,這個類就很好的解決了上面所提到的兩種尷尬。

 

IntentService類

IntentService的做用:
當咱們須要這樣一次性完成的任務時,就可使用IntentService來完成。

IntentService的用法:

1)新建一個MyIntentService類,繼承自IntentService,並重寫父類的onHandleIntent()方法,代碼以下:

public class MyIntentService extends IntentService{
    public MyIntentService() {
       //第一步:重寫父類的onHandleIntent()方法,這裏首先要提供一個無參的構造方法,
       //而且必須在其內部調用父類的有參構造方法,這裏咱們手動給服務起個名字爲:MyIntentService
       super("MyIntentService");
   }

     //第二步:重寫父類的onHandleIntent()方法,該方法在會在一個單獨的線程中執行,
     //來完成工做任務。任務結束後,該Service自動中止
   @Override
   protected void onHandleIntent(Intent intent) {
       for(int i = 0;i<3;i++) {
           //Service要執行的邏輯
           //這裏咱們只打印當前線程的id
           Log.d("MyIntentService","IntentService線程的id是:"+Thread.currentThread().getId());
           try {
               //線程睡眠一秒鐘
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }        
   }

   @Override
   public void onDestroy() {
       super.onDestroy();
       Log.d("MyIntentService","onDestroy");
   }
}

 

經過startService方式(繼承IntentService類) 總結
一、啓動一個IntentService和啓動一個普通的Service,步驟是類似的。
二、與直接繼承Service不一樣在於:經過繼承IntentService運行,自動開啓了單獨線程,並且完成任務後自動銷燬了Service。

 

【補充】Service和Thread的關係:
很多Android初學者均可能會有這樣的疑惑,Service和Thread到底有什麼關係呢?何時應該用Service,何時又應該用Thread?答案可能會有點讓你吃驚,由於Service和Thread之間沒有任何關係!
之因此有很多人會把它們聯繫起來,主要就是由於Service的後臺概念。Android的後臺就是指,它的運行是徹底不依賴UI的。即便Activity被銷燬,或者程序被關閉,只要進程還在,Service就能夠繼續運行。Thread咱們你們都知道,是用於開啓一個子線程,在這裏去執行一些耗時操做就不會阻塞主線程的運行。而Service咱們最初理解的時候,總會以爲它是用來處理一些後臺任務的,一些比較耗時的操做也能夠放在這裏運行,這就會讓人產生混淆了。可是,Service實際上是運行在主線程裏的,一些比較耗時的操做須要開啓單獨線程

 

經過bindService方式定義一個Service:(使用Bind Service完成Service和Activity之間的通訊):

 

Bind Service的引入:

有沒有什麼辦法能讓Service與組件的關聯更多一些呢?好比說在Activity中指揮Service去幹什麼,Service就去幹什麼。固然能夠,只須要讓Activity和Service創建關聯就行了。這時咱們就能夠經過bindService方式定義一個Service。

 

Bind Service的實現原理:

 

應用程序組件(客戶端)經過調用bindService()方法可以綁定服務,而後Android系統會調用服務的onBind()回調方法,則個方法會返回一個跟服務器端交互的Binder對象。

bindService()方法當即返回,而且不給客戶端返回IBinder對象。要接收IBinder對象,客戶端必須建立一個ServiceConnection類的實例,而且把這個實例傳遞給bindService()方法。ServiceConnection對象包含了一個系統調用的傳遞IBinder對象的回調方法。

 

Bind Service實現流程:

1)一直有一個onBind()方法咱們都沒有使用到,這個方法其實就是用於和Activity創建關聯的,修改MyService中的代碼,以下所示:

public class MyBindService01 extends Service {
  public static final String TAG = "MyBindService01";
  private MyBinder mBinder = new MyBinder();
  @Override
  public void onCreate() {
      super.onCreate();
      Log.d(TAG, "onCreate");
  }

  @Override
  public IBinder onBind(Intent intent) {
      return mBinder;  //在這裏返回新建的MyBinder類
  }

  @Override
  public boolean onUnbind(Intent intent) {
      Log.d(TAG, "onUnbind");
      return super.onUnbind(intent);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Log.d(TAG, "onStartCommand");
      return super.onStartCommand(intent, flags, startId);
  }

  @Override
  public void onDestroy() {
      super.onDestroy();
      Log.d(TAG, "onDestroy");
  }

  //MyBinder類,繼承Binder:讓裏面的方法執行下載任務,並獲取下載進度
  class MyBinder extends Binder {
      public void startDownload() {
          Log.d("TAG", "startDownload() executed");
          // 執行具體的下載任務  
      }
      public int getProgress(){
          Log.d("TAG", "getProgress() executed");
          return 0;
      }
  }
}
  • 新建一個MyBinder類,繼承Binder:讓裏面的方法執行下載任務,並獲取下載進度。固然,這裏只是兩個模擬方法,並無實現真正的功能,咱們經過打印日誌的形式來體現。
  • 接着建立MyBinder的實例,而後在onBind()方法裏返回這個實例。返回這個mBinder,是一個IBinder類型,就能夠把這個IBinder類型傳遞到MainActivity中,從而調用Service裏面的方法。

2)檢查清單文件,是否已經對Service進行註冊:
<service android:name=".MyBindService01" ></service>
3)讓咱們修改MainActivity和MyBindService01之間創建關聯

public class MainActivity extends Activity implements OnClickListener {
  private Button button1_bind_service;
  private Button button2_unbind_service;
  private MyBindService01.MyBinder myBinder;

  boolean mBound = false; //一開始,並無和Service綁定.這個參數是用來顯示綁定狀態

  //匿名內部類:服務鏈接對象
  private ServiceConnection connection = new ServiceConnection() {

      //當服務異常終止時會調用。注意,解除綁定服務時不會調用
      @Override
      public void onServiceDisconnected(ComponentName name) {
          mBound = false; //服務異常終止時,狀態爲未綁定
          //解決了屢次執行unbindService()方法引起的異常問題
      }

      //和服務綁定成功後,服務會回調該方法
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
          myBinder = (MyBindService01.MyBinder) service;
          //在Activity中調用Service裏面的方法
          myBinder.startDownload();
          myBinder.getProgress();
          mBound = true; //true說明是綁定狀態
      }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      button1_bind_service = (Button) findViewById(R.id.button1_bind_service);
      button2_unbind_service = (Button) findViewById(R.id.button2_unbind_service);
      button1_bind_service.setOnClickListener(this);
      button2_unbind_service.setOnClickListener(this);
  }
  @Override
  public void onClick(View v) {
      switch (v.getId()) {
      case R.id.button1_bind_service:
          Intent bindIntent = new Intent(this, MyService.class);
          bindService(bindIntent, connection, BIND_AUTO_CREATE);
//這裏傳入BIND_AUTO_CREATE表示在Activity和Service創建關聯後會自動建立Service(即便以前沒有建立
//Service也沒有關係),這會使得MyService中的onCreate()方法獲得執行,但onStartCommand()方法不會執行。
          break;
      case R.id.button2_unbind_service:
          //若是和Service是綁定的狀態,就解除綁定。
          if(mBound){
              unbindService(connection);
              mBound=false;
          }
          break;
      default:
          break;
      }
  }
}

說明:這裏咱們首先建立了一個ServiceConnection的匿名類,在裏面重寫了onServiceConnected()方法和onServiceDisconnected()方法,若是當前Activity與服務鏈接成功後,服務會回調onServiceConnected()方法
在onServiceConnected()方法中,咱們又經過向下轉型獲得了MyBinder的實例,有了這個實例,Activity和Service之間的關係就變得很是緊密了。如今咱們能夠在Activity中根據具體的場景來調用MyBinder中的任何public方法,即實現了Activity指揮Service幹什麼Service就去幹什麼的功能。

 

固然,如今Activity和Service其實還沒關聯起來了呢,這個功能是在Bind Service按鈕的點擊事件裏完成的。能夠看到,這裏咱們仍然是構建出了一個Intent對象,而後調用bindService()方法將Activity和Service進行綁定。bindService()方法接收三個參數,第一個參數就是剛剛構建出的Intent對象,第二個參數是前面建立出的ServiceConnection的實例,第三個參數是一個標誌位,這裏傳入BIND_AUTO_CREATE表示在Activity和Service創建關聯後自動建立Service,這會使得MyService中的onCreate()方法獲得執行,但onStartCommand()方法不會執行。

 

經過bindService方式 總結
一、onCreate()、onBind()方法只會在Service第一次被建立的時候調用,屢次點擊綁定啓動不會執行任何方法,onUnbind()、onDestroy()方法會在調用者執行unbindService()方法時執行或者Activity退出時自動執行。

二、若是咱們既經過startService()開啓Service,又用經過bindService開啓,必要unbindService()和stopService()都執行一次(沒有前後順序),Service纔會被銷燬。

三、若是屢次執行unbinsService()方法,程序會異常退出,咱們須要在代碼中加一個判斷是否綁定的標記mBound來解決此問題,上面代碼中有說明。

 case R.id.button4_unbind_service:
          //若是和Service是綁定的狀態,就解除綁定。
          if(mBound){
              unbindService(connection);
              mBound=false;
          }
          break;

四、當在旋轉手機屏幕的時候,當手機屏幕在「橫」「豎」變換時,此時若是你的 Activity 若是會自動旋轉的話,旋轉實際上是 Activity 的從新建立,所以旋轉以前的使用 bindService 創建的鏈接便會斷開(Context 不存在了)。

五、只有Activity、Service、Content Provider可以綁定服務;BroadcastReceiver廣播接收器不能綁定服務。

 

、bindService和startService混合使用:

  • 若是先startService,再bindService:
    在bind的Activity退出的時候,Service會執行unBind方法而不執行其onDestory方法,由於有startService方法調用過,
    因此Activity與Service解除綁定後會有一個與調用者沒有關連的Service存在。
  • 若是先bindService,再startService,再調用Context.stopService
    Service的onDestory方法不會馬上執行,由於有一個與Service綁定的Activity,可是在Activity退出的時候,會執行其(Service的)onDestory方法,若是要馬上執行stopService,就得先解除綁定。

  • 若是先是bind了,那麼start的時候就直接運行Service的onStartCommand()方法,若是先是start,那麼bind的時候就直接運行onBind()方法。

  • 當一個服務沒被onDestory()銷燬以前,只有第一個啓動它的客戶端能調用它的onBind()和onUnbind()。

 

4、startService、bindService區別大總結

一、生命週期不一樣。(詳見圖)
二、屢次啓動,前者會屢次執行onStartCommand()方法,後者什麼都不執行。屢次中止,前者只會執行一次onDestroy()方法,後者報異常信息。
三、當啓動Service的組件已被Destroy的時候,前者不中止,後者會中止。
四、前者中止直接執行onDestroy()方法(Service中的),後者則先解除綁onUnbind()定再執行onDestroy()方法(Service中的)。
五、當手機屏幕在「橫」「豎」變換時,前者建立的Service不會中止,後者會隨着Activity的重建而中止。
六、後者的onBind回調方法將返回給客戶端一個IBinder接口實例,IBinder容許客戶端回調服務的方法,好比獲得Service運行的狀態或其餘操做。而這些操做前者啓動的Service是沒有的。

 

5、在AndroidManifest.xml裏Service元素常見選項

android:name   -- 服務類名
android:label  --  服務的名字,若是此項不設置,那麼默認顯示的服務名則爲類名
android:icon -- 服務的圖標
android:permission -- 申明此服務的權限,這意味着只有提供了該權限的應用才能控制或鏈接此服務
android:process -- 表示該服務是否運行在另一個進程,若是設置了此項,那麼將會在包名後面加上這段字符串表示另外一進程的名字
android:enabled --表示是否能被系統實例化,爲true表示能夠,爲false表示不能夠,默認爲true
android:exported -- 表示該服務是否可以被其餘應用程序所控制或鏈接,不設置默認此項爲 false

 

建立前臺Service

 

Service幾乎都是在後臺運行的,一直以來它都是默默地作着辛苦的工做。可是Service的系統優先級仍是比較低的,當系統出現內存不足狀況時,就有可能會回收掉正在後臺運行的Service。若是你但願Service能夠一直保持運行狀態,而不會因爲系統內存不足的緣由致使被回收,就能夠考慮使用前臺Service。前臺Service和普通Service最大的區別就在於,它會一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄後能夠看到更加詳細的信息,很是相似於通知的效果。固然有時候你也可能不只僅是爲了防止Service被回收才使用前臺Service,有些項目因爲特殊的需求會要求必須使用前臺Service,好比說墨跡天氣,它的Service在後臺更新天氣數據的同時,還會在系統狀態欄一直顯示當前天氣的信息,以下圖所示:

 

那麼咱們就來看一下如何才能建立一個前臺Service吧,其實並不複雜,修改MyService中的代碼,以下所示:

public class MyService extends Service {  
  
    public static final String TAG = "MyService";  
  
    private MyBinder mBinder = new MyBinder();  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Notification notification = new Notification(R.drawable.ic_launcher,  
                "有通知到來", System.currentTimeMillis());  
        Intent notificationIntent = new Intent(this, MainActivity.class);  
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,  
                notificationIntent, 0);  
        notification.setLatestEventInfo(this, "這是通知的標題", "這是通知的內容",  
                pendingIntent);  
        startForeground(1, notification);  
        Log.d(TAG, "onCreate() executed");  
    }  
  
    .........  
  
} 

這裏只是修改了MyService中onCreate()方法的代碼。能夠看到,咱們首先建立了一個Notification對象,而後調用了它的setLatestEventInfo()方法來爲通知初始化佈局和數據,並在這裏設置了點擊通知後就打開MainActivity。而後調用startForeground()方法就可讓MyService變成一個前臺Service,並會將通知的圖片顯示出來。

 

如今從新運行一下程序,並點擊Start Service或Bind Service按鈕,MyService就會之前臺Service的模式啓動了,而且在系統狀態欄會彈出一個通欄圖標,下拉狀態欄後能夠看到通知的詳細內容

相關文章
相關標籤/搜索