Android service ( 一 ) 三種開啟服務方法


一、 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

 

 

context.startService() 啟動再context.bindService()啟動:

 

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

先startService()再context.bindService()的話必須context.stopService() 和 context.onUnbind()都調用才可以停止服務
 

 

使用再context.bindService()啟動時,需要在Service中創建一個IBind接口實例然后通過onBind()方法傳入通道,在Activity中要創建一個ServiceConnection()對象作為通道然后實現其中兩個方法來獲取Service放入通道的IBind接口對象。

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()方法便可以停止服務,無論調用了多少次的啟動服務方法。

 

service可以在和多場合的應用中使用,比如播放多媒體的時候用戶啟動了其他Activity這個時候程序要在后台繼續播放,比如檢測SD卡上文件的變化,再或者在后台記錄你地理信息位置的改變等等,總之服務嘛,總是藏在后頭的。
 

四、 Service示例

 

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

先從使用startService啟動Service學起

首先編寫一個Activity

 

public class PlayMusic extends Activity implements OnClickListener {
    private static final String TAG = "PlayMusic";
    private Button playBtn;
    private Button stopBtn;
    private Button pauseBtn;
    private Button exitBtn;
    private Button closeBtn;
 
    //....(詳見源碼)
 
@Override
    public void onClick(View v) {
        int op = -1;
        Intent intent = new Intent("org.allin.android.musicService");
         
        //廣播用
//      Intent intent = new Intent("org.allin.android.musicReceiver");
         
        switch (v.getId()) {
        case R.id.play:
            Log.d(TAG, "onClick: playing muic");
            op = 1;
            break;
        case R.id.stop:
            Log.d(TAG, "onClick: stoping music");
            op = 2;
            break;
        case R.id.pause:
            Log.d(TAG, "onClick: pausing music");
            op = 3;
            break;
        case R.id.close:
            Log.d(TAG, "onClick: close");
            this.finish();
            break;
        case R.id.exit:
            Log.d(TAG, "onClick: exit");
            op = 4;
            stopService(intent);
            this.finish();
            break;
        }
         
        Bundle bundle  = new Bundle();
        bundle.putInt("op", op);
        intent.putExtras(bundle);
        startService(intent);
         
//      sendBroadcast(intent);
    }
 
 
}

 

通過重寫onClick方法來實現對播放音樂的控制。這里把播放音樂的各種操作用數字的方式通過Intent傳遞給service。 
構造一個Intent ,ntent intent = new Intent("org.allin.android.musicService");
"org.allin.android.musicService"是在AndroidManifest.xml文件中對service類的定義

 

<service android:enabled="true" android:name=".MusicService">
<intent-filter>
<action android:name="org.allin.android.musicService" />
</intent-filter>
</service>

把操作碼放在Bundle中 
Bundle bundle  = new Bundle();
bundle.putInt("op", op);
intent.putExtras(bundle);
最后使用startService(intent);啟動服務。 
下面看看Service是怎么實現的。
 
MusicService.java

 

/**
 * @author allin.dev
 * http://allin.cnblogs.com/
 * 
 */
public class MusicService extends Service {
 
    private static final String TAG = "MyService";
    private MediaPlayer mediaPlayer;
 
    /*
     * (non-Javadoc)
     * 
     * @see android.app.Service#onBind(android.content.Intent)
     */
    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }
 
    @Override
    public void onCreate() {
        Log.v(TAG, "onCreate");
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(this, R.raw.tmp);
            mediaPlayer.setLooping(false);
        }
    }
 
    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy");
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }
 
    @Override
    public void onStart(Intent intent, int startId) {
        Log.v(TAG, "onStart");
        if (intent != null) {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
 
                int op = bundle.getInt("op");
                switch (op) {
                case 1:
                    play();
                    break;
                case 2:
                    stop();
                    break;
                case 3:
                    pause();
                    break;
                }
 
            }
        }
 
    }
 
    public void play() {
        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }
 
    public void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }
 
    public void stop() {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            try {
                // 在調用stop后如果需要再次通過start進行播放,需要之前調用prepare函數
                mediaPlayer.prepare();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
 
}

服務 使用了系統自帶MediaPlayer進行音樂的播放控制。 當調用了startService后服務會先調用onCreate,我們在里面對MediaPlayer進行初始化。接着會調用onStart,可以看到傳遞給startService()的Intent對象會傳遞給onStart()方法,這樣我們就可以得到intent里面的操作碼: 
Iundle bundle = intent.getExtras(); 
int op = bundle.getInt("op");


圖中的”close”和“exit”是不同的,close只是調用finish()退出當前的Activity,但是Service並沒有關掉,音樂會繼續播放。而exit就是調用了stopService(intent);來停止服務,Service會調用onDestroy()方法來對mediaPlayer進行停止和釋放資源。
有時候如果服務只提供一些操作接口,我們也可以通過廣播的g方式來啟動服務。
首先要定義一個Receiver,並繼承BroadcastReceiver,然后在AndroidManifest.xml中進行注冊:

 

<receiver android:name=".MusicReceiver">
<intent-filter>
<action android:name="org.allin.android.musicReceiver" />
</intent-filter>
</receiver>

 

 Receiver的實現:

MusicReceiver.java
/**
 * @author allin.dev
 * http://allin.cnblogs.com/
 *
 */
public class MusicReceiver extends BroadcastReceiver {
 
    private static final String TAG = "MusicReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive");
        Intent it = new Intent("org.allin.android.musicService");
        Bundle bundle = intent.getExtras();
        it.putExtras(bundle);
         
        if(bundle != null){
            int op = bundle.getInt("op");
            if(op == 4){
                context.stopService(it);
            }else{
                context.startService(it);
            }
        }
         
    }
 
}
 


然后對PlayMusic中的onclick方法進行些改造,把Intent指向Receiver
Intent intent = new Intent("org.allin.android.musicReceiver");
intent中綁定的操作碼都不變,再調用sendBroadcast(intent);把intentg廣播出去。
MusicReceiver接受到廣播后根據操作碼進行相應的操作。


接下來的例子就是使用bindService來啟動Service
首先一樣是寫一個Activity

 

public class PlayBindMusic extends Activity implements OnClickListener {
 
    private static final String TAG = "PlayBindMusic";
    private Button playBtn;
    private Button stopBtn;
    private Button pauseBtn;
    private Button exitBtn;
     
    private BindMusicService musicService;
 
    @Override
    public void onClick(View v) {
 
        switch (v.getId()) {
        case R.id.play:
            Log.d(TAG, "onClick: binding srvice");
            musicService.play();
            break;
        case R.id.stop:
            Log.d(TAG, "onClick: stoping srvice");
            if(musicService != null){
                musicService.stop();
            }
            break;
        case R.id.pause:
            Log.d(TAG, "onClick: pausing srvice");
            if(musicService != null){
                musicService.pause();
            }
            break;
        case R.id.exit:
            Log.d(TAG, "onClick: exit");
            this.finish();
            break;
        }
    }
 
 
private void connection(){
        Log.d(TAG, "connecting.....");
        Intent intent = new Intent("org.allin.android.bindService");
        bindService(intent, sc, Context.BIND_AUTO_CREATE);
         
    }
private ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            musicService = null;
            Log.d(TAG, "in onServiceDisconnected");
        }
         
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            musicService = ((BindMusicService.MyBinder)(service)).getService();
            if(musicService != null){
                musicService.play();
            }
             
            Log.d(TAG, "in onServiceConnected");
        }
    };
}

 

這里使用了bindService(intent, sc, Context.BIND_AUTO_CREATE);來啟動服務的,
我們需要定義ServiceConnectionnn,並實現里面的方法,當服務綁定成功后會調用ServiceConnectionnn中的回調函數:
public void onServiceConnected(ComponentName name, IBinder service),
回調函數里面使用musicService = ((BindMusicService.MyBinder)(service)).getService();來獲取BindMusicService服務對象,有了BindMusicService實例對象,就可以調用服務提供的各種控制音樂播放的哦功能。
下面看看BindMusicService.java的實現:

 

/**
 * @author allin.dev
 * http://allin.cnblogs.com/
 */
public class BindMusicService extends Service {
 
    private static final String TAG = "MyService";
    private MediaPlayer mediaPlayer;
 
    private final IBinder binder = new MyBinder();
 
    public class MyBinder extends Binder {
        BindMusicService getService() {
            return BindMusicService.this;
        }
    }
 
    /*
     * (non-Javadoc)
     * 
     * @see android.app.Service#onBind(android.content.Intent)
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        play();
        return binder;
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
         
        Log.d(TAG, "onCreate");
        Toast.makeText(this, "show media player", Toast.LENGTH_SHORT).show();
         
         
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
         
        Log.d(TAG, "onDestroy");
        Toast.makeText(this, "stop media player", Toast.LENGTH_SHORT);
        if(mediaPlayer != null){
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }
 
     
    public void play() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(this, R.raw.tmp);
            mediaPlayer.setLooping(false);
        }
        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }
 
    public void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }
 
    public void stop() {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            try {
                // 在調用stop后如果需要再次通過start進行播放,需要之前調用prepare函數
                mediaPlayer.prepare();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
 
}
 

我們看到Service中有個返回IBinder對象的onBind方法,這個方法會在Service被綁定到其他程序上時被調用,而這個IBinder對象和之前看到的onServiceConnected方法中傳入的那個IBinder是同一個東西。應用和Service間就依靠這個IBinder對象進行通信。

 

 

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

 

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發生了什么,廣播接收者應該作為一個空服務而不是簡單的把耗時的操作單獨放在一個線程里。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM