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

Android——Service總結

編輯:關於Android編程

一、 Service簡介

Service是android 系統中的四大組件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的級別差不多,但不能自己運行只能後台運行,並且可以和其他組件進行交互。service可以在很多場合的應用中使用,比如播放多媒體的時候用戶啟動了其他Activity這個時候程序要在後台繼續播放,比如檢測SD卡上文件的變化,再或者在後台記錄你地理信息位置的改變等等,總之服務總是藏在後台的。

Service的啟動有兩種方式:context.startService()context.bindService()

二、 Service啟動流程

context.startService()啟動流程:

context.startService() -> onCreate()->onStart()->Service running->context.stopService()->onDestroy()->Service stop

如果Service還沒有運行,則android先調用onCreate(),然後調用onStart();

如果Service已經運行,則只調用onStart(),所以一個Service的onStart方法可能會重復調用多次。

如果stopService的時候會直接onDestroy,如果是調用者自己直接退出而沒有調用stopService的話,Service會一直在後台運行,該Service的調用者再啟動起來後可以通過stopService關閉Service。

所以調用startService的生命周期為:onCreate-->onStart(可多次調用) -->onDestroy

context.bindService()啟動流程:

context.bindService() ->onCreate() ->onBind() ->Service running ->onUnbind() ->onDestroy() ->Service stop

onBind()將返回給客戶端一個IBind接口實例,IBind允許客戶端回調服務的方法,比如得到Service的實例、運行狀態或其他操作。這個時候把調用者(Context,例如Activity)會和Service綁定在一起,Context退出了,Srevice就會調用onUnbind->onDestroy相應退出。

所以調用bindService的生命周期為:onCreate --> onBind(只一次,不可多次綁定) --> onUnbind --> onDestory。

在Service每一次的開啟關閉過程中,只有onStart可被多次調用(通過多次startService調用),其他onCreate,onBind,onUnbind,onDestory在一個生命周期中只能被調用一次。

\

三、 Service生命周期

Service的生命周期並不像Activity那麼復雜,它只繼承了onCreate()、onStart()、onDestroy()三個方法

當我們第一次啟動Service時,先後調用了onCreate()、onStart()這兩個方法;當停止Service時,則執行onDestroy()方法。

這裡需要注意的是,如果Service已經啟動了,當我們再次啟動Service時,不會在執行onCreate()方法,而是直接執行onStart()方法。

它可以通過Service.stopSelf()方法或者Service.stopSelfResult()方法來停止自己,只要調用一次stopService()方法便可以停止服務,無論調用了多少次的啟動服務方法。

四、關於bound service

若service允許被別的組件綁定的話,我們必須實現onBind()方法,該方法將返回一個IBinder對象(在啟動方式的service中該方法返回null),以便實現和客戶端進行交互。

客戶端可以通過調用bindService()方法實現綁定,系統會調用服務的onBind(Intent)回調方法,返回一個用於和服務進行交互的IBinder 對象。之後我們必須實現ServiceConnection接口接收IBinder對象,來監控客戶端和服務器之間的連接。綁定是異步進行的,bindService()將立即返回,並不會立即向客戶端返回IBinder對象。但是我們可以通過ServiceConnection接口中的方法接收IBinder對象,完成通信。

多個客戶端可以綁定同一個service,但是只有第一個才會調用onBind()並檢索IBinder對象。其它客戶端直接接收相同的IBinder對象,而不再調用該方法。

當最後一個客戶端解綁service時,系統銷毀該service,除非startService()方式啟動的service。

一個service可以同時started和綁定。若一個service可以同時被started和綁定,那麼當service以started方式啟動時,當所有客戶端解綁時,系統不會銷毀該service。而是必須通過stopSelf()或stopService()。

盡管通常情況下只需要實現onBind()和onStartCommand()兩者中的一個,但是有時候需要實現這兩種方式。例如一個音樂播放器,當activity啟動service時可以播放音樂,即使用戶離開應用;當用戶又回來時activity又可以綁定service,重新控制service。


五、 Service示例

下面我做了一個簡單的音樂播放的應用,分別使用startService和bindService來啟動本地的服務。

Activity

  1. publicclassPlayMusicServiceextendsActivityimplementsOnClickListener{
  2.  
  3. privateButtonplayBtn;
  4. privateButtonstopBtn;
  5. privateButtonpauseBtn;
  6. privateButtonexitBtn;
  7. privateButtoncloseBtn;
  8.  
  9. privateIntentintent;
  10.  
  11. @Override
  12. publicvoidonCreate(BundlesavedInstanceState){
  13. super.onCreate(savedInstanceState);
  14. setContentView(R.layout.music_service);
  15.  
  16. playBtn=(Button)findViewById(R.id.play);
  17. stopBtn=(Button)findViewById(R.id.stop);
  18. pauseBtn=(Button)findViewById(R.id.pause);
  19. exitBtn=(Button)findViewById(R.id.exit);
  20. closeBtn=(Button)findViewById(R.id.close);
  21.  
  22. playBtn.setOnClickListener(this);
  23. stopBtn.setOnClickListener(this);
  24. pauseBtn.setOnClickListener(this);
  25. exitBtn.setOnClickListener(this);
  26. closeBtn.setOnClickListener(this);
  27.  
  28. }
  29.  
  30. @Override
  31. publicvoidonClick(Viewv){
  32. intop=-1;
  33. intent=newIntent("com.homer.service.musicService");
  34.  
  35. switch(v.getId()){
  36. caseR.id.play://playmusic
  37. op=1;
  38. break;
  39. caseR.id.stop://stopmusic
  40. op=2;
  41. break;
  42. caseR.id.pause://pausemusic
  43. op=3;
  44. break;
  45. caseR.id.close://closeactivity
  46. this.finish();
  47. break;
  48. caseR.id.exit://stopService
  49. op=4;
  50. stopService(intent);
  51. this.finish();
  52. break;
  53. }
  54.  
  55. Bundlebundle=newBundle();
  56. bundle.putInt("op",op);
  57. intent.putExtras(bundle);
  58.  
  59. startService(intent);//startService
  60. }
  61.  
  62. @Override
  63. publicvoidonDestroy(){
  64. super.onDestroy();
  65.  
  66. if(intent!=null){
  67. stopService(intent);
  68. }
  69. }
  70. }

Service

  1. publicclassMusicServiceextendsService{
  2. privatestaticfinalStringTAG="MyService";
  3.  
  4. privateMediaPlayermediaPlayer;
  5.  
  6. @Override
  7. publicIBinderonBind(Intentarg0){
  8. returnnull;
  9. }
  10.  
  11. @Override
  12. publicvoidonCreate(){
  13. Log.v(TAG,"onCreate");
  14. Toast.makeText(this,"showmediaplayer",Toast.LENGTH_SHORT).show();
  15.  
  16. if(mediaPlayer==null){
  17. mediaPlayer=MediaPlayer.create(this,R.raw.tmp);
  18. mediaPlayer.setLooping(false);
  19. }
  20. }
  21.  
  22. @Override
  23. publicvoidonDestroy(){
  24. Log.v(TAG,"onDestroy");
  25. Toast.makeText(this,"stopmediaplayer",Toast.LENGTH_SHORT);
  26. if(mediaPlayer!=null){
  27. mediaPlayer.stop();
  28. mediaPlayer.release();
  29. }
  30. }
  31.  
  32. @Override
  33. publicvoidonStart(Intentintent,intstartId){
  34. Log.v(TAG,"onStart");
  35. if(intent!=null){
  36. Bundlebundle=intent.getExtras();
  37. if(bundle!=null){
  38. intop=bundle.getInt("op");
  39. switch(op){
  40. case1:
  41. play();
  42. break;
  43. case2:
  44. stop();
  45. break;
  46. case3:
  47. pause();
  48. break;
  49. }
  50. }
  51. }
  52. }
  53.  
  54. publicvoidplay(){
  55. if(!mediaPlayer.isPlaying()){
  56. mediaPlayer.start();
  57. }
  58. }
  59.  
  60. publicvoidpause(){
  61. if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
  62. mediaPlayer.pause();
  63. }
  64. }
  65.  
  66. publicvoidstop(){
  67. if(mediaPlayer!=null){
  68. mediaPlayer.stop();
  69. try{
  70. mediaPlayer.prepare();//在調用stop後如果需要再次通過start進行播放,需要之前調用prepare函數
  71. }catch(IOExceptionex){
  72. ex.printStackTrace();
  73. }
  74. }
  75. }
  76. }

AndroidManifest.xml

注冊activity

  1. android:name=".service.PlayMusicService"
  2. android:label="@string/app_name"/>

注冊service

  1. android:name=".service.MusicService"
  2. android:enabled="true">
  3.  
  4.  


六、 代碼解析

1、Activity中,PlayMusicService中通過重寫OnClickListener 接口onClick()方法實現對播放音樂的控制,把音樂各種操作用數字通過Intent傳遞給service

然後通過構造一個Intent , intent = new Intent("com.homer.service.musicService");

其中,com.homer.service.musicService是AndroidManifest.xml 對service的定義,即上面“注冊service”

2、Activity中,音樂播放的控制,利用Bundle綁定數字op後,通過startService(intent); 服務後發送出去
Bundle bundle = new Bundle();
bundle.putInt("op", op);
intent.putExtras(bundle);

startService(intent);

3、 Service中,會處理Activity啟動的startService(intent);服務,依次調用service的啟動過程:onCreate --> onStart(可多次調用) --> onDestroy

onCreate(), 創建mediaPlayer

onStart(), 通過獲取Bundle bundle = intent.getExtras();,提取int op = bundle.getInt("op");,然後執行響應的音樂播放操作

onDestroy(),停止並釋放mediaPlayer音樂資源,如果當執行context.stopService()時調用此方法

4、Activity中,onClick()函數中close與exit是執行含義是不同的:

close : 只是執行了this.finish(); 關閉了本Activity窗體,service並沒有被關掉,音樂依然會繼續在後台播放

exit : 先調用了stopService(intent); 關閉了service服務,在Service中會調用3中的onDestroy()停止並釋放音樂資源,後才執行this.finish(); 關閉了本Activity窗體

七、IntentService

不管是何種Service,它默認都是在應用程序的主線程(亦即UI線程)中運行的。所以,如果你的Service將要運行非常耗時或者可能被阻塞的操作時,你的應用程序將會被掛起,甚至會出現ANR錯誤。為了避免這一問題,你應該在Service中重新啟動一個新的線程來進行這些操作。現有兩種方法共大家參考:

① 直接在Service的onStartCommand()方法中重啟一個線程來執行。

② Android SDK 中為我們提供了一個現成的Service類來實現這個功能,它就是IntentService,官方文檔上對IntentService 是這樣描述的:
1、IntentService 會創建一個線程,來處理所有傳給onStartCommand()的Intent請求。
2、對於startService()請求執行onHandleIntent()中的耗時任務,會生成一個隊列,每次只有一個Intent傳入onHandleIntent()方法並執行。也就是同一時間只會有一個耗時任務被執行,其他的請求還要在後面排隊, onHandleIntent()方法不會多線程並發執行。
3、當所有startService()請求被執行完成後,IntentService 會自動銷毀,所以不需要自己寫stopSelf()或stopService()來銷毀服務。
4、提供默認的onBind()實現 ,即返回null,不適合綁定的 Service。
5、提供默認的 onStartCommand() 實現,將intent傳入等待隊列中,然後到onHandleIntent()的實現。所以如果需要重寫onStartCommand() 方法一定要調用父類的實現。

IntentService中,對於每個Intent請求,在onHandleIntent()中執行一個耗時5秒的任務:

public class TestIntentService extends IntentService {  
  
    public TestIntentService() {  
        super("TestIntentService");  
    }  
  
    @Override  
    protected void onHandleIntent(Intent intent) {  
        DateFormat format = DateFormat.getTimeInstance();  
        Log.v("test", "onHandleIntent開始:" + format.format(new Date()));  
          
        // 這裡Thread.sleep(5000)模擬執行一個耗時5秒的任務  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
        }  
          
        Log.v("test", "onHandleIntent完成:" + format.format(new Date()));  
    }  
      
    // 這裡還重寫了onDestroy,記錄日志用於觀察Service何時銷毀  
    @Override  
    public void onDestroy() {  
        Log.v("test", "onDestroy");  
        super.onDestroy();  
    }  
}  
在Activity中,添加一個按鈕,通過startService()來啟動Service:
public class MainActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Button button = (Button) findViewById(R.id.button);  
        button.setOnClickListener(new OnClickListener() {  
              
            @Override  
            public void onClick(View view) {  
                startService(new Intent(MainActivity.this, TestIntentService.class));  
            }  
        });   
    }  
}  

運行程序,只點擊一次按鈕, Log:

onHandleIntent開始:5:38:01 AM
onHandleIntent完成:5:38:06 AM
onDestroy

連續點擊3次按鈕,Log:

onHandleIntent開始:5:42:14 AM
onHandleIntent完成:5:42:19 AM
onHandleIntent開始:5:42:19 AM
onHandleIntent完成:5:42:24 AM
onHandleIntent開始:5:42:24 AM
onHandleIntent完成:5:42:29 AM
onDestroy

多次startService請求執行耗時任務,不會並發執行onHandleIntent()方法,而是一個一個順序執行。當所有的任務執行完成,IntentService會自動銷毀。

七、 拓展知識(進程和聲明周期)

Android操作系統嘗試盡可能長時間的保持應用的進程,但當可用內存很低時最終要移走一部分進程。怎樣確定那些程序可以運行,那些要被銷毀,Android讓每一個進程在一個重要級的基礎上運行,重要級低的進程最有可能被淘汰,一共有5級,下面這個列表就是按照重要性排列的:

1 一個前台進程顯示的是用戶此時需要處理和顯示的。下列的條件有任何一個成立,這個進程都被認為是在前台運行的。
a 與用戶正發生交互的。
b 它控制一個與用戶交互的必須的基本的服務。
c 有一個正在調用生命周期的回調函數的service(如onCreate()、onStar()、onDestroy())
d 它有一個正在運行onReceive()方法的廣播接收對象。
只有少數的前台進程可以在任何給定的時間內運行,銷毀他們是系統萬不得已的、最後的選擇——當內存不夠系統繼續運行下去時。通常,在這一點上,設備已經達到了內存分頁狀態,所以殺掉一些前台進程來保證能夠響應用戶的需求。

2 一個可用進程沒有任何前台組件,但它仍然可以影響到用戶的界面。下面兩種情況發生時,可以稱該進程為可用進程。
它是一個非前台的activity,但對用戶仍然可用(onPause()方法已經被調用)這是可能發生的,例如:前台的activity是一個允許上一個activity可見的對話框,即當前activity半透明,能看到前一個activity的界面,它是一個服務於可用activity的服務。

3 一個服務進程是一個通過調用startService()方法啟動的服務,並且不屬於前兩種情況。盡管服務進程沒有直接被用戶看到,但他們確實是用戶所關心的,比如後台播放音樂或網絡下載數據。所以系統保證他們的運行,直到不能保證所有的前台可見程序都正常運行時才會終止他們。

4 一個後台進程就是一個非當前正在運行的activity(activity的onStop()方法已經被調用),他們不會對用戶體驗造成直接的影響,當沒有足夠內存來運行前台可見程序時,他們將會被終止。通常,後台進程會有很多個在運行,所以他們維護一個LRU最近使用程序列表來保證經常運行的activity能最後一個被終止。如果一個activity正確的實現了生命周期的方法,並且保存它當前狀態,殺死這些進程將不會影響到用戶體驗。

5 一個空線程沒有運行任何可用應用程序組,保留他們的唯一原因是為了設立一個緩存機制,來加快組件啟動的時間。系統經常殺死這些內存來平衡系統的整個系統的資源,進程緩存和基本核心緩存之間的資源。
Android把進程裡優先級最高的activity或服務,作為這個進程的優先級。例如,一個進程擁有一個服務和一個可見的activity,那麼這個進程將會被定義為可見進程,而不是服務進程。

此外,如果別的進程依賴某一個進程的話,那麼被依賴的進程會提高優先級。一個進程服務於另一個進程,那麼提供服務的進程不會低於獲得服務的進程。例如,如果進程A的一個內容提供商服務於進程B的一個客戶端,或者進程A的一個service被進程B的一個組件綁定,那麼進程A至少擁有和進程B一樣的優先級,或者更高。

因為一個運行服務的進程的優先級高於運行後台activity的進程,一個activity會准備一個長時間運行的操作來啟動一個服務,而不是啟動一個線程–尤其是這個操作可能會拖垮這個activity。例如後台播放音樂的同時,通過照相機向服務器發送一張照片,啟動一個服務會保證這個操作至少運行在service 進程的優先級下,無論這個activity發生了什麼,廣播接收者應該作為一個空服務而不是簡單的把耗時的操作單獨放在一個線程裡。


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