基礎總結篇之四:Service完全解析

轉載http://blog.csdn.net/liuhe688/article/details/6874378

富貴必從勤苦得,男兒須讀五車書。唐.杜甫《柏學士茅屋》

作爲程序員的我們,須知富貴是要通過勤苦努力才能得到的,要想在行業內有所建樹,就必須刻苦學習和鑽研。

今天我們來講一下Android中Service的相關內容。

Service在Android中和Activity是屬於同一級別上的組件,我們可以將他們認爲是兩個好哥們,Activity儀表不凡,迷倒萬千少女,經常做一些公衆人物角色,而Service一副彪悍的長相,但卻身強力壯,常常在後臺做一些搬運工的力氣活,雖然有些累,但大家都不能失去他。

下面我們就圍繞Service對其進行全面講解:

1.Service生命週期

Service生命週期可以從兩種啓動Service的模式開始講起,分別是context.startService()和context.bindService()

(1).startService的啓動模式下的生命週期:當我們首次使用startService啓動一個服務時,系統會實例化一個Service實例,依次調用其onCreate和onStartCommand方法,然後進入運行狀態,此後,如果再使用startService啓動服務時,不再創建新的服務對象,系統會自動找到剛纔創建的Service實例,調用其onStart方法;如果我們想要停掉一個服務,可使用stopService方法,此時onDestroy方法會被調用,需要注意的是,不管前面使用了多個次startService,只需一次stopService,即可停掉服務。

(2).bindService啓動模式下的生命週期:在這種模式下,當調用者首次使用bindService綁定一個服務時,系統會實例化一個Service實例,並一次調用其onCreate方法和onBind方法,然後調用者就可以和服務進行交互了,此後,如果再次使用bindService綁定服務,系統不會創建新的Service實例,也不會再調用onBind方法;如果我們需要解除與這個服務的綁定,可使用unbindService方法,此時onUnbind方法和onDestroy方法會被調用。

兩種模式有以下幾點不同之處:startService模式下調用者與服務無必然聯繫,即使調用者結束了自己的生命週期,只要沒有使用stopService方法停止這個服務,服務仍會運行;通常情況下,bindService模式下服務是與調用者生死與共的,在綁定結束之後,一旦調用者被銷燬,服務也就立即終止,就像江湖上的一句話:不求同生,但願同死。

值得一提的是,以前我們在使用startService啓動服務時都是習慣重寫onStart方法,在Android2.0時系統引進了onStartCommand方法取代onStart方法,爲了兼容以前的程序,在onStartCommand方法中其實調用了onStart方法,不過我們最好是重寫onStartCommand方法。

以上兩種模式的流程如下圖所示:


下面我們就結合實例來演示一下這兩種模式的生命週期過程。我們新建一個名爲service的項目,然後創建一個MyService的服務類,代碼如下:

[java] view plain copy
  1. package com.scott.service;  
  2.   
  3. import android.app.Service;  
  4. import android.content.Intent;  
  5. import android.os.IBinder;  
  6. import android.util.Log;  
  7.   
  8. public class MyService extends Service {  
  9.   
  10.     private static final String TAG = "MyService";  
  11.       
  12.     @Override  
  13.     public void onCreate() {  
  14.         super.onCreate();  
  15.         Log.i(TAG, "onCreate called.");  
  16.     }  
  17.       
  18.     @Override  
  19.     public int onStartCommand(Intent intent, int flags, int startId) {  
  20.         Log.i(TAG, "onStartCommand called.");  
  21.         return super.onStartCommand(intent, flags, startId);  
  22.     }  
  23.       
  24.     @Override  
  25.     public void onStart(Intent intent, int startId) {  
  26.         super.onStart(intent, startId);  
  27.         Log.i(TAG, "onStart called.");  
  28.     }  
  29.       
  30.     @Override  
  31.     public IBinder onBind(Intent intent) {  
  32.         Log.i(TAG, "onBind called.");  
  33.         return null;  
  34.     }  
  35.       
  36.     @Override  
  37.     public boolean onUnbind(Intent intent) {  
  38.         Log.i(TAG, "onUnbind called.");  
  39.         return super.onUnbind(intent);  
  40.     }  
  41.       
  42.     @Override  
  43.     public void onDestroy() {  
  44.         super.onDestroy();  
  45.         Log.i(TAG, "onDestroy called.");  
  46.     }  
  47. }  
然後再AndroidManifest.xml中配置服務信息,不然這個服務就不會生效,配置如下:

[html] view plain copy
  1. <service android:name=".MyService">  
  2.             <intent-filter>  
  3.                  <action android:name="android.intent.action.MyService" />  
  4.                  <category android:name="android.intent.category.DEFAULT" />  
  5.             </intent-filter>  
  6.         </service>  
如果服務只是在本應用中使用,大可以去掉<intent-filter>屬性。

服務搭建完成之後,我們就來關注一下調用者MainActivity,它很簡單,只有兩個按鈕,一個是啓動服務,另一個是停止服務,我們來看一下他們的點擊事件:

[java] view plain copy
  1. /** 
  2.  * 啓動服務 
  3.  * @param view 
  4.  */  
  5. public void start(View view) {  
  6.     Intent intent = new Intent(this, MyService.class);  
  7.     startService(intent);  
  8. }  
  9.   
  10. /** 
  11.  * 停止服務 
  12.  * @param view 
  13.  */  
  14. public void stop(View view) {  
  15.     Intent intent = new Intent(this, MyService.class);  
  16.     stopService(intent);  
  17. }  
接下來我們就先點擊一次啓動按鈕,看看都發生了些什麼。日誌打印結果如下:


當然我們覺得還不過癮,再點擊一次,我們會發現結果略有不同:


我們看到第二次點擊時onCreate方法就不再被調用了,而是直接調用了onStartCommand方法(onStartCommand中又調用了onStart方法)。我們選擇“Settings->Application s->Running services”就會發現我們剛剛啓動的服務:


然後我們點擊停止按鈕,試圖停止服務,我們發現如下現象:


我們會發現onDestroy方法被調用了,此時服務就停止運行了。我們再次查看“Running services”,就會發現MyService這個服務已全無蹤跡。

在這個過程中,onBind方法和onUnbind方法始終沒被調用,我們下面就讓這兩位show一下自己。

我們修改一下MainActivity的代碼,使其可以可以以bindService的方式啓動一個服務,代碼如下:

[java] view plain copy
  1. private ServiceConnection conn = new ServiceConnection() {  
  2.       
  3.     @Override  
  4.     public void onServiceConnected(ComponentName name, IBinder service) {  
  5.         //connected  
  6.         Log.i(TAG, "onServiceConnected called.");  
  7.     }  
  8.   
  9.     /** 
  10.      *  Called when a connection to the Service has been lost. 
  11.      *  This typically happens when the process hosting the service has crashed or been killed. 
  12.      *  This does not remove the ServiceConnection itself. 
  13.      *  this binding to the service will remain active, 
  14.      *  and you will receive a call to onServiceConnected when the Service is next running. 
  15.      */  
  16.     @Override  
  17.     public void onServiceDisconnected(ComponentName name) {  
  18.     }  
  19. };  
  20.   
  21. /** 
  22.  * 綁定服務 
  23.  * @param view 
  24.  */  
  25. public void bind(View view) {  
  26.     Intent intent = new Intent(this, MyService.class);  
  27.     bindService(intent, conn, Context.BIND_AUTO_CREATE);  
  28. }  
  29.   
  30. /** 
  31.  * 解除綁定 
  32.  * @param view 
  33.  */  
  34. public void unbind(View view) {  
  35.     unbindService(conn);  
  36. }  
在使用bindService綁定服務時,我們需要一個ServiceConnection代表與服務的連接,它只有兩個方法,onServiceConnected和onServiceDisconnected,前者是在操作者在連接一個服務成功時被調用,而後者是在服務崩潰或被殺死導致的連接中斷時被調用,而如果我們自己解除綁定時則不會被調用,所以我們這裏只研究onServiceConnected這個方法。

看樣子是可以去綁定一個服務了,其實還不行,因爲我們前面服務中的onBind方法返回值爲null,這樣是不行的,要想實現綁定操作,必須返回一個實現了IBinder接口類型的實例,該接口描述了與遠程對象進行交互的抽象協議,有了它我們才能與服務進行交互。我們於是有了這樣的代碼:

[java] view plain copy
  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     Log.i(TAG, "onBind called.");  
  4.     return new Binder() {};  
  5. }  
我們返回了一個Binder的實例,而這個Binder恰恰是實現了IBinder接口,所以這樣就可以實現綁定服務的操作了,一起來演示一下。

先點擊一下綁定按鈕,我們會發現在MainActivity中打印日誌如下:


似的,onServiceConnected方法被調用了,看來綁定連接已經成功了,看看MyService如何:


onCreate方法和onBind方法被調用了,此時服務已進入運行階段,如果再次點擊綁定按鈕,onCreate和onBinder並不會再次被調用,這個過程中它們僅被調用一次。

然後點擊解除綁定按鈕,我們會發現MyService打印如下:


可以看到onUnbind方法和onDestroy方法被調用了,此時MyService已被銷燬,整個生命週期結束。

另一方面,當我們退出MainActivity時,服務也會隨之而結束,從這一點上看,MyService可以說是誓死追隨着MainActivity。

需要注意的是,在連接中斷狀態再去做解除綁定操作會引起一個異常,在MainActivity銷燬之前沒有進行解除綁定也會導致後臺出現異常信息,此時我們就要想辦法確保不會出現此類情況,可以這樣做:

[java] view plain copy
  1. private boolean binded;  
  2.   
  3. @Override  
  4. public void onServiceConnected(ComponentName name, IBinder service) {  
  5.     binded = true;  
  6. }  
  7.   
  8. /** 
  9.  * 解除綁定 
  10.  * @param view 
  11.  */  
  12. public void unbind(View view) {  
  13.     unbindService();  
  14. }  
  15.   
  16. @Override  
  17. protected void onDestroy() {  
  18.     super.onDestroy();  
  19.     unbindService();  
  20. }  
  21.   
  22. /** 
  23.  * 解除服務綁定 
  24.  */  
  25. private void unbindService() {  
  26.     if (binded) {  
  27.         unbindService(conn);  
  28.         binded = false;  
  29.     }  
  30. }  

以上就是bindService的生命週期,正如我們上面講的一樣,使用bindService啓動服務後調用者和服務綁定到了一起,當調用者被銷燬,服務也立即結終止。

通常情況下是這樣的,不過也有特殊情況。當startService和bindService在同一場合下使用時,就會出現稍微不同的現象。

如果我們先以startService方式啓動服務,然後再用bindService綁定到這個服務,之後使用unbindService解除綁定,此時服務並不會因此而終止,而是繼續運行,直到我們使用stopService來停止這個服務。下面我們再修改一下代碼以驗證這個過程。MyService保持不變,我們只需修改一下MainActivity。MainActivity最新代碼如下:

[java] view plain copy
  1. package com.scott.service;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ComponentName;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.ServiceConnection;  
  8. import android.os.Bundle;  
  9. import android.os.IBinder;  
  10. import android.util.Log;  
  11. import android.view.View;  
  12.   
  13. public class MainActivity extends Activity {  
  14.       
  15.     private static final String TAG = "MainActivity";  
  16.       
  17.     @Override  
  18.     public void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.         setContentView(R.layout.main);  
  21.     }  
  22.       
  23.     private ServiceConnection conn = new ServiceConnection() {  
  24.           
  25.         @Override  
  26.         public void onServiceConnected(ComponentName name, IBinder service) {  
  27.             Log.i(TAG, "onServiceConnected called.");  
  28.         }  
  29.           
  30.         @Override  
  31.         public void onServiceDisconnected(ComponentName name) {  
  32.         }  
  33.     };  
  34.       
  35.     /** 
  36.      * 啓動服務 
  37.      * @param view 
  38.      */  
  39.     public void start(View view) {  
  40.         Intent intent = new Intent(this, MyService.class);  
  41.         startService(intent);  
  42.     }  
  43.       
  44.     /** 
  45.      * 綁定服務 
  46.      * @param view 
  47.      */  
  48.     public void bind(View view) {  
  49.         Intent intent = new Intent(this, MyService.class);  
  50.         bindService(intent, conn, Context.BIND_AUTO_CREATE);  
  51.     }  
  52.       
  53.     /** 
  54.      * 解除綁定 
  55.      * @param view 
  56.      */  
  57.     public void unbind(View view) {  
  58.         unbindService(conn);  
  59.     }  
  60.       
  61.     /** 
  62.      * 停止服務 
  63.      * @param view 
  64.      */  
  65.     public void stop(View view) {  
  66.         Intent intent = new Intent(this, MyService.class);  
  67.         stopService(intent);  
  68.     }  
  69. }  
在MainActivity中包含了四個按鈕事件,分別是startService、bindService、unbindService和stopService,我們逐一地按下,看看都發生了什麼。

首先按下啓動服務的按鈕,MyService打印如下:

恩,意料之中。然後我們再按下綁定服務的按鈕,MyService打印如下:


此時,只有onBind被調用,之後兩者就綁定成功。我們再按下解除綁定的按鈕,MyService打印如下:


此時,onUnbind方法方法被調用,注意,此時MyService並沒有因解除綁定而終止,而是繼續運行。也許我們心裏會問,如果多次按下綁定服務的按鈕或重複以上兩個步驟,結果如何呢?答案是onBind和onUnbind都不會再被調用了。看不到onBind被調用,是不是沒有綁定成功啊,我們來看一下MainActivity打印信息:


重複按下綁定按鈕,幾次都綁定成功了。最後我們按下停止服務的按鈕,MyService打印如下:


此時,onDestroy被調用了,此時MyService停止了運行,整個生命週期結束。

以上就是關於MyService生命週期的講解,下面我們來介紹一下如何與服務進行通信。與服務之間的通信可以分爲兩種,進程內的通信和進程間的通信,前者調用者和服務在同一應用進程內,而後者是分佈在不同應用進程中的。

2.進程內與服務通信

進程內與服務通信實際上就是通過bindService的方式與服務綁定,獲取到通信中介Binder實例,然後通過調用這個實例的方法,完成對服務的各種操作。我們上面也介紹了不少關於bindService的內容,下面我們就針對實際需求對代碼做改動。首先是MyService,代碼如下:

[java] view plain copy
  1. package com.scott.service;  
  2.   
  3. import android.app.Service;  
  4. import android.content.Intent;  
  5. import android.os.Binder;  
  6. import android.os.IBinder;  
  7. import android.util.Log;  
  8.   
  9. public class MyService extends Service {  
  10.   
  11.     private static final String TAG = "MyService";  
  12.       
  13.     @Override  
  14.     public IBinder onBind(Intent intent) {  
  15.         Log.i(TAG, "onBind called.");  
  16.         return new MyBinder();  
  17.     }  
  18.       
  19.     /** 
  20.      * 綁定對象 
  21.      * @author user 
  22.      * 
  23.      */  
  24.     public class MyBinder extends Binder {  
  25.           
  26.         /** 
  27.          * 問候 
  28.          * @param name 
  29.          */  
  30.         public void greet(String name) {  
  31.             Log.i(TAG, "hello, " + name);  
  32.         }  
  33.     }  
  34. }  
我們創建了一個MyBinder的內部類,定義了一個greet方法,在onBind方法中就將這個MyBinder的實例返回,只要調用者獲取到這個實例,就可以像拿着遊戲手柄一樣對服務進行操作。我們來看一下調用者的代碼吧,MainActivity代碼如下:

[java] view plain copy
  1. package com.scott.service;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ComponentName;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.ServiceConnection;  
  8. import android.os.Bundle;  
  9. import android.os.IBinder;  
  10. import android.view.View;  
  11.   
  12. public class MainActivity extends Activity {  
  13.       
  14.     /** 
  15.      * 綁定對象實例 
  16.      */  
  17.     private MyService.MyBinder binder;  
  18.       
  19.     @Override  
  20.     public void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.main);  
  23.     }  
  24.       
  25.     private ServiceConnection conn = new ServiceConnection() {  
  26.           
  27.         @Override  
  28.         public void onServiceConnected(ComponentName name, IBinder service) {  
  29.             binder = (MyService.MyBinder) service;  //獲取其實例  
  30.             binder.greet("scott");                  //調用其方法  
  31.         }  
  32.           
  33.         @Override  
  34.         public void onServiceDisconnected(ComponentName name) {  
  35.         }  
  36.     };  
  37.       
  38.     /** 
  39.      * 綁定服務 
  40.      * @param view 
  41.      */  
  42.     public void bind(View view) {  
  43.         Intent intent = new Intent(this, MyService.class);  
  44.         bindService(intent, conn, Context.BIND_AUTO_CREATE);  
  45.     }  
  46.       
  47.     /** 
  48.      * 解除綁定 
  49.      * @param view 
  50.      */  
  51.     public void unbind(View view) {  
  52.         unbindService(conn);  
  53.     }  
  54. }  
在上面的代碼中,我們是在綁定服務成功時將IBinder類型的service參數強轉爲MyService.MyBinder類型,獲取綁定中介實例,然後調用其greet方法。

操作一下,看看效果如何。先點擊綁定服務的按鈕,MyService打印如下:


需要注意的是,與服務綁定是一個異步的過程,也就是說,在這一刻我們綁定服務,下一刻我們去操作binder對象,也許它還爲null,這就容易引起空指針異常,正確的做法是把這些操作放到綁定成功之後,確保萬無一失。

以上就是進程內通信的內容。

關於進程間通信的內容,限於篇幅原因,這裏就不再陳述了,大家可以看我之前的兩篇文章,做一下了解:

使用AIDL實現進程間的通信

使用AIDL實現進程間的通信之複雜類型傳遞


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